// SPDX-License-Identifier: (GPL-2.0+ OR MIT) /* * Copyright (c) 2019 Amlogic, Inc. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dolby_fw.h" #include #define DOLBY_FW_DEVICE_NAME "dolby_fw" #define DOLBY_FW_DRIVER_NAME "dolby_fw" #define DOLBY_FW_CLASS_NAME "dolby_fw" #define DOLBY_FW_MODULE_NAME "dolby_fw" static int major_id; static struct class *class_dolby_fw; static struct device *dev_dolby_fw; static struct device *device; static int mem_size; static void __iomem *sharemem_in_base; static void __iomem *sharemem_out_base; #define IOCTL_SIGNATURE 0x10 #define IOCTL_DECRYPT 0x20 #define IOCTL_VERIFY 0x30 #define IOCTL_GET_SESSION_ID 0x40 #define IOCTL_VERIFY_LIB_RESP 0x41 #define IOCTL_AUDIO_LICENSE_QUERY 0x50 #define IOCTL_CRITICAL_QUERY 0x60 #define AUDIO_LICENSE_QUERY_MAX_SIZE 0x40 #define DOLBY_FW_CRITICAL_MAX_SIZE 0x400 #define DOLBY_FW_MAX_SIZE (10 * 1024 * 1024) struct dolby_fw_args { u64 src_addr; unsigned int src_len; u64 dest_addr; unsigned int dest_len; u64 arg1; u64 arg2; u64 arg3; }; static int dolby_fw_smc_call(u64 function_id, u64 arg1, u64 arg2, u64 arg3) { struct arm_smccc_res res; arm_smccc_smc(function_id, arg1, arg2, arg3, 0, 0, 0, 0, &res); return res.a0; } static int dolby_fw_open(struct inode *inode, struct file *file) { return 0; } static int dolby_fw_release(struct inode *inode, struct file *file) { return 0; } static int dolby_fw_signature(struct dolby_fw_args *info) { int ret = -1; meson_sm_mutex_lock(); if (info->src_len > mem_size) goto sig_err; ret = copy_from_user(sharemem_in_base, (void *)(uintptr_t)info->src_addr, info->src_len); if (ret != 0) { pr_err("%s:%d: copy signature data fail!\n", __func__, __LINE__); goto sig_err; } ret = dolby_fw_smc_call(DOLBY_FW_SIGNATURE, info->src_len, 0, 0); sig_err: meson_sm_mutex_unlock(); return ret; } static int dolby_fw_decrypt(struct dolby_fw_args *info) { int ret = -1; int i, data_size, size, process_size; if (info->src_len > DOLBY_FW_MAX_SIZE) { pr_err("%s:%d: src_len too large: %u\n", __func__, __LINE__, info->src_len); goto err; } if (info->src_len != info->dest_len) { pr_err("%s:%d: args are error!\n", __func__, __LINE__); goto err; } data_size = info->src_len; meson_sm_mutex_lock(); for (i = 0, size = 0; i <= data_size / mem_size; i++) { if (size + mem_size <= data_size) process_size = mem_size; else process_size = data_size - size; if (process_size > mem_size) goto err; ret = copy_from_user(sharemem_in_base, (void *)(uintptr_t)(info->src_addr + size), process_size); if (ret != 0) { pr_err("%s:%d: decrypt copy data fail!\n", __func__, __LINE__); goto err; } ret = dolby_fw_smc_call(DOLBY_FW_DECRYPT, 0, 0, process_size); if (ret) { pr_err("%s:%d: audio firmware decrypt fail!\n", __func__, __LINE__); goto err; } if (process_size > info->dest_len) goto err; ret = copy_to_user((void *)(uintptr_t)(info->dest_addr + size), sharemem_out_base, process_size); if (ret != 0) { pr_err("%s:%d: decrypt write data fail!\n", __func__, __LINE__); goto err; } size += process_size; } ret = 0; err: meson_sm_mutex_unlock(); return ret; } static int dolby_fw_verify(struct dolby_fw_args *info) { int ret = -1; int i, data_size, size, process_size, cnt; unsigned char pre_shared[32]; if (info->src_len > DOLBY_FW_MAX_SIZE) { pr_err("%s:%d: src_len too large: %u\n", __func__, __LINE__, info->src_len); goto err; } if (info->dest_len < 0x20) { pr_err("%s:%d: verify dest len erro!\n", __func__, __LINE__); goto err; } if (!info->arg2) { pr_err("%s:%d: critical data size error!\n", __func__, __LINE__); goto err; } data_size = info->src_len; cnt = (data_size / mem_size); meson_sm_mutex_lock(); for (i = 0, size = 0; i < cnt; i++) { ret = copy_from_user(sharemem_in_base, (void *)(uintptr_t)(info->src_addr + size), mem_size); if (ret != 0) { pr_err("%s:%d: verify copy data fail!\n", __func__, __LINE__); goto err1; } ret = dolby_fw_smc_call(DOLBY_FW_VERIFY_UPDATE, 0, mem_size, 0); if (ret) { pr_err("%s:%d: audio firmware verify update fail!\n", __func__, __LINE__); goto err1; } size += mem_size; } process_size = data_size - size; if (process_size) { if (process_size > mem_size) goto err2; ret = copy_from_user(sharemem_in_base, (void *)(uintptr_t)(info->src_addr + size), process_size); if (ret != 0) { pr_err("%s:%d: verify copy data fail!\n", __func__, __LINE__); goto err2; } } ret = dolby_fw_smc_call(DOLBY_FW_VERIFY_FINAL, 0, process_size, 0); if (ret) { pr_err("%s:%d: audio firmware verify fail!\n", __func__, __LINE__); goto err2; } memcpy(pre_shared, (void *)sharemem_out_base, 0x20); /*critical data*/ if (info->arg2 > mem_size) goto err2; ret = copy_from_user(sharemem_in_base, (void *)(uintptr_t)(info->arg1), info->arg2); if (ret != 0) { pr_err("%s:%d: critical copy data fail!\n", __func__, __LINE__); goto err2; } ret = dolby_fw_smc_call(DOLBY_FW_VERIFY_CRITICAL, info->arg2, 0, info->arg3); if (ret) { pr_err("%s:%d: verify critical fail!\n", __func__, __LINE__); goto err2; } /* copy share memory output for pre_shared_secret */ ret = copy_to_user((void *)(uintptr_t)(info->dest_addr), (void *)pre_shared, 0x20); if (ret != 0) { pr_err("%s:%d: verify copy data fail!\n", __func__, __LINE__); ret = -1; goto err2; } err2: err1: meson_sm_mutex_unlock(); err: return ret; } static int dolby_fw_get_session_id(struct dolby_fw_args *info) { int ret = -1; meson_sm_mutex_lock(); dolby_fw_smc_call(DOLBY_FW_GET_SESSION_ID, 0, 0, 0); ret = copy_to_user((void *)(uintptr_t)(info->dest_addr), sharemem_out_base, 16 + 4); if (ret != 0) { pr_err("%s:%d: get session id fail!\n", __func__, __LINE__); ret = -1; } meson_sm_mutex_unlock(); return ret; } static int dolby_fw_verify_lib_resp(struct dolby_fw_args *info) { int ret = -1; if (info->src_len < (16 + 32) || info->src_len > 0x1000) { pr_err("verify lib resp input len error!\n"); goto err; } if (info->dest_len < 32) { pr_err("verify lib resp output len error!\n"); goto err; } meson_sm_mutex_lock(); ret = copy_from_user(sharemem_in_base, (void *)(uintptr_t)info->src_addr, info->src_len); if (ret != 0) { pr_err("%s:%d: copy verify lib resp fail!\n", __func__, __LINE__); goto err1; } ret = dolby_fw_smc_call(DOLBY_FW_VERIFY_LIB_RESP, 0, 0, info->arg3); if (ret) { pr_err("%s:%d: verify lib resp fail %d!\n", __func__, __LINE__, ret); ret = -1; goto err1; } ret = copy_to_user((void *)(uintptr_t)(info->dest_addr), sharemem_out_base, 32); if (ret != 0) { pr_err("%s:%d: verify lib resp return fail!\n", __func__, __LINE__); ret = -1; } err1: meson_sm_mutex_unlock(); err: return ret; } static unsigned int dolby_fw_audio_license_query(struct dolby_fw_args *info) { unsigned int ret = 0; unsigned int outlen; if (info->src_len > AUDIO_LICENSE_QUERY_MAX_SIZE) { pr_err("%s:%d: audio license query src length error!\n", __func__, __LINE__); ret = 0; goto err; } meson_sm_mutex_lock(); ret = copy_from_user(sharemem_in_base, (void *)(uintptr_t)info->src_addr, info->src_len); if (ret != 0) { pr_err("%s:%d: copy audio license query fail!\n", __func__, __LINE__); ret = 0; goto err1; } outlen = dolby_fw_smc_call(DOLBY_FW_AUDIO_LICENSE_QUERY, info->src_len, info->dest_len, 0); if (!outlen || info->dest_len < outlen) { pr_err("%s:%d: audio license query fail!\n", __func__, __LINE__); goto err1; } ret = copy_to_user((void *)(uintptr_t)(info->dest_addr), sharemem_out_base, outlen); if (ret != 0) { pr_err("%s:%d: audio license query fail!\n", __func__, __LINE__); ret = 0; goto err1; } ret = outlen; err1: meson_sm_mutex_unlock(); err: return ret; } static int dolby_fw_critical_query(struct dolby_fw_args *info) { int ret = 0; unsigned int size; if (info->src_len != 0) { if (info->src_len > DOLBY_FW_CRITICAL_MAX_SIZE) { pr_err("%s:%d: critical query src length error!\n", __func__, __LINE__); ret = 0; goto err; } } meson_sm_mutex_lock(); if (info->src_len) { ret = copy_from_user(sharemem_in_base, (void *)(uintptr_t)info->src_addr, info->src_len); if (ret != 0) { pr_err("%s:%d: copy critical src buffer fail!\n", __func__, __LINE__); ret = 0; goto err1; } } size = dolby_fw_smc_call(DOLBY_FW_CRITICAL_DATA_QUERY, info->src_len, 0, info->arg3); if (!size) { pr_err("%s:%d: query critical fail!\n", __func__, __LINE__); goto err1; } if (info->dest_len < size || size > DOLBY_FW_CRITICAL_MAX_SIZE) { pr_err("%s:%d: critical size error!\n", __func__, __LINE__); goto err1; } ret = copy_to_user((void *)(uintptr_t)(info->dest_addr), sharemem_out_base, size); if (ret != 0) { pr_err("%s:%d: get critical fail!\n", __func__, __LINE__); ret = 0; goto err1; } ret = size; err1: meson_sm_mutex_unlock(); err: return ret; } static long dolby_fw_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { void __user *argp = (void __user *)arg; struct dolby_fw_args info; int ret; if (!argp) { pr_err("%s:%d,wrong param\n", __func__, __LINE__); return -1; } ret = copy_from_user(&info, (struct dolby_fw_args *)argp, sizeof(struct dolby_fw_args)); if (ret != 0) { pr_err("%s:%d,copy_from_user fail\n", __func__, __LINE__); return ret; } switch (cmd) { case IOCTL_SIGNATURE: ret = dolby_fw_signature(&info); break; case IOCTL_DECRYPT: ret = dolby_fw_decrypt(&info); break; case IOCTL_VERIFY: ret = dolby_fw_verify(&info); break; case IOCTL_GET_SESSION_ID: ret = dolby_fw_get_session_id(&info); break; case IOCTL_VERIFY_LIB_RESP: ret = dolby_fw_verify_lib_resp(&info); break; case IOCTL_AUDIO_LICENSE_QUERY: ret = dolby_fw_audio_license_query(&info); break; case IOCTL_CRITICAL_QUERY: ret = dolby_fw_critical_query(&info); break; default: return -ENOTTY; } return ret; } struct file_operations const dolby_fw_fops = { .owner = THIS_MODULE, .open = dolby_fw_open, .release = dolby_fw_release, .unlocked_ioctl = dolby_fw_ioctl, .compat_ioctl = dolby_fw_ioctl, }; static int dolby_fw_probe(struct platform_device *pdev) { int ret = 0; unsigned int val; int sharemem_in, sharemem_out; device = &pdev->dev; platform_set_drvdata(pdev, NULL); ret = register_chrdev(0, DOLBY_FW_DEVICE_NAME, &dolby_fw_fops); if (ret < 0) { pr_err("Can't register %s device", DOLBY_FW_DEVICE_NAME); ret = -ENODEV; goto err; } major_id = ret; class_dolby_fw = class_create(DOLBY_FW_DEVICE_NAME); if (IS_ERR(class_dolby_fw)) { ret = PTR_ERR(class_dolby_fw); goto err1; } dev_dolby_fw = device_create(class_dolby_fw, NULL, MKDEV(major_id, 0), NULL, DOLBY_FW_DEVICE_NAME); if (IS_ERR(dev_dolby_fw)) { ret = PTR_ERR(dev_dolby_fw); goto err2; } ret = of_property_read_u32(pdev->dev.of_node, "mem_size", &val); if (ret) { dev_err(&pdev->dev, "config mem_size in dts\n"); ret = -1; goto err3; } mem_size = val; sharemem_in = get_secmon_sharemem_in_size(); sharemem_out = get_secmon_sharemem_out_size(); if (mem_size > sharemem_in) mem_size = sharemem_in; if (mem_size > sharemem_out) mem_size = sharemem_out; sharemem_in_base = get_meson_sm_input_base(); sharemem_out_base = get_meson_sm_output_base(); if (!sharemem_in_base || !sharemem_out_base) goto err3; return 0; err3: device_destroy(class_dolby_fw, MKDEV(major_id, 0)); err2: class_destroy(class_dolby_fw); err1: unregister_chrdev(major_id, DOLBY_FW_DEVICE_NAME); err: return ret; } static void dolby_fw_remove(struct platform_device *pdev) { device_destroy(class_dolby_fw, MKDEV(major_id, 0)); class_destroy(class_dolby_fw); unregister_chrdev(major_id, DOLBY_FW_DEVICE_NAME); platform_set_drvdata(pdev, NULL); } static const struct of_device_id amlogic_dolby_fw_dt_match[] = { { .compatible = "amlogic, dolby_fw", }, {}, }; static struct platform_driver dolby_fw_driver = { .probe = dolby_fw_probe, .remove = dolby_fw_remove, .driver = { .name = DOLBY_FW_DEVICE_NAME, .of_match_table = amlogic_dolby_fw_dt_match, .owner = THIS_MODULE, }, }; int __init dolby_fw_init(void) { int ret = -1; ret = platform_driver_register(&dolby_fw_driver); if (ret != 0) { pr_err("failed to register dolby fw driver, error %d\n", ret); return -ENODEV; } return ret; } /* module unload */ void __exit dolby_fw_exit(void) { platform_driver_unregister(&dolby_fw_driver); }