1*f1939c6aSGatien Chevallier // SPDX-License-Identifier: GPL-2.0-only 2*f1939c6aSGatien Chevallier /* 3*f1939c6aSGatien Chevallier * Copyright (C) 2026, STMicroelectronics - All Rights Reserved 4*f1939c6aSGatien Chevallier */ 5*f1939c6aSGatien Chevallier 6*f1939c6aSGatien Chevallier #include <linux/bus/stm32_firewall.h> 7*f1939c6aSGatien Chevallier #include <linux/bus/stm32_firewall_device.h> 8*f1939c6aSGatien Chevallier #include <linux/device.h> 9*f1939c6aSGatien Chevallier #include <linux/err.h> 10*f1939c6aSGatien Chevallier #include <linux/kernel.h> 11*f1939c6aSGatien Chevallier #include <linux/module.h> 12*f1939c6aSGatien Chevallier #include <linux/of.h> 13*f1939c6aSGatien Chevallier #include <linux/of_platform.h> 14*f1939c6aSGatien Chevallier #include <linux/platform_device.h> 15*f1939c6aSGatien Chevallier #include <linux/pm_runtime.h> 16*f1939c6aSGatien Chevallier #include <linux/tee_drv.h> 17*f1939c6aSGatien Chevallier #include <linux/types.h> 18*f1939c6aSGatien Chevallier 19*f1939c6aSGatien Chevallier enum stm32_dbg_profile { 20*f1939c6aSGatien Chevallier PERIPHERAL_DBG_PROFILE = 0, 21*f1939c6aSGatien Chevallier HDP_DBG_PROFILE = 1, 22*f1939c6aSGatien Chevallier }; 23*f1939c6aSGatien Chevallier 24*f1939c6aSGatien Chevallier enum stm32_dbg_pta_command { 25*f1939c6aSGatien Chevallier /* 26*f1939c6aSGatien Chevallier * PTA_CMD_GRANT_DBG_ACCESS - Verify the debug configuration against the given debug profile 27*f1939c6aSGatien Chevallier * and grant access or not 28*f1939c6aSGatien Chevallier * 29*f1939c6aSGatien Chevallier * [in] value[0].a Debug profile to grant access to. 30*f1939c6aSGatien Chevallier */ 31*f1939c6aSGatien Chevallier PTA_CMD_GRANT_DBG_ACCESS, 32*f1939c6aSGatien Chevallier }; 33*f1939c6aSGatien Chevallier 34*f1939c6aSGatien Chevallier /** 35*f1939c6aSGatien Chevallier * struct stm32_dbg_bus - OP-TEE based STM32 debug bus private data 36*f1939c6aSGatien Chevallier * @dev: STM32 debug bus device. 37*f1939c6aSGatien Chevallier * @ctx: OP-TEE context handler. 38*f1939c6aSGatien Chevallier */ 39*f1939c6aSGatien Chevallier struct stm32_dbg_bus { 40*f1939c6aSGatien Chevallier struct device *dev; 41*f1939c6aSGatien Chevallier struct tee_context *ctx; 42*f1939c6aSGatien Chevallier }; 43*f1939c6aSGatien Chevallier 44*f1939c6aSGatien Chevallier /* Expect at most 1 instance of this driver */ 45*f1939c6aSGatien Chevallier static struct stm32_dbg_bus *stm32_dbg_bus_priv; 46*f1939c6aSGatien Chevallier 47*f1939c6aSGatien Chevallier static int stm32_dbg_pta_open_session(u32 *id) 48*f1939c6aSGatien Chevallier { 49*f1939c6aSGatien Chevallier struct tee_client_device *dbg_bus_dev = to_tee_client_device(stm32_dbg_bus_priv->dev); 50*f1939c6aSGatien Chevallier struct tee_ioctl_open_session_arg sess_arg; 51*f1939c6aSGatien Chevallier int ret; 52*f1939c6aSGatien Chevallier 53*f1939c6aSGatien Chevallier memset(&sess_arg, 0, sizeof(sess_arg)); 54*f1939c6aSGatien Chevallier export_uuid(sess_arg.uuid, &dbg_bus_dev->id.uuid); 55*f1939c6aSGatien Chevallier sess_arg.clnt_login = TEE_IOCTL_LOGIN_REE_KERNEL; 56*f1939c6aSGatien Chevallier 57*f1939c6aSGatien Chevallier ret = tee_client_open_session(stm32_dbg_bus_priv->ctx, &sess_arg, NULL); 58*f1939c6aSGatien Chevallier if (ret < 0 || sess_arg.ret) { 59*f1939c6aSGatien Chevallier dev_err(stm32_dbg_bus_priv->dev, "Failed opening tee session, err: %#x\n", 60*f1939c6aSGatien Chevallier sess_arg.ret); 61*f1939c6aSGatien Chevallier return -EOPNOTSUPP; 62*f1939c6aSGatien Chevallier } 63*f1939c6aSGatien Chevallier 64*f1939c6aSGatien Chevallier *id = sess_arg.session; 65*f1939c6aSGatien Chevallier 66*f1939c6aSGatien Chevallier return 0; 67*f1939c6aSGatien Chevallier } 68*f1939c6aSGatien Chevallier 69*f1939c6aSGatien Chevallier static void stm32_dbg_pta_close_session(u32 id) 70*f1939c6aSGatien Chevallier { 71*f1939c6aSGatien Chevallier tee_client_close_session(stm32_dbg_bus_priv->ctx, id); 72*f1939c6aSGatien Chevallier } 73*f1939c6aSGatien Chevallier 74*f1939c6aSGatien Chevallier static int stm32_dbg_bus_grant_access(struct stm32_firewall_controller *ctrl, u32 dbg_profile) 75*f1939c6aSGatien Chevallier { 76*f1939c6aSGatien Chevallier struct tee_ioctl_invoke_arg inv_arg = {0}; 77*f1939c6aSGatien Chevallier struct tee_param param[1] = {0}; 78*f1939c6aSGatien Chevallier u32 session_id; 79*f1939c6aSGatien Chevallier int ret; 80*f1939c6aSGatien Chevallier 81*f1939c6aSGatien Chevallier if (dbg_profile != PERIPHERAL_DBG_PROFILE && dbg_profile != HDP_DBG_PROFILE) 82*f1939c6aSGatien Chevallier return -EOPNOTSUPP; 83*f1939c6aSGatien Chevallier 84*f1939c6aSGatien Chevallier ret = stm32_dbg_pta_open_session(&session_id); 85*f1939c6aSGatien Chevallier if (ret) 86*f1939c6aSGatien Chevallier return ret; 87*f1939c6aSGatien Chevallier 88*f1939c6aSGatien Chevallier inv_arg.func = PTA_CMD_GRANT_DBG_ACCESS; 89*f1939c6aSGatien Chevallier inv_arg.session = session_id; 90*f1939c6aSGatien Chevallier inv_arg.num_params = 1; 91*f1939c6aSGatien Chevallier param[0].attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT; 92*f1939c6aSGatien Chevallier param[0].u.value.a = dbg_profile; 93*f1939c6aSGatien Chevallier 94*f1939c6aSGatien Chevallier ret = tee_client_invoke_func(stm32_dbg_bus_priv->ctx, &inv_arg, param); 95*f1939c6aSGatien Chevallier if (ret < 0 || inv_arg.ret != 0) { 96*f1939c6aSGatien Chevallier dev_dbg(stm32_dbg_bus_priv->dev, 97*f1939c6aSGatien Chevallier "When invoking function, err %x, TEE returns: %x\n", ret, inv_arg.ret); 98*f1939c6aSGatien Chevallier if (!ret) 99*f1939c6aSGatien Chevallier ret = -EACCES; 100*f1939c6aSGatien Chevallier } 101*f1939c6aSGatien Chevallier 102*f1939c6aSGatien Chevallier stm32_dbg_pta_close_session(session_id); 103*f1939c6aSGatien Chevallier 104*f1939c6aSGatien Chevallier return ret; 105*f1939c6aSGatien Chevallier } 106*f1939c6aSGatien Chevallier 107*f1939c6aSGatien Chevallier /* Implement mandatory release_access ops even if it does nothing*/ 108*f1939c6aSGatien Chevallier static void stm32_dbg_bus_release_access(struct stm32_firewall_controller *ctrl, u32 dbg_profile) 109*f1939c6aSGatien Chevallier { 110*f1939c6aSGatien Chevallier } 111*f1939c6aSGatien Chevallier 112*f1939c6aSGatien Chevallier static int stm32_dbg_bus_plat_probe(struct platform_device *pdev) 113*f1939c6aSGatien Chevallier { 114*f1939c6aSGatien Chevallier struct stm32_firewall_controller *dbg_controller; 115*f1939c6aSGatien Chevallier int ret; 116*f1939c6aSGatien Chevallier 117*f1939c6aSGatien Chevallier /* Defer if OP-TEE service is not yet available */ 118*f1939c6aSGatien Chevallier if (!stm32_dbg_bus_priv) 119*f1939c6aSGatien Chevallier return -EPROBE_DEFER; 120*f1939c6aSGatien Chevallier 121*f1939c6aSGatien Chevallier dbg_controller = devm_kzalloc(&pdev->dev, sizeof(*dbg_controller), GFP_KERNEL); 122*f1939c6aSGatien Chevallier if (!dbg_controller) 123*f1939c6aSGatien Chevallier return dev_err_probe(&pdev->dev, -ENOMEM, "Couldn't allocate debug controller\n"); 124*f1939c6aSGatien Chevallier 125*f1939c6aSGatien Chevallier dbg_controller->dev = &pdev->dev; 126*f1939c6aSGatien Chevallier dbg_controller->mmio = NULL; 127*f1939c6aSGatien Chevallier dbg_controller->name = dev_driver_string(dbg_controller->dev); 128*f1939c6aSGatien Chevallier dbg_controller->type = STM32_PERIPHERAL_FIREWALL; 129*f1939c6aSGatien Chevallier dbg_controller->grant_access = stm32_dbg_bus_grant_access; 130*f1939c6aSGatien Chevallier dbg_controller->release_access = stm32_dbg_bus_release_access; 131*f1939c6aSGatien Chevallier 132*f1939c6aSGatien Chevallier ret = stm32_firewall_controller_register(dbg_controller); 133*f1939c6aSGatien Chevallier if (ret) { 134*f1939c6aSGatien Chevallier dev_err(dbg_controller->dev, "Couldn't register as a firewall controller: %d", ret); 135*f1939c6aSGatien Chevallier return ret; 136*f1939c6aSGatien Chevallier } 137*f1939c6aSGatien Chevallier 138*f1939c6aSGatien Chevallier ret = stm32_firewall_populate_bus(dbg_controller); 139*f1939c6aSGatien Chevallier if (ret) { 140*f1939c6aSGatien Chevallier dev_err(dbg_controller->dev, "Couldn't populate debug bus: %d", ret); 141*f1939c6aSGatien Chevallier stm32_firewall_controller_unregister(dbg_controller); 142*f1939c6aSGatien Chevallier return ret; 143*f1939c6aSGatien Chevallier } 144*f1939c6aSGatien Chevallier 145*f1939c6aSGatien Chevallier pm_runtime_enable(&pdev->dev); 146*f1939c6aSGatien Chevallier 147*f1939c6aSGatien Chevallier ret = of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev); 148*f1939c6aSGatien Chevallier if (ret) { 149*f1939c6aSGatien Chevallier dev_err(dbg_controller->dev, "Couldn't populate the node: %d", ret); 150*f1939c6aSGatien Chevallier stm32_firewall_controller_unregister(dbg_controller); 151*f1939c6aSGatien Chevallier return ret; 152*f1939c6aSGatien Chevallier } 153*f1939c6aSGatien Chevallier 154*f1939c6aSGatien Chevallier return 0; 155*f1939c6aSGatien Chevallier } 156*f1939c6aSGatien Chevallier 157*f1939c6aSGatien Chevallier static const struct of_device_id stm32_dbg_bus_of_match[] = { 158*f1939c6aSGatien Chevallier { .compatible = "st,stm32mp131-dbg-bus", }, 159*f1939c6aSGatien Chevallier { .compatible = "st,stm32mp151-dbg-bus", }, 160*f1939c6aSGatien Chevallier { }, 161*f1939c6aSGatien Chevallier }; 162*f1939c6aSGatien Chevallier MODULE_DEVICE_TABLE(of, stm32_dbg_bus_of_match); 163*f1939c6aSGatien Chevallier 164*f1939c6aSGatien Chevallier static struct platform_driver stm32_dbg_bus_driver = { 165*f1939c6aSGatien Chevallier .probe = stm32_dbg_bus_plat_probe, 166*f1939c6aSGatien Chevallier .driver = { 167*f1939c6aSGatien Chevallier .name = "stm32-dbg-bus", 168*f1939c6aSGatien Chevallier .of_match_table = stm32_dbg_bus_of_match, 169*f1939c6aSGatien Chevallier }, 170*f1939c6aSGatien Chevallier }; 171*f1939c6aSGatien Chevallier 172*f1939c6aSGatien Chevallier static int optee_ctx_match(struct tee_ioctl_version_data *ver, const void *data) 173*f1939c6aSGatien Chevallier { 174*f1939c6aSGatien Chevallier return (ver->impl_id == TEE_IMPL_ID_OPTEE); 175*f1939c6aSGatien Chevallier } 176*f1939c6aSGatien Chevallier 177*f1939c6aSGatien Chevallier static void stm32_dbg_bus_remove(struct tee_client_device *tee_dev) 178*f1939c6aSGatien Chevallier { 179*f1939c6aSGatien Chevallier tee_client_close_context(stm32_dbg_bus_priv->ctx); 180*f1939c6aSGatien Chevallier stm32_dbg_bus_priv = NULL; 181*f1939c6aSGatien Chevallier 182*f1939c6aSGatien Chevallier of_platform_depopulate(&tee_dev->dev); 183*f1939c6aSGatien Chevallier } 184*f1939c6aSGatien Chevallier 185*f1939c6aSGatien Chevallier static int stm32_dbg_bus_probe(struct tee_client_device *tee_dev) 186*f1939c6aSGatien Chevallier { 187*f1939c6aSGatien Chevallier struct device *dev = &tee_dev->dev; 188*f1939c6aSGatien Chevallier struct stm32_dbg_bus *priv; 189*f1939c6aSGatien Chevallier int ret = 0; 190*f1939c6aSGatien Chevallier 191*f1939c6aSGatien Chevallier if (stm32_dbg_bus_priv) 192*f1939c6aSGatien Chevallier return dev_err_probe(dev, -EBUSY, 193*f1939c6aSGatien Chevallier "A STM32 debug bus device is already initialized\n"); 194*f1939c6aSGatien Chevallier 195*f1939c6aSGatien Chevallier priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); 196*f1939c6aSGatien Chevallier if (!priv) 197*f1939c6aSGatien Chevallier return -ENOMEM; 198*f1939c6aSGatien Chevallier 199*f1939c6aSGatien Chevallier /* Open context with TEE driver */ 200*f1939c6aSGatien Chevallier priv->ctx = tee_client_open_context(NULL, optee_ctx_match, NULL, NULL); 201*f1939c6aSGatien Chevallier if (IS_ERR_OR_NULL(priv->ctx)) 202*f1939c6aSGatien Chevallier return dev_err_probe(dev, PTR_ERR_OR_ZERO(priv->ctx), "Cannot open TEE context\n"); 203*f1939c6aSGatien Chevallier 204*f1939c6aSGatien Chevallier stm32_dbg_bus_priv = priv; 205*f1939c6aSGatien Chevallier stm32_dbg_bus_priv->dev = dev; 206*f1939c6aSGatien Chevallier 207*f1939c6aSGatien Chevallier return ret; 208*f1939c6aSGatien Chevallier } 209*f1939c6aSGatien Chevallier 210*f1939c6aSGatien Chevallier static const struct tee_client_device_id optee_dbg_bus_id_table[] = { 211*f1939c6aSGatien Chevallier {UUID_INIT(0xdd05bc8b, 0x9f3b, 0x49f0, 212*f1939c6aSGatien Chevallier 0xb6, 0x49, 0x01, 0xaa, 0x10, 0xc1, 0xc2, 0x10)}, 213*f1939c6aSGatien Chevallier {} 214*f1939c6aSGatien Chevallier }; 215*f1939c6aSGatien Chevallier 216*f1939c6aSGatien Chevallier static struct tee_client_driver stm32_optee_dbg_bus_driver = { 217*f1939c6aSGatien Chevallier .id_table = optee_dbg_bus_id_table, 218*f1939c6aSGatien Chevallier .probe = stm32_dbg_bus_probe, 219*f1939c6aSGatien Chevallier .remove = stm32_dbg_bus_remove, 220*f1939c6aSGatien Chevallier .driver = { 221*f1939c6aSGatien Chevallier .name = "optee_dbg_bus", 222*f1939c6aSGatien Chevallier }, 223*f1939c6aSGatien Chevallier }; 224*f1939c6aSGatien Chevallier 225*f1939c6aSGatien Chevallier static void __exit stm32_optee_dbg_bus_driver_exit(void) 226*f1939c6aSGatien Chevallier { 227*f1939c6aSGatien Chevallier platform_driver_unregister(&stm32_dbg_bus_driver); 228*f1939c6aSGatien Chevallier tee_client_driver_unregister(&stm32_optee_dbg_bus_driver); 229*f1939c6aSGatien Chevallier } 230*f1939c6aSGatien Chevallier module_exit(stm32_optee_dbg_bus_driver_exit); 231*f1939c6aSGatien Chevallier 232*f1939c6aSGatien Chevallier static int __init stm32_optee_dbg_bus_driver_init(void) 233*f1939c6aSGatien Chevallier { 234*f1939c6aSGatien Chevallier int err; 235*f1939c6aSGatien Chevallier 236*f1939c6aSGatien Chevallier err = tee_client_driver_register(&stm32_optee_dbg_bus_driver); 237*f1939c6aSGatien Chevallier if (err) 238*f1939c6aSGatien Chevallier return err; 239*f1939c6aSGatien Chevallier 240*f1939c6aSGatien Chevallier err = platform_driver_register(&stm32_dbg_bus_driver); 241*f1939c6aSGatien Chevallier if (err) 242*f1939c6aSGatien Chevallier tee_client_driver_unregister(&stm32_optee_dbg_bus_driver); 243*f1939c6aSGatien Chevallier 244*f1939c6aSGatien Chevallier return err; 245*f1939c6aSGatien Chevallier } 246*f1939c6aSGatien Chevallier module_init(stm32_optee_dbg_bus_driver_init); 247*f1939c6aSGatien Chevallier 248*f1939c6aSGatien Chevallier MODULE_LICENSE("GPL"); 249*f1939c6aSGatien Chevallier MODULE_AUTHOR("Gatien Chevallier <gatien.chevallier@foss.st.com>"); 250*f1939c6aSGatien Chevallier MODULE_DESCRIPTION("OP-TEE based STM32 debug access bus driver"); 251