| /* |
| * 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 <linux/firmware.h> |
| #include <linux/fs.h> |
| #include <linux/io.h> |
| #include <linux/mm.h> |
| #include <linux/slab.h> |
| #include <linux/uaccess.h> |
| |
| 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; |
| } |