blob: cfa1518b9f047273914def7a012a34597a0e7264 [file] [log] [blame]
// SPDX-License-Identifier: (GPL-2.0+ OR MIT)
/*
* Copyright (c) 2019 Amlogic, Inc. All rights reserved.
*/
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/dma-mapping.h>
#include <asm/cacheflush.h>
#include <linux/arm-smccc.h>
#include "dolby_fw.h"
#ifdef CONFIG_AMLOGIC_SEC
#include <linux/amlogic/secmon.h>
#endif
#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"
#undef pr_fmt
#define pr_fmt(fmt) "dolbyfw: " fmt
static int major_id;
static struct class *class_dolby_fw;
static struct device *dev_dolby_fw;
static struct device *device;
static int mem_size;
void __iomem *sharemem_in_base;
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
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 != 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->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, 0);
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, 0);
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;
}
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, 0);
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;
}
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;
}
static 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 divece", DOLBY_FW_DEVICE_NAME);
ret = -ENODEV;
goto err;
}
major_id = ret;
class_dolby_fw = class_create(THIS_MODULE,
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 int 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);
return 0;
}
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,
},
};
static 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 */
static void __exit dolby_fw_exit(void)
{
platform_driver_unregister(&dolby_fw_driver);
}
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Amlogic DOLBY FW Interface driver");
module_init(dolby_fw_init);
module_exit(dolby_fw_exit);