/* * drivers/video/tegra/host/tsec/tsec.c * * Tegra TSEC Module Support * * Copyright (c) 2012-2013, NVIDIA CORPORATION. All rights reserved. * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope 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. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include /* for kzalloc */ #include #include #include #include #include /* for parsing ucode image wrt endianness */ #include /* for udelay */ #include #include #include #include #include #include #include "dev.h" #include "tsec.h" #include "hw_tsec.h" #include "bus_client.h" #include "nvhost_acm.h" #include "chip_support.h" #include "nvhost_memmgr.h" #include "nvhost_intr.h" #include "t114/t114.h" #include "t124/t124.h" #define TSEC_IDLE_TIMEOUT_DEFAULT 10000 /* 10 milliseconds */ #define TSEC_IDLE_CHECK_PERIOD 10 /* 10 usec */ #define TSEC_KEY_LENGTH 16 #define TSEC_RESERVE 256 #define TSEC_KEY_OFFSET (TSEC_RESERVE - TSEC_KEY_LENGTH) #define TSEC_OS_START_OFFSET 256 #define get_tsec(ndev) ((struct tsec *)(ndev)->dev.platform_data) #define set_tsec(ndev, f) ((ndev)->dev.platform_data = f) /* The key value in ascii hex */ static u8 otf_key[TSEC_KEY_LENGTH]; /* caller is responsible for freeing */ static char *tsec_get_fw_name(struct platform_device *dev) { char *fw_name; u8 maj, min; struct nvhost_device_data *pdata = platform_get_drvdata(dev); /* note size here is a little over...*/ fw_name = kzalloc(32, GFP_KERNEL); if (!fw_name) return NULL; decode_tsec_ver(pdata->version, &maj, &min); if (maj == 1) { /* there are no minor versions so far for maj==1 */ sprintf(fw_name, "nvhost_tsec.fw"); } else { kfree(fw_name); return NULL; } dev_dbg(&dev->dev, "fw name:%s\n", fw_name); return fw_name; } static int tsec_dma_wait_idle(struct platform_device *dev, u32 *timeout) { if (!*timeout) *timeout = TSEC_IDLE_TIMEOUT_DEFAULT; do { u32 check = min_t(u32, TSEC_IDLE_CHECK_PERIOD, *timeout); u32 dmatrfcmd = nvhost_device_readl(dev, tsec_dmatrfcmd_r()); u32 idle_v = tsec_dmatrfcmd_idle_v(dmatrfcmd); if (tsec_dmatrfcmd_idle_true_v() == idle_v) return 0; udelay(TSEC_IDLE_CHECK_PERIOD); *timeout -= check; } while (*timeout); dev_err(&dev->dev, "dma idle timeout"); return -1; } static int tsec_dma_pa_to_internal_256b(struct platform_device *dev, u32 offset, u32 internal_offset, bool imem) { u32 cmd = tsec_dmatrfcmd_size_256b_f(); u32 pa_offset = tsec_dmatrffboffs_offs_f(offset); u32 i_offset = tsec_dmatrfmoffs_offs_f(internal_offset); u32 timeout = 0; /* default*/ if (imem) cmd |= tsec_dmatrfcmd_imem_true_f(); nvhost_device_writel(dev, tsec_dmatrfmoffs_r(), i_offset); nvhost_device_writel(dev, tsec_dmatrffboffs_r(), pa_offset); nvhost_device_writel(dev, tsec_dmatrfcmd_r(), cmd); return tsec_dma_wait_idle(dev, &timeout); } static int tsec_wait_idle(struct platform_device *dev, u32 *timeout) { if (!*timeout) *timeout = TSEC_IDLE_TIMEOUT_DEFAULT; do { u32 check = min_t(u32, TSEC_IDLE_CHECK_PERIOD, *timeout); u32 w = nvhost_device_readl(dev, tsec_idlestate_r()); if (!w) return 0; udelay(TSEC_IDLE_CHECK_PERIOD); *timeout -= check; } while (*timeout); return -1; } int tsec_boot(struct platform_device *dev) { u32 timeout; u32 offset; int err = 0; struct tsec *m = get_tsec(dev); if (!m || !m->valid) return -ENOMEDIUM; if (m->is_booted) return 0; nvhost_device_writel(dev, tsec_dmactl_r(), 0); nvhost_device_writel(dev, tsec_dmatrfbase_r(), (m->dma_addr + m->os.bin_data_offset) >> 8); for (offset = 0; offset < m->os.data_size; offset += 256) tsec_dma_pa_to_internal_256b(dev, m->os.data_offset + offset, offset, false); tsec_dma_pa_to_internal_256b(dev, m->os.code_offset+TSEC_OS_START_OFFSET, TSEC_OS_START_OFFSET, true); /* boot tsec */ nvhost_device_writel(dev, tsec_bootvec_r(), tsec_bootvec_vec_f(TSEC_OS_START_OFFSET)); nvhost_device_writel(dev, tsec_cpuctl_r(), tsec_cpuctl_startcpu_true_f()); timeout = 0; /* default */ err = tsec_wait_idle(dev, &timeout); if (err != 0) { dev_err(&dev->dev, "boot failed due to timeout"); return err; } /* setup tsec interrupts and enable interface */ nvhost_device_writel(dev, tsec_irqmset_r(), (tsec_irqmset_ext_f(0xff) | tsec_irqmset_swgen1_set_f() | tsec_irqmset_swgen0_set_f() | tsec_irqmset_exterr_set_f() | tsec_irqmset_halt_set_f() | tsec_irqmset_wdtmr_set_f())); nvhost_device_writel(dev, tsec_itfen_r(), (tsec_itfen_mthden_enable_f() | tsec_itfen_ctxen_enable_f())); m->is_booted = true; return err; } static int tsec_setup_ucode_image(struct platform_device *dev, u32 *ucode_ptr, const struct firmware *ucode_fw) { struct tsec *m = get_tsec(dev); /* image data is little endian. */ struct tsec_ucode_v1 ucode; int w; u32 reserved_offset; u32 tsec_key_offset; /* copy the whole thing taking into account endianness */ for (w = 0; w < ucode_fw->size / sizeof(u32); w++) ucode_ptr[w] = le32_to_cpu(((u32 *)ucode_fw->data)[w]); ucode.bin_header = (struct tsec_ucode_bin_header_v1 *)ucode_ptr; /* endian problems would show up right here */ if (ucode.bin_header->bin_magic != 0x10de) { dev_err(&dev->dev, "failed to get firmware magic"); return -EINVAL; } if (ucode.bin_header->bin_ver != 1) { dev_err(&dev->dev, "unsupported firmware version"); return -ENOENT; } /* shouldn't be bigger than what firmware thinks */ if (ucode.bin_header->bin_size > ucode_fw->size) { dev_err(&dev->dev, "ucode image size inconsistency"); return -EINVAL; } dev_dbg(&dev->dev, "ucode bin header: magic:0x%x ver:%d size:%d\n", ucode.bin_header->bin_magic, ucode.bin_header->bin_ver, ucode.bin_header->bin_size); dev_dbg(&dev->dev, "ucode bin header: os bin (header,data) offset size: 0x%x, 0x%x %d\n", ucode.bin_header->os_bin_header_offset, ucode.bin_header->os_bin_data_offset, ucode.bin_header->os_bin_size); ucode.os_header = (struct tsec_ucode_os_header_v1 *) (((void *)ucode_ptr) + ucode.bin_header->os_bin_header_offset); dev_dbg(&dev->dev, "os ucode header: os code (offset,size): 0x%x, 0x%x\n", ucode.os_header->os_code_offset, ucode.os_header->os_code_size); dev_dbg(&dev->dev, "os ucode header: os data (offset,size): 0x%x, 0x%x\n", ucode.os_header->os_data_offset, ucode.os_header->os_data_size); dev_dbg(&dev->dev, "os ucode header: num apps: %d\n", ucode.os_header->num_apps); /* make space for reserved area - we need 20 bytes, but we move 256 * bytes because firmware needs to be 256 byte aligned */ reserved_offset = ucode.bin_header->os_bin_data_offset; memmove(((void *)ucode_ptr) + reserved_offset + TSEC_RESERVE, ((void *)ucode_ptr) + reserved_offset, ucode.bin_header->os_bin_size); ucode.bin_header->os_bin_data_offset += TSEC_RESERVE; /* clear 256 bytes before ucode os code */ memset(((void *)ucode_ptr) + reserved_offset, 0, TSEC_RESERVE); /* Copy key to be the 16 bytes before the firmware */ tsec_key_offset = reserved_offset + TSEC_KEY_OFFSET; memcpy(((void *)ucode_ptr) + tsec_key_offset, otf_key, TSEC_KEY_LENGTH); m->os.size = ucode.bin_header->os_bin_size; m->os.reserved_offset = reserved_offset; m->os.bin_data_offset = ucode.bin_header->os_bin_data_offset; m->os.code_offset = ucode.os_header->os_code_offset; m->os.data_offset = ucode.os_header->os_data_offset; m->os.data_size = ucode.os_header->os_data_size; return 0; } int tsec_read_ucode(struct platform_device *dev, const char *fw_name) { struct tsec *m = get_tsec(dev); const struct firmware *ucode_fw; int err; DEFINE_DMA_ATTRS(attrs); m->dma_addr = 0; m->mapped = NULL; ucode_fw = nvhost_client_request_firmware(dev, fw_name); if (!ucode_fw) { dev_err(&dev->dev, "failed to get tsec firmware\n"); err = -ENOENT; return err; } m->size = ucode_fw->size; m->mapped = dma_alloc_attrs(&dev->dev, m->size, &m->dma_addr, GFP_KERNEL, &attrs); if (!m->mapped) { dev_err(&dev->dev, "dma memory allocation failed"); err = -ENOMEM; goto clean_up; } err = tsec_setup_ucode_image(dev, m->mapped, ucode_fw); if (err) { dev_err(&dev->dev, "failed to parse firmware image\n"); goto clean_up; } m->valid = true; release_firmware(ucode_fw); return 0; clean_up: if (m->mapped) { dma_free_attrs(&dev->dev, m->size, m->mapped, m->dma_addr, &attrs); m->mapped = NULL; m->dma_addr = 0; } release_firmware(ucode_fw); return err; } int nvhost_tsec_init(struct platform_device *dev) { int err = 0; struct tsec *m; char *fw_name; fw_name = tsec_get_fw_name(dev); if (!fw_name) { dev_err(&dev->dev, "couldn't determine firmware name"); return -EINVAL; } m = kzalloc(sizeof(struct tsec), GFP_KERNEL); if (!m) { dev_err(&dev->dev, "couldn't alloc ucode"); kfree(fw_name); return -ENOMEM; } set_tsec(dev, m); m->is_booted = false; err = tsec_read_ucode(dev, fw_name); kfree(fw_name); fw_name = 0; if (err || !m->valid) { dev_err(&dev->dev, "ucode not valid"); goto clean_up; } nvhost_module_busy(dev); err = tsec_boot(dev); if (err) goto clean_up; nvhost_module_idle(dev); return 0; clean_up: dev_err(&dev->dev, "failed"); return err; } void nvhost_tsec_deinit(struct platform_device *dev) { struct tsec *m = get_tsec(dev); DEFINE_DMA_ATTRS(attrs); if (m->mapped) { dma_free_attrs(&dev->dev, m->size, m->mapped, m->dma_addr, &attrs); m->mapped = NULL; m->dma_addr = 0; } kfree(m); set_tsec(dev, NULL); } int nvhost_tsec_finalize_poweron(struct platform_device *dev) { struct nvhost_device_data *pdata = platform_get_drvdata(dev); tegra_periph_reset_assert(pdata->clk[0]); udelay(10); tegra_periph_reset_deassert(pdata->clk[0]); return tsec_boot(dev); } int nvhost_tsec_prepare_poweroff(struct platform_device *dev) { struct tsec *m = get_tsec(dev); if (m) m->is_booted = false; return 0; } static struct of_device_id tegra_tsec_of_match[] = { #ifdef TEGRA_11X_OR_HIGHER_CONFIG { .compatible = "nvidia,tegra114-tsec", .data = (struct nvhost_device_data *)&t11_tsec_info }, #endif #ifdef TEGRA_12X_OR_HIGHER_CONFIG { .compatible = "nvidia,tegra124-tsec", .data = (struct nvhost_device_data *)&t124_tsec_info }, #endif { }, }; static int tsec_probe(struct platform_device *dev) { int err; struct nvhost_device_data *pdata = NULL; if (dev->dev.of_node) { const struct of_device_id *match; match = of_match_device(tegra_tsec_of_match, &dev->dev); if (match) pdata = (struct nvhost_device_data *)match->data; } else pdata = (struct nvhost_device_data *)dev->dev.platform_data; WARN_ON(!pdata); if (!pdata) { dev_info(&dev->dev, "no platform data\n"); return -ENODATA; } pdata->pdev = dev; mutex_init(&pdata->lock); platform_set_drvdata(dev, pdata); err = nvhost_client_device_get_resources(dev); if (err) return err; nvhost_module_init(dev); #ifdef CONFIG_PM_GENERIC_DOMAINS pdata->pd.name = "tsec"; err = nvhost_module_add_domain(&pdata->pd, dev); if (err) return err; #endif err = nvhost_client_device_init(dev); return err; } static int __exit tsec_remove(struct platform_device *dev) { #ifdef CONFIG_PM_RUNTIME pm_runtime_put(&dev->dev); pm_runtime_disable(&dev->dev); #else nvhost_module_disable_clk(&dev->dev); #endif return 0; } static struct platform_driver tsec_driver = { .probe = tsec_probe, .remove = __exit_p(tsec_remove), .driver = { .owner = THIS_MODULE, .name = "tsec", #ifdef CONFIG_PM .pm = &nvhost_module_pm_ops, #endif #ifdef CONFIG_OF .of_match_table = tegra_tsec_of_match, #endif } }; static int __init tsec_key_setup(char *line) { int i; u8 tmp[] = {0, 0, 0}; pr_debug("tsec otf key: %s\n", line); if (strlen(line) != TSEC_KEY_LENGTH*2) { pr_warn("invalid tsec key: %s\n", line); return 0; } for (i = 0; i < TSEC_KEY_LENGTH; i++) { int err; memcpy(tmp, &line[i*2], 2); err = kstrtou8(tmp, 16, &otf_key[i]); if (err) { pr_warn("cannot read tsec otf key: %d", err); break; } } return 0; } __setup("otf_key=", tsec_key_setup); static int __init tsec_init(void) { return platform_driver_register(&tsec_driver); } static void __exit tsec_exit(void) { platform_driver_unregister(&tsec_driver); } module_init(tsec_init); module_exit(tsec_exit);