// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) 2013-2019 TRUSTONIC LIMITED * All Rights Reserved. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include #include #include #include "public/mc_user.h" #include "public/mc_admin.h" /* MC_ADMIN_DEVNODE */ #include "platform.h" /* MC_PM_RUNTIME */ #include "main.h" #include "arm.h" #include "admin.h" #include "user.h" #include "iwp.h" #include "mcp.h" #include "nq.h" #include "protocol.h" #include "client.h" #include "build_tag.h" #ifdef MC_TEE_HOTPLUG #include #endif /* Default entry for our driver in device tree */ #ifndef MC_DEVICE_PROPNAME #define MC_DEVICE_PROPNAME "arm,mcd" #endif /* Define a MobiCore device structure for use with dev_debug() etc */ static struct device_driver driver = { .name = "Trustonic" }; static struct device device = { .driver = &driver }; struct mc_device_ctx g_ctx = { .mcd = &device }; static struct { /* Device tree compatibility */ bool use_platform_driver; /* TEE start return code mutex */ struct mutex start_mutex; /* TEE start return code */ int start_ret; #ifdef MC_PM_RUNTIME /* Whether hibernation succeeded */ bool did_hibernate; /* Reboot notifications */ struct notifier_block reboot_notifier; /* PM notifications */ struct notifier_block pm_notifier; #endif /* Devices */ dev_t device; struct class *class; /* Admin device */ struct cdev admin_cdev; /* User device */ dev_t user_dev; struct cdev user_cdev; /* Debug counters */ struct mutex struct_counters_buf_mutex; char struct_counters_buf[256]; int struct_counters_buf_len; } main_ctx; static int mobicore_start(void); static void mobicore_stop(void); int kasnprintf(struct kasnprintf_buf *buf, const char *fmt, ...) { va_list args; int max_size = buf->size - buf->off; int i; va_start(args, fmt); i = vsnprintf(buf->buf + buf->off, max_size, fmt, args); if (i >= max_size) { int new_size = PAGE_ALIGN(buf->size + i + 1); char *new_buf = krealloc(buf->buf, new_size, buf->gfp); if (!new_buf) { i = -ENOMEM; } else { buf->buf = new_buf; buf->size = new_size; max_size = buf->size - buf->off; i = vsnprintf(buf->buf + buf->off, max_size, fmt, args); } } if (i > 0) buf->off += i; va_end(args); return i; } static inline void kasnprintf_buf_reset(struct kasnprintf_buf *buf) { kfree(buf->buf); buf->buf = NULL; buf->size = 0; buf->off = 0; } ssize_t debug_generic_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos, int (*function)(struct kasnprintf_buf *buf)) { struct kasnprintf_buf *buf = file->private_data; int ret = 0; mutex_lock(&buf->mutex); /* Add/update buffer */ if (!*ppos) { kasnprintf_buf_reset(buf); ret = function(buf); if (ret < 0) { kasnprintf_buf_reset(buf); goto end; } } ret = simple_read_from_buffer(user_buf, count, ppos, buf->buf, buf->off); end: mutex_unlock(&buf->mutex); return ret; } int debug_generic_open(struct inode *inode, struct file *file) { struct kasnprintf_buf *buf; file->private_data = kzalloc(sizeof(*buf), GFP_KERNEL); if (!file->private_data) return -ENOMEM; buf = file->private_data; mutex_init(&buf->mutex); buf->gfp = GFP_KERNEL; return 0; } int debug_generic_release(struct inode *inode, struct file *file) { struct kasnprintf_buf *buf = file->private_data; if (!buf) return 0; kasnprintf_buf_reset(buf); kfree(buf); return 0; } static ssize_t debug_structs_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) { return debug_generic_read(file, user_buf, count, ppos, clients_debug_structs); } static const struct file_operations debug_structs_ops = { .read = debug_structs_read, .llseek = default_llseek, .open = debug_generic_open, .release = debug_generic_release, }; static ssize_t debug_struct_counters_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos) { if (!*ppos) { int ret; mutex_lock(&main_ctx.struct_counters_buf_mutex); ret = snprintf(main_ctx.struct_counters_buf, sizeof(main_ctx.struct_counters_buf), "clients: %d\n" "cwsms: %d\n" "sessions: %d\n" "swsms: %d\n" "mmus: %d\n" "maps: %d\n" "slots: %d\n" "vm fes: %d\n" "vm maps: %d\n", atomic_read(&g_ctx.c_clients), atomic_read(&g_ctx.c_cwsms), atomic_read(&g_ctx.c_sessions), atomic_read(&g_ctx.c_wsms), atomic_read(&g_ctx.c_mmus), atomic_read(&g_ctx.c_maps), atomic_read(&g_ctx.c_slots), atomic_read(&g_ctx.c_vm_fes), atomic_read(&g_ctx.c_vm_maps)); mutex_unlock(&main_ctx.struct_counters_buf_mutex); if (ret > 0) main_ctx.struct_counters_buf_len = ret; } return simple_read_from_buffer(user_buf, count, ppos, main_ctx.struct_counters_buf, main_ctx.struct_counters_buf_len); } static const struct file_operations debug_struct_counters_ops = { .read = debug_struct_counters_read, .llseek = default_llseek, }; static inline int device_user_init(void) { struct device *dev; int ret = 0; main_ctx.user_dev = MKDEV(MAJOR(main_ctx.device), 1); /* Create the user node */ mc_user_init(&main_ctx.user_cdev); ret = cdev_add(&main_ctx.user_cdev, main_ctx.user_dev, 1); if (ret) { mc_dev_err(ret, "user cdev_add failed"); return ret; } main_ctx.user_cdev.owner = THIS_MODULE; dev = device_create(main_ctx.class, NULL, main_ctx.user_dev, NULL, MC_USER_DEVNODE); if (IS_ERR(dev)) { ret = PTR_ERR(dev); cdev_del(&main_ctx.user_cdev); mc_dev_err(ret, "user device_create failed"); return ret; } /* Create debugfs structs entry */ debugfs_create_file("structs", 0400, g_ctx.debug_dir, NULL, &debug_structs_ops); return 0; } static inline void device_user_exit(void) { device_destroy(main_ctx.class, main_ctx.user_dev); cdev_del(&main_ctx.user_cdev); } #ifdef MC_PM_RUNTIME static int reboot_notifier(struct notifier_block *nb, unsigned long event, void *dummy) { switch (event) { case SYS_HALT: case SYS_POWER_OFF: main_ctx.did_hibernate = true; break; } return 0; } static int suspend_notifier(struct notifier_block *nb, unsigned long event, void *dummy) { int ret = 0; main_ctx.did_hibernate = false; #ifdef TRUSTONIC_HIBERNATION_SUPPORT switch (event) { case PM_HIBERNATION_PREPARE: /* Try to stop the TEE nicely (ignore failure) */ nq_stop(); break; case PM_POST_HIBERNATION: if (main_ctx.did_hibernate) { /* Really did hibernate */ client_cleanup(); main_ctx.start_ret = TEE_START_NOT_TRIGGERED; return mobicore_start(); } /* Did not hibernate, just restart the TEE */ ret = nq_start(); } #endif return ret; } #endif /* MC_PM_RUNTIME */ static inline int check_version(void) { struct mc_version_info version_info; int ret; /* Must be called before creating the user device node to avoid race */ ret = mcp_get_version(&version_info); if (ret) return ret; /* CMP version is meaningless in this case and is thus not printed */ mc_dev_info("\n" " product_id = %s\n" " version_mci = 0x%08x\n" " version_so = 0x%08x\n" " version_mclf = 0x%08x\n" " version_container = 0x%08x\n" " version_mc_config = 0x%08x\n" " version_tl_api = 0x%08x\n" " version_dr_api = 0x%08x\n" " version_nwd = 0x%08x\n", version_info.product_id, version_info.version_mci, version_info.version_so, version_info.version_mclf, version_info.version_container, version_info.version_mc_config, version_info.version_tl_api, version_info.version_dr_api, version_info.version_nwd); /* Determine which features are supported */ if (version_info.version_mci != MC_VERSION(1, 9)) { ret = -EHOSTDOWN; mc_dev_err(ret, "TEE incompatible with this driver"); return ret; } /* Verify NWd version number */ if (version_info.version_nwd != MC_VERSION( MCDRVMODULEAPI_VERSION_MAJOR, MCDRVMODULEAPI_VERSION_MINOR)) { ret = -EHOSTDOWN; mc_dev_err(ret, "Bad NWd version "); return ret; } return 0; } static int mobicore_start_fe(void) { mutex_lock(&main_ctx.start_mutex); if (main_ctx.start_ret != TEE_START_NOT_TRIGGERED) goto end; /* Must be called before creating the user device node to avoid race */ main_ctx.start_ret = check_version(); if (main_ctx.start_ret) goto end; main_ctx.start_ret = device_user_init(); end: mutex_unlock(&main_ctx.start_mutex); return main_ctx.start_ret; } static int mobicore_start(void) { int ret; mutex_lock(&main_ctx.start_mutex); if (main_ctx.start_ret != TEE_START_NOT_TRIGGERED) goto got_ret; ret = nq_start(); if (ret) { mc_dev_err(ret, "NQ start failed"); goto err_nq; } ret = mcp_start(); if (ret) { mc_dev_err(ret, "MCP start failed"); goto err_mcp; } ret = iwp_start(); if (ret) { mc_dev_err(ret, "IWP start failed"); goto err_iwp; } /* Must be called before creating the user device node to avoid race */ ret = check_version(); if (ret) goto err_version; #ifdef MC_PM_RUNTIME main_ctx.reboot_notifier.notifier_call = reboot_notifier; ret = register_reboot_notifier(&main_ctx.reboot_notifier); if (ret) { mc_dev_err(ret, "reboot notifier registration failed"); goto err_pm_notif; } main_ctx.pm_notifier.notifier_call = suspend_notifier; ret = register_pm_notifier(&main_ctx.pm_notifier); if (ret) { unregister_reboot_notifier(&main_ctx.reboot_notifier); mc_dev_err(ret, "PM notifier register failed"); goto err_pm_notif; } #endif #ifdef MC_TEE_HOTPLUG #if KERNEL_VERSION(4, 6, 0) <= LINUX_VERSION_CODE ret = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "Trustonic", nq_cpu_on, nq_cpu_off); #endif #endif ret = protocol_start(); if (ret) { mc_dev_err(ret, "protocol start failed"); goto err_protocol; } ret = device_user_init(); if (ret) goto err_device_user; main_ctx.start_ret = 0; goto got_ret; err_device_user: protocol_stop(); err_protocol: #ifdef MC_PM_RUNTIME unregister_reboot_notifier(&main_ctx.reboot_notifier); unregister_pm_notifier(&main_ctx.pm_notifier); err_pm_notif: #endif err_version: iwp_stop(); err_iwp: mcp_stop(); err_mcp: nq_stop(); err_nq: main_ctx.start_ret = ret; got_ret: mutex_unlock(&main_ctx.start_mutex); return main_ctx.start_ret; } static void mobicore_stop(void) { device_user_exit(); protocol_stop(); if (!protocol_is_fe()) { #ifdef MC_PM_RUNTIME unregister_reboot_notifier(&main_ctx.reboot_notifier); unregister_pm_notifier(&main_ctx.pm_notifier); #endif iwp_stop(); mcp_stop(); nq_stop(); } } int mc_wait_tee_start(void) { int ret; mutex_lock(&main_ctx.start_mutex); while (main_ctx.start_ret == TEE_START_NOT_TRIGGERED) { mutex_unlock(&main_ctx.start_mutex); ssleep(1); mutex_lock(&main_ctx.start_mutex); } ret = main_ctx.start_ret; mutex_unlock(&main_ctx.start_mutex); return ret; } static inline int device_common_init(void) { int ret; ret = alloc_chrdev_region(&main_ctx.device, 0, 2, "trustonic_tee"); if (ret) { mc_dev_err(ret, "alloc_chrdev_region failed"); return ret; } main_ctx.class = class_create(THIS_MODULE, "trustonic_tee"); if (IS_ERR(main_ctx.class)) { ret = PTR_ERR(main_ctx.class); mc_dev_err(ret, "class_create failed"); unregister_chrdev_region(main_ctx.device, 2); return ret; } return 0; } static inline void device_common_exit(void) { class_destroy(main_ctx.class); unregister_chrdev_region(main_ctx.device, 2); } static inline int device_admin_init(void) { struct device *dev; int ret = 0; /* Create the ADMIN node */ ret = mc_admin_init(&main_ctx.admin_cdev, mobicore_start, mobicore_stop); if (ret) goto err_init; ret = cdev_add(&main_ctx.admin_cdev, main_ctx.device, 1); if (ret) { mc_dev_err(ret, "admin cdev_add failed"); goto err_cdev; } main_ctx.admin_cdev.owner = THIS_MODULE; dev = device_create(main_ctx.class, NULL, main_ctx.device, NULL, MC_ADMIN_DEVNODE); if (IS_ERR(dev)) { ret = PTR_ERR(dev); mc_dev_err(ret, "admin device_create failed"); goto err_device; } return 0; err_device: cdev_del(&main_ctx.admin_cdev); err_cdev: mc_admin_exit(); err_init: return ret; } static inline void device_admin_exit(void) { device_destroy(main_ctx.class, main_ctx.device); cdev_del(&main_ctx.admin_cdev); mc_admin_exit(); } static int mobicore_probe(struct platform_device *pdev) { int ret = 0; if (pdev) g_ctx.mcd->of_node = pdev->dev.of_node; #ifdef MOBICORE_COMPONENT_BUILD_TAG mc_dev_info("MobiCore %s", MOBICORE_COMPONENT_BUILD_TAG); #endif ret = of_property_read_u32(g_ctx.mcd->of_node, "trustonic,real-drv", &g_ctx.real_drv); if (ret || !g_ctx.real_drv) { mc_dev_info("MobiCore dummy driver"); return 0; } /* Hardware does not support ARM TrustZone -> Cannot continue! */ if (!protocol_is_fe() && !has_security_extensions()) { ret = -ENODEV; mc_dev_err(ret, "Hardware doesn't support ARM TrustZone!"); return ret; } /* Running in secure mode -> Cannot load the driver! */ if (is_secure_mode()) { ret = -ENODEV; mc_dev_err(ret, "Running in secure MODE!"); return ret; } /* Make sure we can create debugfs entries */ g_ctx.debug_dir = debugfs_create_dir("trustonic_tee", NULL); /* Initialize debug counters */ atomic_set(&g_ctx.c_clients, 0); atomic_set(&g_ctx.c_cwsms, 0); atomic_set(&g_ctx.c_sessions, 0); atomic_set(&g_ctx.c_wsms, 0); atomic_set(&g_ctx.c_mmus, 0); atomic_set(&g_ctx.c_maps, 0); atomic_set(&g_ctx.c_slots, 0); atomic_set(&g_ctx.c_vm_fes, 0); atomic_set(&g_ctx.c_vm_maps, 0); main_ctx.start_ret = TEE_START_NOT_TRIGGERED; mutex_init(&main_ctx.start_mutex); mutex_init(&main_ctx.struct_counters_buf_mutex); /* Create debugfs info entries */ debugfs_create_file("structs_counters", 0400, g_ctx.debug_dir, NULL, &debug_struct_counters_ops); /* Initialize common API layer */ client_init(); /* Initialize plenty of nice features */ ret = nq_init(); if (ret) { mc_dev_err(ret, "NQ init failed"); goto fail_nq_init; } ret = mcp_init(); if (ret) { mc_dev_err(ret, "MCP init failed"); goto err_mcp; } ret = iwp_init(); if (ret) { mc_dev_err(ret, "IWP init failed"); goto err_iwp; } ret = device_common_init(); if (ret) goto err_common; if (!protocol_is_fe()) { /* Admin dev is for the daemon to communicate with the driver */ ret = device_admin_init(); if (ret) goto err_admin; #ifndef MC_DELAYED_TEE_START ret = mobicore_start(); if (ret) goto err_start; #endif } ret = protocol_init(); if (ret) { mc_dev_err(ret, "protocol init failed"); goto err_protocol; } return 0; err_protocol: #ifndef MC_DELAYED_TEE_START err_start: #endif if (!protocol_is_fe()) device_admin_exit(); err_admin: device_common_exit(); err_common: iwp_exit(); err_iwp: mcp_exit(); err_mcp: nq_exit(); fail_nq_init: debugfs_remove_recursive(g_ctx.debug_dir); return ret; } static int mobicore_probe_not_of(void) { return mobicore_probe(NULL); } static const struct of_device_id of_match_table[] = { { .compatible = MC_DEVICE_PROPNAME }, { } }; static struct platform_driver mc_plat_driver = { .probe = mobicore_probe, .driver = { .name = "mcd", .owner = THIS_MODULE, .of_match_table = of_match_table, } }; static int __init mobicore_init(void) { int ret; dev_set_name(g_ctx.mcd, "TEE"); /* * Do not remove or change the following trace. * The string "MobiCore" is used to detect if the TEE is in of the image */ mc_dev_info("MobiCore mcDrvModuleApi version is %d.%d", MCDRVMODULEAPI_VERSION_MAJOR, MCDRVMODULEAPI_VERSION_MINOR); /* In a Xen DomU, just register the front-end */ ret = protocol_early_init(mobicore_probe_not_of, mobicore_start_fe); if (ret) { if (ret == 1) { mc_dev_devel("delayed init reported"); return 0; } return ret; } main_ctx.use_platform_driver = of_find_compatible_node(NULL, NULL, MC_DEVICE_PROPNAME); if (main_ctx.use_platform_driver) return platform_driver_register(&mc_plat_driver); return mobicore_probe_not_of(); } static void __exit mobicore_exit(void) { if (main_ctx.use_platform_driver) platform_driver_unregister(&mc_plat_driver); if (!protocol_is_fe()) device_admin_exit(); device_common_exit(); protocol_exit(); iwp_exit(); mcp_exit(); nq_exit(); debugfs_remove_recursive(g_ctx.debug_dir); } module_init(mobicore_init); module_exit(mobicore_exit); MODULE_AUTHOR("Trustonic Limited"); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("MobiCore driver");