| /* |
| * drivers/amlogic/media/common/vpu_security/vpu_security.c |
| * |
| * Copyright (C) 2017 Amlogic, Inc. 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 as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * 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 <linux/version.h> |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/spinlock.h> |
| #include <linux/interrupt.h> |
| #include <linux/fs.h> |
| #include <linux/dma-mapping.h> |
| |
| #include <linux/string.h> |
| #include <linux/io.h> |
| #include <linux/mm.h> |
| #include <linux/err.h> |
| #include <linux/mutex.h> |
| #include <linux/platform_device.h> |
| #include <linux/ctype.h> |
| #include <linux/sched.h> |
| #include <linux/poll.h> |
| #include <linux/clk.h> |
| #include <linux/kthread.h> |
| #include <linux/slab.h> |
| #include <linux/of.h> |
| #include <linux/of_fdt.h> |
| #include <linux/amlogic/iomap.h> |
| #include <linux/io.h> |
| #include <linux/delay.h> |
| #include <linux/arm-smccc.h> |
| #include <linux/amlogic/aml_dmc.h> |
| |
| #include <linux/amlogic/media/registers/regs/viu_regs.h> |
| #include <linux/amlogic/media/vpu_secure/vpu_secure.h> |
| #include <linux/amlogic/media/video_sink/video_signal_notify.h> |
| |
| #include "vpu_security.h" |
| |
| #define DEVICE_NAME "vpu_security" |
| #define CLASS_NAME "vpu_security" |
| |
| static struct vpu_security_device_info vpu_security_info; |
| static struct sec_dev_data_s sec_meson_dev; |
| static u32 secure_cfg; |
| |
| static struct sec_dev_data_s vpu_security_sc2 = { |
| .cpu_type = MESON_CPU_MAJOR_ID_SC2_, |
| }; |
| |
| static const struct of_device_id vpu_security_dt_match[] = { |
| { |
| .compatible = "amlogic, meson-sc2, vpu_security", |
| .data = &vpu_security_sc2, |
| }, |
| {} |
| }; |
| |
| static bool is_meson_sc2_cpu(void) |
| { |
| if (sec_meson_dev.cpu_type == |
| MESON_CPU_MAJOR_ID_SC2_) |
| return true; |
| else |
| return false; |
| } |
| |
| static int check_secure_enable(enum secure_module_e module) |
| { |
| int i, secure_enable = 0; |
| struct vpu_security_device_info *info = &vpu_security_info; |
| struct vpu_secure_ins *ins = NULL; |
| |
| for (i = 0; i < MODULE_NUM; i++) { |
| if (i == module) |
| continue; |
| ins = &info->ins[i]; |
| if (ins->secure_enable) |
| secure_enable = 1; |
| } |
| return secure_enable; |
| } |
| |
| bool get_secure_state(enum module_port_e port) |
| { |
| bool secure_enable = false; |
| |
| switch (port) { |
| case VD1_OUT: |
| if (secure_cfg & VD1_INPUT_SECURE) |
| secure_enable = true; |
| break; |
| case VD2_OUT: |
| if (secure_cfg & VD2_INPUT_SECURE) |
| secure_enable = true; |
| break; |
| case OSD1_VPP_OUT: |
| if ((secure_cfg & OSD1_INPUT_SECURE) || |
| (secure_cfg & OSD2_INPUT_SECURE) || |
| (secure_cfg & OSD3_INPUT_SECURE)) |
| secure_enable = true; |
| break; |
| case OSD2_VPP_OUT: |
| break; |
| case POST_BLEND_OUT: |
| if (secure_cfg) |
| secure_enable = true; |
| break; |
| } |
| return secure_enable; |
| } |
| |
| u32 set_vpu_module_security(struct vpu_secure_ins *ins, |
| enum secure_module_e module, |
| u32 secure_src) |
| { |
| static u32 osd_secure, video_secure; |
| static int value_save = -1; |
| u32 value = 0; |
| int secure_update = 0; |
| struct vd_secure_info_s vd_secure[MAX_SECURE_OUT]; |
| |
| if (is_meson_sc2_cpu()) { |
| if (check_secure_enable(module) || secure_src) |
| secure_src |= VPP_OUTPUT_SECURE; |
| switch (module) { |
| case OSD_MODULE: |
| if ((secure_src & OSD1_INPUT_SECURE) || |
| (secure_src & OSD2_INPUT_SECURE) || |
| (secure_src & OSD3_INPUT_SECURE) || |
| (secure_src & MALI_AFBCD_SECURE)) { |
| /* OSD module secure */ |
| osd_secure = secure_src; |
| value = osd_secure | video_secure; |
| ins->secure_enable = 1; |
| ins->secure_status = value; |
| } else { |
| /* OSD none secure */ |
| osd_secure = secure_src; |
| value = osd_secure | video_secure; |
| ins->secure_enable = 0; |
| ins->secure_status = value; |
| } |
| break; |
| case VIDEO_MODULE: |
| if ((secure_src & DV_INPUT_SECURE) || |
| (secure_src & AFBCD_INPUT_SECURE) || |
| (secure_src & VD2_INPUT_SECURE) || |
| (secure_src & VD1_INPUT_SECURE)) { |
| /* video module secure */ |
| video_secure = secure_src; |
| value = video_secure | osd_secure; |
| ins->secure_enable = 1; |
| ins->secure_status = value; |
| } else { |
| /* video module secure */ |
| video_secure = secure_src; |
| value = video_secure | osd_secure; |
| ins->secure_enable = 0; |
| ins->secure_status = value; |
| } |
| break; |
| case DI_MODULE: |
| break; |
| case VDIN_MODULE: |
| break; |
| } |
| if (module == OSD_MODULE || |
| module == VIDEO_MODULE || |
| module == DI_MODULE) { |
| if ((ins->reg_wr_op) && (value_save != value)) { |
| ins->reg_wr_op(VIU_DATA_SEC, value); |
| secure_update = 1; |
| } |
| value_save = value; |
| } |
| } |
| secure_cfg = (osd_secure | video_secure) & (~VPP_OUTPUT_SECURE); |
| if (secure_update) { |
| int i; |
| |
| for (i = 0; i < MAX_SECURE_OUT; i++) { |
| vd_secure[i].secure_type = i; |
| vd_secure[i].secure_enable = get_secure_state(i); |
| } |
| vd_signal_notifier_call_chain |
| (VIDEO_SECURE_TYPE_CHANGED, |
| &vd_secure[0]); |
| } |
| return value; |
| } |
| |
| int secure_register(enum secure_module_e module, |
| int config_delay, |
| void *reg_op, |
| void *cb) |
| { |
| struct vpu_security_device_info *info = &vpu_security_info; |
| struct vpu_secure_ins *ins = NULL; |
| struct mutex *lock = NULL; |
| |
| if (!info->probed) |
| return -1; |
| if (module >= MODULE_NUM) |
| return -1; |
| ins = &info->ins[module]; |
| if (!ins->registered) { |
| lock = &info->ins[module].secure_lock; |
| mutex_lock(lock); |
| ins->registered = 1; |
| ins->config_delay = config_delay; |
| ins->reg_wr_op = reg_op; |
| ins->secure_cb = cb; |
| mutex_unlock(lock); |
| } |
| return 0; |
| } |
| |
| int secure_unregister(enum secure_module_e module) |
| { |
| struct vpu_security_device_info *info = &vpu_security_info; |
| struct vpu_secure_ins *ins = NULL; |
| struct mutex *lock = NULL; |
| |
| if (!info->probed) |
| return -1; |
| if (module >= MODULE_NUM) |
| return -1; |
| ins = &info->ins[module]; |
| if (ins->registered) { |
| lock = &info->ins[module].secure_lock; |
| mutex_lock(lock); |
| ins->registered = 0; |
| ins->config_delay = 0; |
| ins->reg_wr_op = NULL; |
| ins->secure_cb = NULL; |
| mutex_unlock(lock); |
| } |
| return 0; |
| } |
| |
| int secure_config(enum secure_module_e module, int secure_src) |
| { |
| struct vpu_security_device_info *info = &vpu_security_info; |
| struct vpu_secure_ins *ins = NULL; |
| u32 reg_value = -1; |
| |
| if (module >= MODULE_NUM) |
| return -1; |
| ins = &info->ins[module]; |
| if (ins->registered) |
| reg_value = set_vpu_module_security(ins, module, secure_src); |
| return 0; |
| } |
| |
| static ssize_t vpu_security_info_show(struct class *cla, |
| struct class_attribute *attr, char *buf) |
| { |
| struct vpu_security_device_info *info = &vpu_security_info; |
| |
| return snprintf(buf, PAGE_SIZE, "vpu_security mismatch cnt:%d\n", |
| info->mismatch_cnt); |
| } |
| |
| static struct class_attribute vpu_security_attrs[] = { |
| __ATTR(security_info, 0444, |
| vpu_security_info_show, NULL), |
| }; |
| |
| irqreturn_t vpu_security_isr(int irq, void *dev_id) |
| { |
| struct vpu_security_device_info *info = &vpu_security_info; |
| struct vpu_secure_ins *ins = NULL; |
| int i; |
| |
| info->mismatch_cnt++; |
| for (i = 0; i < MODULE_NUM; i++) { |
| ins = &info->ins[i]; |
| if (ins->registered) { |
| if (ins->secure_cb) |
| ins->secure_cb(ins->secure_status); |
| } |
| } |
| return IRQ_HANDLED; |
| } |
| |
| static int vpu_security_probe(struct platform_device *pdev) |
| { |
| int i, ret = 0, err = 0; |
| int int_vpu_security; |
| struct vpu_security_device_info *info = &vpu_security_info; |
| |
| if (pdev->dev.of_node) { |
| const struct of_device_id *match; |
| struct sec_dev_data_s *sec_meson; |
| struct device_node *of_node = pdev->dev.of_node; |
| |
| match = of_match_node(vpu_security_dt_match, of_node); |
| if (match) { |
| sec_meson = |
| (struct sec_dev_data_s *)match->data; |
| if (sec_meson) |
| memcpy(&sec_meson_dev, sec_meson, |
| sizeof(struct sec_dev_data_s)); |
| else |
| err = 1; |
| |
| } else { |
| err = 2; |
| } |
| } else { |
| err = 3; |
| } |
| if (err) { |
| pr_err("dev %s NOT match, err=%d\n", __func__, err); |
| return -ENODEV; |
| } |
| |
| info->pdev = pdev; |
| int_vpu_security = platform_get_irq_byname(pdev, "vpu_security"); |
| if (request_irq(int_vpu_security, &vpu_security_isr, |
| IRQF_SHARED, "vpu_security", (void *)"vpu_security")) { |
| dev_err(&pdev->dev, "can't request irq for vpu_security\n"); |
| return -ENODEV; |
| } |
| |
| info->clsp = class_create(THIS_MODULE, |
| CLASS_NAME); |
| if (IS_ERR(info->clsp)) { |
| ret = PTR_ERR(info->clsp); |
| pr_err("fail to create class\n"); |
| goto fail_create_class; |
| } |
| for (i = 0; i < ARRAY_SIZE(vpu_security_attrs); i++) { |
| if (class_create_file |
| (info->clsp, |
| &vpu_security_attrs[i]) < 0) { |
| pr_err("fail to class_create_file\n"); |
| goto fail_class_create_file; |
| } |
| } |
| for (i = 0; i < MODULE_NUM; i++) |
| mutex_init(&info->ins[i].secure_lock); |
| |
| info->probed = 1; |
| return 0; |
| fail_class_create_file: |
| for (i = 0; i < ARRAY_SIZE(vpu_security_attrs); i++) |
| class_remove_file( |
| info->clsp, &vpu_security_attrs[i]); |
| class_destroy(info->clsp); |
| info->clsp = NULL; |
| fail_create_class: |
| return ret; |
| } |
| |
| static int vpu_security_remove(struct platform_device *pdev) |
| { |
| int i; |
| struct vpu_security_device_info *info = &vpu_security_info; |
| |
| for (i = 0; i < ARRAY_SIZE(vpu_security_attrs); i++) |
| class_remove_file( |
| info->clsp, &vpu_security_attrs[i]); |
| class_destroy(info->clsp); |
| info->clsp = NULL; |
| info->probed = 0; |
| return 0; |
| } |
| |
| static struct platform_driver vpu_security_driver = { |
| .probe = vpu_security_probe, |
| .remove = vpu_security_remove, |
| .driver = { |
| .name = "amlogic_vpu_security", |
| .of_match_table = vpu_security_dt_match, |
| }, |
| }; |
| |
| static int __init vpu_security_init(void) |
| { |
| int r; |
| |
| r = platform_driver_register(&vpu_security_driver); |
| if (r) { |
| pr_err("Unable to register vpu_security driver\n"); |
| return r; |
| } |
| return 0; |
| } |
| |
| static void __exit vpu_security_exit(void) |
| { |
| platform_driver_unregister(&vpu_security_driver); |
| } |
| |
| subsys_initcall(vpu_security_init); |
| module_exit(vpu_security_exit); |
| |
| MODULE_DESCRIPTION("AMLOGIC VPU SECURITY driver"); |
| MODULE_LICENSE("GPL"); |
| MODULE_AUTHOR("PengCheng.Chen <pengcheng.chen@amlogic.com>"); |