/* * Copyright (C) 2019, Samsung Electronics Co. Ltd. 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 "ssp_firmware.h" #include "factory/ssp_sensor.h" #include "ssp.h" #include #include #include #include #include #include enum fw_type { FW_TYPE_NONE, FW_TYPE_BIN, FW_TYPE_PUSH, FW_TYPE_SPU, }; #define SUPPORT_SPU_FW #define UPDATE_BIN_PATH "/vendor/firmware/shub.bin" #define UPDATE_BIN_FW_NAME "shub.bin" #ifdef SUPPORT_SPU_FW #define SPU_FW_FILE "/spu/sensorhub/shub_spu.bin" #define FW_VER_LEN 8 extern long spu_firmware_signature_verify(const char *fw_name, const u8 *fw_data, const long fw_size); static int request_spu_firmware(struct ssp_data *data, u8 **fw_buf) { int ret = 0; size_t file_size = 0, remaining; int offset = 0; unsigned int read_size = PAGE_SIZE * 10; long fw_size = 0; struct file *filp = NULL; u8 *file_buf; mm_segment_t old_fs = get_fs(); set_fs(KERNEL_DS); ssp_infof(""); filp = filp_open(SPU_FW_FILE, O_RDONLY, 0); if (IS_ERR(filp)) { ssp_infof("filp_open failed %d", PTR_ERR(filp)); set_fs(old_fs); return 0; } file_size = filp->f_path.dentry->d_inode->i_size; if (file_size <= 0) { filp_close(filp, NULL); set_fs(old_fs); return 0; } file_buf = kvzalloc(file_size, GFP_KERNEL); if (file_buf == NULL) { ssp_errf("file buf kvzalloc error"); return 0; } remaining = file_size; while (remaining > 0) { int ret = 0; if (read_size > remaining) { read_size = remaining; } ret = (unsigned int)vfs_read(filp, (char __user *)(file_buf + offset), read_size, &filp->f_pos); if (ret != read_size) { ssp_errf("file read fail %d", ret); break; } offset += read_size; remaining -= read_size; filp->f_pos = offset; } filp_close(filp, NULL); set_fs(old_fs); if (ret < 0) { ssp_errf("file read fail %d", ret); kfree(file_buf); return 0; } // check signing fw_size = spu_firmware_signature_verify("SENSORHUB", file_buf, file_size); if (fw_size < 0) { ssp_errf("signature verification failed %d", fw_size); fw_size = 0; } else { u32 fw_version = 0; char str_ver[9] = ""; ssp_infof("signature verification success %d", fw_size); if (fw_size < FW_VER_LEN) { ssp_errf("fw size is wrong %d", fw_size); kfree(file_buf); return 0; } memcpy(str_ver, file_buf + fw_size - FW_VER_LEN, 8); ret = kstrtou32(str_ver, 10, &fw_version); ssp_infof("urgent fw_version %d kernel ver %u", fw_version, get_module_rev(data)); if (fw_version > get_module_rev(data)) { fw_size -= FW_VER_LEN; ssp_infof("use spu fw size %d", fw_size); *fw_buf = kvzalloc(fw_size, GFP_KERNEL); if (*fw_buf == NULL) { ssp_errf("fw buf kvzalloc error"); kfree(file_buf); return 0; } memcpy(*fw_buf, file_buf, fw_size); } else fw_size = 0; } kfree(file_buf); return (int)fw_size; } static void release_spu_firmware(struct ssp_data *data, u8 *fw_buf) { kfree(fw_buf); } #endif #ifdef CONFIG_SSP_ENG_DEBUG static bool is_exist_force_update_fw(void) { bool is_exist = false; mm_segment_t old_fs; struct file *fw_file = NULL; ssp_infof("check push bin file"); old_fs = get_fs(); set_fs(KERNEL_DS); fw_file = filp_open(UPDATE_BIN_PATH, O_RDONLY, 0660); if (IS_ERR(fw_file)) { ssp_errf("error %d", PTR_ERR(fw_file)); is_exist = false; } else { is_exist = true; } set_fs(old_fs); return is_exist; } #endif int download_sensorhub_firmware(struct ssp_data *data, struct device *dev, void *addr) { int ret = 0; int fw_size; char *fw_buf = NULL; const struct firmware *entry = NULL; if (addr == NULL) return -EINVAL; #ifdef CONFIG_SSP_ENG_DEBUG if (is_exist_force_update_fw()) { ret = request_firmware(&entry, UPDATE_BIN_FW_NAME, dev); if (!ret) { data->fw_type = FW_TYPE_PUSH; fw_size = (int)entry->size; fw_buf = (char *)entry->data; } } else #endif { #ifdef SUPPORT_SPU_FW fw_size = request_spu_firmware(data, (u8 **)&fw_buf); if (fw_size > 0) { ssp_infof("dowload spu firmware"); data->fw_type = FW_TYPE_SPU; } else #endif { ssp_infof("download %s", data->fw_name); ret = request_firmware(&entry, data->fw_name, dev); if (ret) { ssp_errf("request_firmware failed %d", ret); data->fw_type = FW_TYPE_NONE; release_firmware(entry); return -EINVAL; } data->fw_type = FW_TYPE_BIN; fw_size = (int)entry->size; fw_buf = (char *)entry->data; } } ssp_infof("fw type %d bin(size:%d) on %lx", data->fw_type, (int)fw_size, (unsigned long)addr); memcpy(addr, fw_buf, fw_size); if (entry) release_firmware(entry); #ifdef SUPPORT_SPU_FW if (data->fw_type == FW_TYPE_SPU) release_spu_firmware(data, fw_buf); #endif return 0; }