| /* |
| * drivers/amlogic/media/common/firmware/firmware.c |
| * |
| * Copyright (C) 2016 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/kernel.h> |
| #include <linux/module.h> |
| #include <linux/types.h> |
| #include <linux/fs.h> |
| #include <linux/init.h> |
| #include <linux/device.h> |
| #include <linux/vmalloc.h> |
| #include <linux/mm.h> |
| #include <linux/slab.h> |
| |
| #include <linux/amlogic/media/utils/vformat.h> |
| #include <linux/amlogic/media/registers/cpu_version.h> |
| #include "../../stream_input/amports/amports_priv.h" |
| #include "../../frame_provider/decoder/utils/vdec.h" |
| #include "firmware_priv.h" |
| #include "../chips/chips.h" |
| #include <linux/string.h> |
| #include <linux/amlogic/media/utils/log.h> |
| #include <linux/firmware.h> |
| #include <linux/amlogic/tee.h> |
| //#include <linux/amlogic/major.h> //if kernel is 4.9 then use this one |
| #include <uapi/linux/major.h> |
| #include <linux/cdev.h> |
| #include <linux/crc32.h> |
| #include "../chips/decoder_cpu_ver_info.h" |
| |
| /* major.minor */ |
| #define PACK_VERS "v0.3" |
| |
| #define CLASS_NAME "firmware_codec" |
| #define DEV_NAME "firmware_vdec" |
| #define DIR "video" |
| #define FRIMWARE_SIZE (64 * 1024) /*64k*/ |
| #define BUFF_SIZE (1024 * 1024 * 2) |
| |
| #define FW_LOAD_FORCE (0x1) |
| #define FW_LOAD_TRY (0X2) |
| |
| /*the first 256 bytes are signature data*/ |
| #define SEC_OFFSET (256) |
| |
| #define TRY_PARSE_MAX (256) |
| |
| #define PACK ('P' << 24 | 'A' << 16 | 'C' << 8 | 'K') |
| #define CODE ('C' << 24 | 'O' << 16 | 'D' << 8 | 'E') |
| |
| #ifndef FIRMWARE_MAJOR |
| #define FIRMWARE_MAJOR AMSTREAM_MAJOR |
| #endif |
| |
| static DEFINE_MUTEX(mutex); |
| |
| static struct ucode_file_info_s ucode_info[] = { |
| #include "firmware_cfg.h" |
| }; |
| |
| static const struct file_operations fw_fops = { |
| .owner = THIS_MODULE |
| }; |
| |
| struct fw_mgr_s *g_mgr; |
| struct fw_dev_s *g_dev; |
| struct package_head_s package_head; |
| |
| static u32 debug; |
| static u32 detail; |
| |
| int get_firmware_data(unsigned int format, char *buf) |
| { |
| int data_len, ret = -1; |
| struct fw_mgr_s *mgr = g_mgr; |
| struct fw_info_s *info; |
| |
| pr_info("[%s], the fw (%s) will be loaded...\n", |
| tee_enabled() ? "TEE" : "LOCAL", |
| get_fw_format_name(format)); |
| |
| if (tee_enabled()) |
| return 0; |
| |
| mutex_lock(&mutex); |
| |
| if (list_empty(&mgr->fw_head)) { |
| pr_info("the info list is empty.\n"); |
| goto out; |
| } |
| |
| list_for_each_entry(info, &mgr->fw_head, node) { |
| if (format != info->format) |
| continue; |
| |
| data_len = info->data->head.data_size; |
| memcpy(buf, info->data->data, data_len); |
| ret = data_len; |
| |
| break; |
| } |
| |
| out: |
| mutex_unlock(&mutex); |
| |
| return ret; |
| } |
| EXPORT_SYMBOL(get_firmware_data); |
| |
| int get_data_from_name(const char *name, char *buf) |
| { |
| int data_len, ret = -1; |
| struct fw_mgr_s *mgr = g_mgr; |
| struct fw_info_s *info; |
| char *fw_name = __getname(); |
| int len; |
| |
| if (fw_name == NULL) |
| return -ENOMEM; |
| |
| len = snprintf(fw_name, PATH_MAX, "%s.bin", name); |
| if (len >= PATH_MAX) { |
| __putname(fw_name); |
| return -ENAMETOOLONG; |
| } |
| |
| mutex_lock(&mutex); |
| |
| if (list_empty(&mgr->fw_head)) { |
| pr_info("the info list is empty.\n"); |
| goto out; |
| } |
| |
| list_for_each_entry(info, &mgr->fw_head, node) { |
| if (strcmp(fw_name, info->name)) |
| continue; |
| |
| data_len = info->data->head.data_size; |
| memcpy(buf, info->data->data, data_len); |
| ret = data_len; |
| |
| break; |
| } |
| out: |
| mutex_unlock(&mutex); |
| |
| __putname(fw_name); |
| |
| return ret; |
| } |
| EXPORT_SYMBOL(get_data_from_name); |
| |
| static int fw_probe(char *buf) |
| { |
| int magic = 0; |
| |
| memcpy(&magic, buf, sizeof(int)); |
| return magic; |
| } |
| |
| static int request_firmware_from_sys(const char *file_name, |
| char *buf, int size) |
| { |
| int ret = -1; |
| const struct firmware *fw; |
| int magic, offset = 0; |
| |
| pr_info("Try to load %s ...\n", file_name); |
| |
| ret = request_firmware(&fw, file_name, g_dev->dev); |
| if (ret < 0) { |
| pr_info("Error : %d can't load the %s.\n", ret, file_name); |
| goto err; |
| } |
| |
| if (fw->size > size) { |
| pr_info("Not enough memory size for ucode.\n"); |
| ret = -ENOMEM; |
| goto release; |
| } |
| |
| magic = fw_probe((char *)fw->data); |
| if (magic != PACK && magic != CODE) { |
| if (fw->size < SEC_OFFSET) { |
| pr_info("This is an invalid firmware file.\n"); |
| goto release; |
| } |
| |
| magic = fw_probe((char *)fw->data + SEC_OFFSET); |
| if (magic != PACK) { |
| pr_info("The firmware file is not packet.\n"); |
| goto release; |
| } |
| |
| offset = SEC_OFFSET; |
| } |
| |
| memcpy(buf, (char *)fw->data + offset, fw->size - offset); |
| |
| pr_info("load firmware size : %zd, Name : %s.\n", |
| fw->size, file_name); |
| ret = fw->size; |
| release: |
| release_firmware(fw); |
| err: |
| return ret; |
| } |
| |
| int request_decoder_firmware_on_sys(enum vformat_e format, |
| const char *file_name, char *buf, int size) |
| { |
| int ret; |
| |
| ret = get_data_from_name(file_name, buf); |
| if (ret < 0) |
| pr_info("Get firmware fail.\n"); |
| |
| if (ret > size) { |
| pr_info("Not enough memory.\n"); |
| return -ENOMEM; |
| } |
| |
| return ret; |
| } |
| int get_decoder_firmware_data(enum vformat_e format, |
| const char *file_name, char *buf, int size) |
| { |
| int ret; |
| |
| ret = request_decoder_firmware_on_sys(format, file_name, buf, size); |
| if (ret < 0) |
| pr_info("get_decoder_firmware_data %s for format %d failed!\n", |
| file_name, format); |
| |
| return ret; |
| } |
| EXPORT_SYMBOL(get_decoder_firmware_data); |
| |
| static unsigned long fw_mgr_lock(struct fw_mgr_s *mgr) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&mgr->lock, flags); |
| return flags; |
| } |
| |
| static void fw_mgr_unlock(struct fw_mgr_s *mgr, unsigned long flags) |
| { |
| spin_unlock_irqrestore(&mgr->lock, flags); |
| } |
| |
| static void fw_add_info(struct fw_info_s *info) |
| { |
| unsigned long flags; |
| struct fw_mgr_s *mgr = g_mgr; |
| |
| flags = fw_mgr_lock(mgr); |
| list_add(&info->node, &mgr->fw_head); |
| fw_mgr_unlock(mgr, flags); |
| } |
| |
| static void fw_del_info(struct fw_info_s *info) |
| { |
| unsigned long flags; |
| struct fw_mgr_s *mgr = g_mgr; |
| |
| flags = fw_mgr_lock(mgr); |
| list_del(&info->node); |
| kfree(info); |
| fw_mgr_unlock(mgr, flags); |
| } |
| |
| static void fw_info_walk(void) |
| { |
| struct fw_mgr_s *mgr = g_mgr; |
| struct fw_info_s *info; |
| |
| if (list_empty(&mgr->fw_head)) { |
| pr_info("the info list is empty.\n"); |
| return; |
| } |
| |
| list_for_each_entry(info, &mgr->fw_head, node) { |
| if (IS_ERR_OR_NULL(info->data)) |
| continue; |
| |
| pr_info("name : %s.\n", info->name); |
| pr_info("ver : %s.\n", |
| info->data->head.version); |
| pr_info("crc : 0x%x.\n", |
| info->data->head.checksum); |
| pr_info("size : %d.\n", |
| info->data->head.data_size); |
| pr_info("maker: %s.\n", |
| info->data->head.maker); |
| pr_info("from : %s.\n", info->src_from); |
| pr_info("date : %s.\n", |
| info->data->head.date); |
| if (info->data->head.duplicate) |
| pr_info("NOTE : Dup from %s.\n", |
| info->data->head.dup_from); |
| pr_info("\n"); |
| } |
| } |
| |
| static void fw_files_info_walk(void) |
| { |
| struct fw_mgr_s *mgr = g_mgr; |
| struct fw_files_s *files; |
| |
| if (list_empty(&mgr->files_head)) { |
| pr_info("the file list is empty.\n"); |
| return; |
| } |
| |
| list_for_each_entry(files, &mgr->files_head, node) { |
| pr_info("type : %s.\n", !files->fw_type ? |
| "VIDEO_DECODE" : files->fw_type == 1 ? |
| "VIDEO_ENCODE" : "VIDEO_MISC"); |
| pr_info("from : %s.\n", !files->file_type ? |
| "VIDEO_PACKAGE" : "VIDEO_FW_FILE"); |
| pr_info("path : %s.\n", files->path); |
| pr_info("name : %s.\n\n", files->name); |
| } |
| } |
| |
| static ssize_t info_show(struct class *class, |
| struct class_attribute *attr, char *buf) |
| { |
| char *pbuf = buf; |
| struct fw_mgr_s *mgr = g_mgr; |
| struct fw_info_s *info; |
| unsigned int secs = 0; |
| struct tm tm; |
| char history_change_id[7] = {0}; |
| int i; |
| |
| mutex_lock(&mutex); |
| |
| if (list_empty(&mgr->fw_head)) { |
| pbuf += sprintf(pbuf, "No firmware.\n"); |
| goto out; |
| } |
| |
| /* shows version of driver. */ |
| pr_info("The ucode driver version is %s\n", PACK_VERS); |
| |
| pr_info("The firmware version is %d.%d.%d-g%s\n", |
| (package_head.version >> 16) & 0xff, |
| package_head.version & 0xff, |
| package_head.submit_count, |
| package_head.commit); |
| |
| pr_info("change id history:\n"); |
| for (i = 0; i < 5; i++) { |
| memset(history_change_id, 0, sizeof(history_change_id)); |
| strncpy(history_change_id, &(package_head.history_change_id[i * 6]), 6); |
| pr_info("\t%s\n", history_change_id); |
| |
| } |
| |
| list_for_each_entry(info, &mgr->fw_head, node) { |
| if (IS_ERR_OR_NULL(info->data)) |
| continue; |
| |
| if (detail) { |
| pr_info("%-5s: %s\n", "name", info->name); |
| pr_info("%-5s: %s\n", "ver", |
| info->data->head.version); |
| pr_info("%-5s: 0x%x\n", "sum", |
| info->data->head.checksum); |
| pr_info("%-5s: %d\n", "size", |
| info->data->head.data_size); |
| pr_info("%-5s: %s\n", "maker", |
| info->data->head.maker); |
| pr_info("%-5s: %s\n", "from", |
| info->src_from); |
| pr_info("%-5s: %s\n\n", "date", |
| info->data->head.date); |
| continue; |
| } |
| |
| secs = info->data->head.time |
| - sys_tz.tz_minuteswest * 60; |
| //time_to_tm(secs, 0, &tm);//kernel4.9 |
| time64_to_tm(secs, 0, &tm); |
| |
| pr_info("%s %-16s, %02d:%02d:%02d %d/%d/%ld, %s %-8s, %s %-8s, %s %s\n", |
| "fmt:", info->data->head.format, |
| tm.tm_hour, tm.tm_min, tm.tm_sec, |
| tm.tm_mon + 1, tm.tm_mday, tm.tm_year + 1900, |
| "cmtid:", info->data->head.commit, |
| "chgid:", info->data->head.change_id, |
| "mk:", info->data->head.maker); |
| } |
| out: |
| mutex_unlock(&mutex); |
| |
| return pbuf - buf; |
| } |
| |
| static ssize_t info_store(struct class *cls, |
| struct class_attribute *attr, const char *buf, size_t count) |
| { |
| if (kstrtoint(buf, 0, &detail) < 0) |
| return -EINVAL; |
| |
| return count; |
| } |
| |
| static int fw_info_fill(void) |
| { |
| int ret = 0, i, len; |
| struct fw_mgr_s *mgr = g_mgr; |
| struct fw_files_s *files; |
| int info_size = ARRAY_SIZE(ucode_info); |
| char *path = __getname(); |
| const char *name; |
| |
| if (path == NULL) |
| return -ENOMEM; |
| |
| for (i = 0; i < info_size; i++) { |
| name = ucode_info[i].name; |
| if (IS_ERR_OR_NULL(name)) |
| break; |
| |
| len = snprintf(path, PATH_MAX, "%s/%s", DIR, |
| ucode_info[i].name); |
| if (len >= PATH_MAX) |
| continue; |
| |
| files = kzalloc(sizeof(struct fw_files_s), GFP_KERNEL); |
| if (files == NULL) { |
| __putname(path); |
| return -ENOMEM; |
| } |
| |
| files->file_type = ucode_info[i].file_type; |
| files->fw_type = ucode_info[i].fw_type; |
| strncpy(files->path, path, sizeof(files->path)); |
| files->path[sizeof(files->path) - 1] = '\0'; |
| strncpy(files->name, name, sizeof(files->name)); |
| files->name[sizeof(files->name) - 1] = '\0'; |
| |
| list_add(&files->node, &mgr->files_head); |
| } |
| |
| __putname(path); |
| |
| if (debug) |
| fw_files_info_walk(); |
| |
| return ret; |
| } |
| |
| static int fw_data_check_sum(struct firmware_s *fw) |
| { |
| unsigned int crc; |
| |
| crc = crc32_le(~0U, fw->data, fw->head.data_size); |
| |
| /*pr_info("firmware crc result : 0x%x\n", crc ^ ~0U);*/ |
| |
| return fw->head.checksum != (crc ^ ~0U) ? 0 : 1; |
| } |
| |
| static int fw_data_filter(struct firmware_s *fw, |
| struct fw_info_s *fw_info) |
| { |
| struct fw_mgr_s *mgr = g_mgr; |
| struct fw_info_s *info, *tmp; |
| int cpu = fw_get_cpu(fw->head.cpu); |
| |
| if (mgr->cur_cpu < cpu) { |
| kfree(fw_info); |
| kfree(fw); |
| return -1; |
| } |
| |
| /* the encode fw need to ignoring filtering rules. */ |
| if (fw_info->format == FIRMWARE_MAX) |
| return 0; |
| |
| list_for_each_entry_safe(info, tmp, &mgr->fw_head, node) { |
| if (info->format != fw_info->format) |
| continue; |
| |
| if (IS_ERR_OR_NULL(info->data)) { |
| fw_del_info(info); |
| return 0; |
| } |
| |
| /* high priority of VIDEO_FW_FILE */ |
| if (info->file_type == VIDEO_FW_FILE) { |
| pr_info("the %s need to priority proc.\n",info->name); |
| kfree(fw_info); |
| kfree(fw); |
| return 1; |
| } |
| |
| /* the cpu ver is lower and needs to be filtered */ |
| if (cpu < fw_get_cpu(info->data->head.cpu)) { |
| if (debug) |
| pr_info("keep the newer fw (%s) and ignore the older fw (%s).\n", |
| info->name, fw_info->name); |
| kfree(fw_info); |
| kfree(fw); |
| return 1; |
| } |
| |
| /* removes not match fw from info list */ |
| if (debug) |
| pr_info("drop the old fw (%s) will be load the newer fw (%s).\n", |
| info->name, fw_info->name); |
| kfree(info->data); |
| fw_del_info(info); |
| } |
| |
| return 0; |
| } |
| |
| static int fw_replace_dup_data(char *buf) |
| { |
| int ret = 0; |
| struct fw_mgr_s *mgr = g_mgr; |
| struct package_s *pkg = |
| (struct package_s *) buf; |
| struct package_info_s *pinfo = |
| (struct package_info_s *) pkg->data; |
| struct fw_info_s *info = NULL; |
| char *pdata = pkg->data; |
| int try_cnt = TRY_PARSE_MAX; |
| |
| do { |
| if (!pinfo->head.length) |
| break; |
| list_for_each_entry(info, &mgr->fw_head, node) { |
| struct firmware_s *comp = NULL; |
| struct firmware_s *data = NULL; |
| int len = 0; |
| |
| comp = (struct firmware_s *)pinfo->data; |
| if (comp->head.duplicate) |
| break; |
| |
| if (!info->data->head.duplicate || |
| comp->head.checksum != |
| info->data->head.checksum) |
| continue; |
| |
| len = pinfo->head.length; |
| data = kzalloc(len, GFP_KERNEL); |
| if (data == NULL) { |
| ret = -ENOMEM; |
| goto out; |
| } |
| |
| memcpy(data, pinfo->data, len); |
| |
| /* update header information. */ |
| memcpy(data, info->data, sizeof(*data)); |
| |
| /* if replaced success need to update real size. */ |
| data->head.data_size = comp->head.data_size; |
| |
| kfree(info->data); |
| info->data = data; |
| } |
| pdata += (pinfo->head.length + sizeof(*pinfo)); |
| pinfo = (struct package_info_s *)pdata; |
| } while (try_cnt--); |
| out: |
| return ret; |
| } |
| |
| static int fw_check_pack_version(char *buf) |
| { |
| struct package_s *pack = NULL; |
| int major, minor, major_fw, minor_fw; |
| int ret; |
| |
| pack = (struct package_s *) buf; |
| ret = sscanf(PACK_VERS, "v%x.%x", &major, &minor); |
| if (ret != 2) |
| return -1; |
| |
| package_head = pack->head; |
| major_fw = (pack->head.version >> 16) & 0xff; |
| minor_fw = pack->head.version & 0xff; |
| |
| if (major < major_fw) { |
| pr_info("the pack ver v%d.%d too higher to unsupport.\n", |
| major_fw, minor_fw); |
| return -1; |
| } |
| |
| if (minor < minor_fw) { |
| pr_info("The fw driver version (v%d.%d) is lower than the pkg version (v%d.%d).\n", |
| major, minor, major_fw, minor_fw); |
| pr_info("The driver version is too low that may affect the work please update asap.\n"); |
| } |
| |
| if (debug) { |
| pr_info("The package has %d fws totally.\n", pack->head.total); |
| pr_info("The driver ver is v%d.%d\n", major, minor); |
| pr_info("The firmware ver is v%d.%d.%d\n", major_fw, minor_fw, pack->head.submit_count); |
| } |
| |
| return 0; |
| } |
| |
| static int fw_package_parse(struct fw_files_s *files, |
| char *buf, int size) |
| { |
| int ret = 0; |
| struct package_info_s *pack_info; |
| struct fw_info_s *info; |
| struct firmware_s *data; |
| char *pack_data; |
| int info_len, len; |
| int try_cnt = TRY_PARSE_MAX; |
| char *path = __getname(); |
| |
| if (path == NULL) |
| return -ENOMEM; |
| |
| pack_data = ((struct package_s *)buf)->data; |
| pack_info = (struct package_info_s *)pack_data; |
| info_len = sizeof(struct package_info_s); |
| |
| do { |
| if (!pack_info->head.length) |
| break; |
| |
| len = snprintf(path, PATH_MAX, "%s/%s", DIR, |
| pack_info->head.name); |
| if (len >= PATH_MAX) |
| continue; |
| |
| info = kzalloc(sizeof(struct fw_info_s), GFP_KERNEL); |
| if (info == NULL) { |
| ret = -ENOMEM; |
| goto out; |
| } |
| |
| data = kzalloc(FRIMWARE_SIZE, GFP_KERNEL); |
| if (data == NULL) { |
| kfree(info); |
| ret = -ENOMEM; |
| goto out; |
| } |
| |
| info->file_type = files->file_type; |
| strncpy(info->src_from, files->name, |
| sizeof(info->src_from)); |
| info->src_from[sizeof(info->src_from) - 1] = '\0'; |
| strncpy(info->name, pack_info->head.name, |
| sizeof(info->name)); |
| info->name[sizeof(info->name) - 1] = '\0'; |
| info->format = get_fw_format(pack_info->head.format); |
| |
| len = pack_info->head.length; |
| memcpy(data, pack_info->data, len); |
| |
| pack_data += (pack_info->head.length + info_len); |
| pack_info = (struct package_info_s *)pack_data; |
| |
| if (!data->head.duplicate && |
| !fw_data_check_sum(data)) { |
| pr_info("check sum fail !\n"); |
| kfree(data); |
| kfree(info); |
| goto out; |
| } |
| |
| if (fw_data_filter(data, info)) |
| continue; |
| |
| if (debug) |
| pr_info("adds %s to the fw list.\n", info->name); |
| |
| info->data = data; |
| fw_add_info(info); |
| } while (try_cnt--); |
| |
| /* process the fw of dup attribute. */ |
| ret = fw_replace_dup_data(buf); |
| if (ret) |
| pr_err("replace dup fw failed.\n"); |
| out: |
| __putname(path); |
| |
| return ret; |
| } |
| |
| static int fw_code_parse(struct fw_files_s *files, |
| char *buf, int size) |
| { |
| struct fw_info_s *info; |
| |
| info = kzalloc(sizeof(struct fw_info_s), GFP_KERNEL); |
| if (info == NULL) |
| return -ENOMEM; |
| |
| info->data = kzalloc(FRIMWARE_SIZE, GFP_KERNEL); |
| if (info->data == NULL) { |
| kfree(info); |
| return -ENOMEM; |
| } |
| |
| info->file_type = files->file_type; |
| strncpy(info->src_from, files->name, |
| sizeof(info->src_from)); |
| info->src_from[sizeof(info->src_from) - 1] = '\0'; |
| memcpy(info->data, buf, size); |
| |
| if (!fw_data_check_sum(info->data)) { |
| pr_info("check sum fail !\n"); |
| kfree(info->data); |
| kfree(info); |
| return -1; |
| } |
| |
| if (debug) |
| pr_info("adds %s to the fw list.\n", info->name); |
| |
| fw_add_info(info); |
| |
| return 0; |
| } |
| |
| static int get_firmware_from_sys(const char *path, |
| char *buf, int size) |
| { |
| int len = 0; |
| |
| len = request_firmware_from_sys(path, buf, size); |
| if (len < 0) |
| pr_info("get data from fsys fail.\n"); |
| |
| return len; |
| } |
| |
| static int fw_data_binding(void) |
| { |
| int ret = 0, magic = 0; |
| struct fw_mgr_s *mgr = g_mgr; |
| struct fw_files_s *files, *tmp; |
| char *buf = NULL; |
| int size; |
| |
| if (list_empty(&mgr->files_head)) { |
| pr_info("the file list is empty.\n"); |
| return 0; |
| } |
| |
| buf = vmalloc(BUFF_SIZE); |
| if (IS_ERR_OR_NULL(buf)) |
| return -ENOMEM; |
| |
| memset(buf, 0, BUFF_SIZE); |
| |
| list_for_each_entry_safe(files, tmp, &mgr->files_head, node) { |
| size = get_firmware_from_sys(files->path, buf, BUFF_SIZE); |
| magic = fw_probe(buf); |
| |
| if (files->file_type == VIDEO_PACKAGE && magic == PACK) { |
| if (!fw_check_pack_version(buf)) |
| ret = fw_package_parse(files, buf, size); |
| } else if (files->file_type == VIDEO_FW_FILE && magic == CODE) { |
| ret = fw_code_parse(files, buf, size); |
| } else { |
| list_del(&files->node); |
| kfree(files); |
| pr_info("invaild file type.\n"); |
| } |
| |
| memset(buf, 0, BUFF_SIZE); |
| } |
| |
| if (debug) |
| fw_info_walk(); |
| |
| vfree(buf); |
| |
| return ret; |
| } |
| |
| static int fw_pre_load(void) |
| { |
| if (fw_info_fill() < 0) { |
| pr_info("Get path fail.\n"); |
| return -1; |
| } |
| |
| if (fw_data_binding() < 0) { |
| pr_info("Set data fail.\n"); |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static int fw_mgr_init(void) |
| { |
| g_mgr = kzalloc(sizeof(struct fw_mgr_s), GFP_KERNEL); |
| if (IS_ERR_OR_NULL(g_mgr)) |
| return -ENOMEM; |
| |
| g_mgr->cur_cpu = get_cpu_major_id(); |
| INIT_LIST_HEAD(&g_mgr->files_head); |
| INIT_LIST_HEAD(&g_mgr->fw_head); |
| spin_lock_init(&g_mgr->lock); |
| |
| return 0; |
| } |
| |
| static void fw_ctx_clean(void) |
| { |
| struct fw_mgr_s *mgr = g_mgr; |
| struct fw_files_s *files; |
| struct fw_info_s *info; |
| unsigned long flags; |
| |
| flags = fw_mgr_lock(mgr); |
| while (!list_empty(&mgr->files_head)) { |
| files = list_entry(mgr->files_head.next, |
| struct fw_files_s, node); |
| list_del(&files->node); |
| kfree(files); |
| } |
| |
| while (!list_empty(&mgr->fw_head)) { |
| info = list_entry(mgr->fw_head.next, |
| struct fw_info_s, node); |
| list_del(&info->node); |
| kfree(info->data); |
| kfree(info); |
| } |
| fw_mgr_unlock(mgr, flags); |
| } |
| |
| int video_fw_reload(int mode) |
| { |
| int ret = 0; |
| struct fw_mgr_s *mgr = g_mgr; |
| |
| if (tee_enabled()) |
| return 0; |
| |
| mutex_lock(&mutex); |
| |
| if (mode & FW_LOAD_FORCE) { |
| fw_ctx_clean(); |
| |
| ret = fw_pre_load(); |
| if (ret < 0) |
| pr_err("The fw reload fail.\n"); |
| } else if (mode & FW_LOAD_TRY) { |
| if (!list_empty(&mgr->fw_head)) { |
| pr_info("The fw has been loaded.\n"); |
| goto out; |
| } |
| |
| ret = fw_pre_load(); |
| if (ret < 0) |
| pr_err("The fw try to reload fail.\n"); |
| } |
| out: |
| mutex_unlock(&mutex); |
| |
| return ret; |
| } |
| EXPORT_SYMBOL(video_fw_reload); |
| |
| static ssize_t reload_show(struct class *class, |
| struct class_attribute *attr, char *buf) |
| { |
| char *pbuf = buf; |
| |
| pbuf += sprintf(pbuf, "The fw reload usage.\n"); |
| pbuf += sprintf(pbuf, "> set 1 means that the fw is forced to update\n"); |
| pbuf += sprintf(pbuf, "> set 2 means that the fw is try to reload\n"); |
| |
| return pbuf - buf; |
| } |
| |
| static ssize_t reload_store(struct class *class, |
| struct class_attribute *attr, |
| const char *buf, size_t size) |
| { |
| int ret = -1; |
| unsigned int val; |
| |
| ret = kstrtoint(buf, 0, &val); |
| if (ret != 0) |
| return -EINVAL; |
| |
| ret = video_fw_reload(val); |
| if (ret < 0) |
| pr_err("fw reload fail.\n"); |
| |
| return size; |
| } |
| |
| static ssize_t debug_show(struct class *cls, |
| struct class_attribute *attr, char *buf) |
| { |
| return sprintf(buf, "%x\n", debug); |
| } |
| |
| static ssize_t debug_store(struct class *cls, |
| struct class_attribute *attr, const char *buf, size_t count) |
| { |
| if (kstrtoint(buf, 0, &debug) < 0) |
| return -EINVAL; |
| |
| return count; |
| } |
| |
| #if 0 //kernel4.9 |
| static struct class_attribute fw_class_attrs[] = { |
| __ATTR(info, 0664, info_show, info_store), |
| __ATTR(reload, 0664, reload_show, reload_store), |
| __ATTR(debug, 0664, debug_show, debug_store), |
| __ATTR_NULL |
| }; |
| |
| static struct class fw_class = { |
| .name = CLASS_NAME, |
| .class_attrs = fw_class_attrs, |
| }; |
| #else //below is for kernel 4.19 and 5.4 |
| static CLASS_ATTR_RW(info); |
| static CLASS_ATTR_RW(reload); |
| static CLASS_ATTR_RW(debug); |
| |
| static struct attribute *fw_class_attrs[] = { |
| &class_attr_info.attr, |
| &class_attr_reload.attr, |
| &class_attr_debug.attr, |
| NULL |
| }; |
| |
| ATTRIBUTE_GROUPS(fw_class); |
| |
| static struct class fw_class = { |
| .name = CLASS_NAME, |
| .class_groups = fw_class_groups, |
| }; |
| |
| #endif |
| static int fw_driver_init(void) |
| { |
| int ret = -1; |
| |
| g_dev = kzalloc(sizeof(struct fw_dev_s), GFP_KERNEL); |
| if (IS_ERR_OR_NULL(g_dev)) |
| return -ENOMEM; |
| |
| g_dev->dev_no = MKDEV(FIRMWARE_MAJOR, 100); |
| |
| ret = register_chrdev_region(g_dev->dev_no, 1, DEV_NAME); |
| if (ret < 0) { |
| pr_info("Can't get major number %d.\n", FIRMWARE_MAJOR); |
| goto err; |
| } |
| |
| cdev_init(&g_dev->cdev, &fw_fops); |
| g_dev->cdev.owner = THIS_MODULE; |
| |
| ret = cdev_add(&g_dev->cdev, g_dev->dev_no, 1); |
| if (ret) { |
| pr_info("Error %d adding cdev fail.\n", ret); |
| goto err; |
| } |
| |
| ret = class_register(&fw_class); |
| if (ret < 0) { |
| pr_info("Failed in creating class.\n"); |
| goto err; |
| } |
| |
| g_dev->dev = device_create(&fw_class, NULL, |
| g_dev->dev_no, NULL, DEV_NAME); |
| if (IS_ERR_OR_NULL(g_dev->dev)) { |
| pr_info("Create device failed.\n"); |
| ret = -ENODEV; |
| goto err; |
| } |
| |
| pr_info("Registered firmware driver success.\n"); |
| err: |
| return ret; |
| } |
| |
| static void fw_driver_exit(void) |
| { |
| cdev_del(&g_dev->cdev); |
| device_destroy(&fw_class, g_dev->dev_no); |
| class_unregister(&fw_class); |
| unregister_chrdev_region(g_dev->dev_no, 1); |
| kfree(g_dev); |
| kfree(g_mgr); |
| } |
| |
| static int __init fw_module_init(void) |
| { |
| int ret = -1; |
| |
| ret = fw_driver_init(); |
| if (ret) { |
| pr_info("Error %d firmware driver init fail.\n", ret); |
| goto err; |
| } |
| |
| ret = fw_mgr_init(); |
| if (ret) { |
| pr_info("Error %d firmware mgr init fail.\n", ret); |
| goto err; |
| } |
| |
| ret = fw_pre_load(); |
| if (ret) { |
| pr_info("Error %d firmware pre load fail.\n", ret); |
| goto err; |
| } |
| err: |
| return ret; |
| } |
| |
| static void __exit fw_module_exit(void) |
| { |
| fw_ctx_clean(); |
| fw_driver_exit(); |
| pr_info("Firmware driver cleaned up.\n"); |
| } |
| |
| module_init(fw_module_init); |
| module_exit(fw_module_exit); |
| |
| MODULE_LICENSE("GPL"); |
| MODULE_AUTHOR("Nanxin Qin <nanxin.qin@amlogic.com>"); |