sdk-hwV1.3/lichee/linux-4.9/drivers/remoteproc/sunxi_rproc_firmware.c

662 lines
14 KiB
C
Raw Normal View History

2024-05-07 10:09:20 +00:00
/* SPDX-License-Identifier: GPL-2.0 */
/*
* sunxi's firmware loader driver
* it implement search elf file from boot_package.
*
* Copyright (C) 2022 Allwinnertech - All Rights Reserved
*
* Author: lijiajian <lijiajian@allwinnertech.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/device.h>
#include <linux/firmware.h>
#include <linux/uaccess.h>
#include <linux/list.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/mtd/aw-spinand.h>
#include <linux/string.h>
#include <linux/memblock.h>
#include <linux/elf.h>
#include <asm/cacheflush.h>
#include "sunxi_remoteproc_internal.h"
#define DELAY_TIME msecs_to_jiffies(500)
#define FW_TRY_CNT 5
#define FIRMWARE_MAGIC 0x89119800
#ifdef FW_SAVE_IN_MEM
struct fw_mem_info {
const char *name;
void *addr;
phys_addr_t pa;
int len;
struct list_head list;
/* use to wait elf decompress complete */
wait_queue_head_t wq;
struct timer_list timer;
};
static LIST_HEAD(g_memory_fw_list);
static DEFINE_MUTEX(g_list_lock);
#endif
struct load_storage {
const char *name;
const char *path;
const int head_off;
const int offset;
const u32 flag;
};
#define DUMP_DATA_LEN 128
#define SUNXI_FW_FLAG_EMMC 2
#define SUNXI_FW_FLAG_NOR 3
#define SUNXI_FW_FLAG_NAND 5
static const struct load_storage firmware_storages[] = {
{ "nor", "/dev/mtd0", 128 * 512, 128 * 512, SUNXI_FW_FLAG_NOR},
{ "nand", "/dev/mtd1", 0, 0, SUNXI_FW_FLAG_NAND},
{ "emmc", "/dev/mmcblk0", 32800 * 512, 32800 * 512, SUNXI_FW_FLAG_EMMC},
};
typedef struct firmware_head_info {
u8 name[16];
u32 magic; /* must is 0x89119800 */
u32 reserved1[3];
u32 items_nr;
u32 reserved2[6];
u32 end;
} firmware_head_info_t;
typedef struct firmware_item_info {
char name[64];
u32 data_offset;
u32 data_len;
u32 reserved[74];
} firmware_item_info_t;
struct sunxi_firmware_work {
struct delayed_work work;
const char *name;
struct device *device;
void *context;
void (*cont)(const struct firmware *fw, void *context);
};
#ifdef DEBUG
static int sunxi_firmware_dump_data(void *buf, int len)
{
int i, j, line;
u8 *p = buf;
pr_info("firmware:\n");
for (i = 0; i < len; i += 16) {
j = 0;
if (len > (i + 16))
line = 16;
else
line = len - i;
for (j = 0; j < line; j++)
pr_cont("%02x ", p[i + j]);
pr_cont("\t\t|");
for (j = 0; j < line; j++) {
if (p[i + j] > 32 && p[i + j] < 126)
pr_cont("%c", p[i + j]);
else
pr_cont(".");
}
pr_cont("|\n");
}
pr_cont("\n");
return 0;
}
#endif
static int sunxi_firmware_read(const char *path, void *buf, u32 len, loff_t *pos, u32 flag)
{
int ret;
struct file *fp;
mm_segment_t fs;
fp = filp_open(path, O_RDONLY, 0444);
if (IS_ERR(fp))
return PTR_ERR(fp);
fs = get_fs();
set_fs(KERNEL_DS);
ret = vfs_read(fp, buf, len, pos);
filp_close(fp, NULL);
set_fs(fs);
return ret;
}
static int sunxi_get_storage_type(void)
{
int ret, storage_type;
struct file *fp;
mm_segment_t fs;
char buf[1024];
loff_t pos = 0;
char *p;
static const char *path = "/proc/cmdline";
fp = filp_open(path, O_RDONLY, 0444);
if (IS_ERR(fp))
return PTR_ERR(fp);
fs = get_fs();
set_fs(KERNEL_DS);
ret = vfs_read(fp, buf, sizeof(buf) - 1, &pos);
if (ret < 0)
return ret;
buf[ret] = '\0';
p = strstr(buf, "boot_type=");
if (!p) {
pr_err("Can't find boot_type!\n");
return -EFAULT;
}
/* boot_type range in [0,6]*/
storage_type = ((int)p[10]) - 0x30;
if (storage_type < 0 || storage_type > 6) {
pr_err("Invalid boot_type!\n");
return -EFAULT;
}
pr_debug("storage_type = %d\n", storage_type);
filp_close(fp, NULL);
set_fs(fs);
return storage_type;
}
static int sunxi_firmware_parser_head(struct device *dev, const char *name, void *buf,
u32 *addr, u32 *len)
{
u8 *pbuf = buf;
int i;
struct firmware_head_info *head;
struct firmware_item_info *item;
head = (struct firmware_head_info *)pbuf;
item = (struct firmware_item_info *)(pbuf + sizeof(*head));
#ifdef DEBUG
sunxi_firmware_dump_data(pbuf, DUMP_DATA_LEN);
#endif
for (i = 0; i < head->items_nr; i++, item++) {
dev_dbg(dev, "item:%s addr=0x%08x,len=0x%x\n", item->name,
item->data_offset, item->data_len);
if (strncmp(item->name, name, strlen(item->name)) == 0) {
*addr = item->data_offset;
*len = item->data_len;
return 0;
}
}
return -ENOENT;
}
static int sunxi_find_firmware_storage(void)
{
struct firmware_head_info *head;
int i, len, ret;
loff_t pos;
const char *path;
u32 flag;
len = sizeof(*head);
head = kmalloc(len, GFP_KERNEL);
if (!head)
return -ENOMEM;
ret = sunxi_get_storage_type();
for (i = 0; i < ARRAY_SIZE(firmware_storages); i++) {
path = firmware_storages[i].path;
pos = firmware_storages[i].head_off;
flag = firmware_storages[i].flag;
if (flag != ret)
continue;
pr_debug("try to open %s\n", path);
ret = sunxi_firmware_read(path, head, len, &pos, flag);
if (ret < 0)
pr_err("open %s failed,ret=%d\n", path, ret);
if (ret != len)
continue;
if (head->magic == FIRMWARE_MAGIC) {
kfree(head);
return i;
}
}
kfree(head);
return -ENODEV;
}
static int sunxi_firmware_get_info(struct device *dev, int index,
const char *name, u32 *img_addr, u32 *img_len)
{
int ret, len;
u8 *head;
loff_t pos;
u32 offset, flag;
const char *path;
offset = firmware_storages[index].offset;
path = firmware_storages[index].path;
flag = firmware_storages[index].flag;
len = 4 * 1024;
head = vmalloc(len);
if (!head)
return -ENOMEM;
pos = firmware_storages[index].head_off;
ret = sunxi_firmware_read(path, head, len, &pos, flag);
if (ret != len) {
dev_err(dev, "%s not exits\n", path);
ret = -EINVAL;
goto out;
}
ret = sunxi_firmware_parser_head(dev, name, head, img_addr, img_len);
if (ret < 0) {
dev_err(dev, "failed to parser head (%s) ret=%d\n", name, ret);
ret = -EFAULT;
goto out;
}
*img_addr += offset;
dev_dbg(dev, "firmware: addr:0x%08x len:0x%x\n", *img_addr, *img_len);
out:
vfree(head);
return ret;
}
static int sunxi_firmware_get_data(struct device *dev, int index,
u32 addr, u32 len, struct firmware **fw)
{
int ret;
u8 *img;
const char *path;
struct firmware *fw_p = NULL;
loff_t pos;
u32 flag;
path = firmware_storages[index].path;
flag = firmware_storages[index].flag;
img = vmalloc(len);
if (!img) {
dev_err(dev, "failed to alloc Firmware\n");
ret = -ENOMEM;
goto out;
}
pos = addr;
/* read data from storage */
ret = sunxi_firmware_read(path, img, len, &pos, flag);
if (ret < 0) {
dev_err(dev, "failed to read firmware data\n");
ret = -EFAULT;
goto out;
}
fw_p = kmalloc(sizeof(struct firmware), GFP_KERNEL);
if (!fw_p) {
dev_err(dev, "failed to alloc Firmware\n");
ret = -ENOMEM;
goto out;
}
fw_p->size = len;
fw_p->priv = NULL;
fw_p->pages = NULL;
fw_p->data = img;
*fw = fw_p;
ret = 0;
#ifdef DEBUG
sunxi_firmware_dump_data(img, 128);
#endif
out:
return ret;
}
#ifdef FW_SAVE_IN_MEM
int sunxi_register_memory_fw(const char *name, phys_addr_t addr, int len)
{
struct fw_mem_info *info;
info = kzalloc(sizeof(*info), GFP_KERNEL);
if (!info) {
pr_err("Can't kzalloc fw_mem_info.\n");
return -ENOMEM;
}
info->name = name;
info->pa = addr;
info->len = len;
info->addr = phys_to_virt(addr);
mutex_lock(&g_list_lock);
list_add(&info->list, &g_memory_fw_list);
mutex_unlock(&g_list_lock);
pr_debug("register fw:%s addr=%pad,len=%d,va=0x%p\n",
name, &addr, len, info->addr);
return 0;
}
EXPORT_SYMBOL(sunxi_register_memory_fw);
static struct fw_mem_info *sunxi_find_memory_fw(const char *name)
{
struct fw_mem_info *pos, *tmp;
mutex_lock(&g_list_lock);
list_for_each_entry_safe(pos, tmp, &g_memory_fw_list, list) {
if (!strcmp(pos->name, name)) {
mutex_unlock(&g_list_lock);
return pos;
}
}
mutex_unlock(&g_list_lock);
return NULL;
}
static int sunxi_unregister_memory_fw(struct fw_mem_info *info)
{
int ret;
mutex_lock(&g_list_lock);
list_del(&info->list);
mutex_unlock(&g_list_lock);
pr_debug("remove fw:%s addr=%pad,len=0x%x\n",
info->name, &info->pa, info->len);
ret = memblock_free(info->pa, info->len);
if (ret)
pr_err("memblock_free failed,ret=%d.\n", ret);
free_reserved_area(__va(info->pa), __va(info->pa + info->len), -1, NULL);
kfree(info);
return 0;
}
static void sunxi_remove_memory_fw(const char *name)
{
int ret;
struct fw_mem_info *pos, *tmp;
mutex_lock(&g_list_lock);
list_for_each_entry_safe(pos, tmp, &g_memory_fw_list, list) {
if (!strcmp(pos->name, name)) {
pr_debug("remove fw:%s addr=%pad,len=0x%x\n",
name, &pos->pa, pos->len);
list_del(&pos->list);
ret = memblock_free(pos->pa, pos->len);
if (ret)
pr_err("memblock_free failed,ret=%d.\n", ret);
free_reserved_area(__va(pos->pa), __va(pos->pa + pos->len), -1, NULL);
kfree(pos);
mutex_unlock(&g_list_lock);
return;
}
}
mutex_unlock(&g_list_lock);
}
static void decompress_timeout_handler(unsigned long arg)
{
struct fw_mem_info *info = (struct fw_mem_info *)arg;
Elf32_Ehdr *ehdr = info->addr;
if (!arg)
return;
#ifdef CONFIG_ARM64
__dma_flush_range((void *)ehdr, sizeof(*ehdr));
#else
dmac_flush_range((void *)ehdr, (void *)ehdr + sizeof(*ehdr));
#endif
if ((ehdr->e_machine & 0xff00) == 0)
wake_up_interruptible(&info->wq);
else
mod_timer(&info->timer, jiffies + msecs_to_jiffies(10));
}
static int sunxi_request_fw_from_mem(const struct firmware **fw,
const char *name, struct device *dev)
{
int ret;
u8 *img;
struct firmware *fw_p = NULL;
struct fw_mem_info *info;
Elf32_Ehdr *ehdr;
info = sunxi_find_memory_fw(name);
if (!info)
return -ENODEV;
img = vmalloc(info->len);
if (!img) {
sunxi_unregister_memory_fw(info);
return -ENOMEM;
}
fw_p = kmalloc(sizeof(struct firmware), GFP_KERNEL);
if (!fw_p) {
dev_err(dev, "failed to alloc Firmware\n");
vfree(img);
sunxi_unregister_memory_fw(info);
return -ENOMEM;
}
/* test if the elf is decompressing */
ehdr = (Elf32_Ehdr *)info->addr;
#ifdef CONFIG_ARM64
__dma_flush_range((void *)ehdr, sizeof(*ehdr));
#else
dmac_flush_range((void *)ehdr, (void *)ehdr + sizeof(*ehdr));
#endif
if ((ehdr->e_machine & 0xff00) == 0xff00) {
init_waitqueue_head(&info->wq);
init_timer(&info->timer);
info->timer.function = decompress_timeout_handler;
info->timer.data = (unsigned long)info;
info->timer.expires = jiffies + msecs_to_jiffies(10);
dev_dbg(dev, "wait for %s firmware decompress complete.\n", name);
add_timer(&info->timer);
/* decompressing */
if (wait_event_interruptible_timeout(info->wq,
!(ehdr->e_machine & 0xff00),
msecs_to_jiffies(5000)) <= 0) {
dev_dbg(dev, "%s decompress timeout.\n", name);
del_timer_sync(&info->timer);
vfree(img);
sunxi_unregister_memory_fw(info);
return -ENODEV;
}
dev_dbg(dev, "%s decompress complete.\n", name);
del_timer_sync(&info->timer);
}
/* read data from memory */
memcpy(img, info->addr, info->len);
fw_p->size = info->len;
fw_p->priv = NULL;
fw_p->pages = NULL;
fw_p->data = img;
*fw = fw_p;
ret = 0;
#ifdef DEBUG
sunxi_firmware_dump_data(img, 128);
#endif
return ret;
}
#else
int sunxi_register_memory_fw(const char *name, phys_addr_t addr, int len)
{
pr_info("don't support register memory fw.\n");
return 0;
}
EXPORT_SYMBOL(sunxi_register_memory_fw);
#endif
int sunxi_request_firmware(const struct firmware **fw, const char *name, struct device *dev)
{
int ret, index;
struct firmware *fw_p = NULL;
u32 img_addr, img_len;
#ifdef FW_SAVE_IN_MEM
/* try to read fw from memory */
ret = sunxi_request_fw_from_mem(fw, name, dev);
if (!ret) {
dev_dbg(dev, "finded %s in memory.\n", name);
ret = 0;
goto out;
}
dev_dbg(dev, "Can't find %s in memory.\n", name);
#endif
ret = sunxi_find_firmware_storage();
if (ret < 0) {
dev_warn(dev, "Can't finded boot_package head\n");
return -ENODEV;
}
index = ret;
ret = sunxi_firmware_get_info(dev, index, name, &img_addr, &img_len);
if (ret < 0) {
dev_warn(dev, "failed to read boot_package item\n");
ret = -EFAULT;
goto out;
}
ret = sunxi_firmware_get_data(dev, index, img_addr, img_len, &fw_p);
if (ret < 0) {
dev_err(dev, "failed to read Firmware\n");
ret = -ENOMEM;
goto out;
}
*fw = fw_p;
out:
return ret;
}
EXPORT_SYMBOL(sunxi_request_firmware);
static void request_firmware_work_func(struct work_struct *work)
{
struct sunxi_firmware_work *fw_work;
const struct firmware *fw;
int ret;
static int try;
fw_work = container_of(to_delayed_work(work), struct sunxi_firmware_work, work);
try++;
dev_dbg(fw_work->device, "try to request firmware,try%d\n", try);
ret = sunxi_request_firmware(&fw, fw_work->name, fw_work->device);
if (ret < 0 && try < FW_TRY_CNT) {
schedule_delayed_work(&fw_work->work, DELAY_TIME);
} else {
if (ret < 0)
dev_warn(fw_work->device, "sunxi_request_firmware"
"failed,ret=%d\n", ret);
else
fw_work->cont(fw, fw_work->context);
put_device(fw_work->device);
try = 0;
cancel_delayed_work(&fw_work->work);
#ifdef FW_SAVE_IN_MEM
if (ret == 0) {
dev_dbg(fw_work->device, "Success boot fw,remove fw.\n");
sunxi_remove_memory_fw(fw_work->name);
}
#endif
kfree_const(fw_work->name);
kfree(fw_work);
}
}
int
sunxi_request_firmware_nowait(
const char *name, struct device *device, gfp_t gfp, void *context,
void (*cont)(const struct firmware *fw, void *context))
{
struct sunxi_firmware_work *fw_work;
#ifdef CONFIG_SUNXI_RPROC_FASTBOOT
const struct firmware *fw;
int ret;
dev_dbg(device, "try to directly request firmware\n");
ret = sunxi_request_firmware(&fw, name, device);
if (!ret) {
cont(fw, context);
sunxi_remove_memory_fw(name);
return 0;
}
#endif
fw_work = kzalloc(sizeof(*fw_work), gfp);
if (!fw_work)
return -ENOMEM;
fw_work->name = kstrdup_const(name, gfp);
if (!fw_work->name) {
kfree(fw_work);
return -ENOMEM;
}
fw_work->device = device;
fw_work->context = context;
fw_work->cont = cont;
get_device(fw_work->device);
INIT_DELAYED_WORK(&fw_work->work, request_firmware_work_func);
schedule_delayed_work(&fw_work->work, 0);
return 0;
}
EXPORT_SYMBOL(sunxi_request_firmware_nowait);
MODULE_DESCRIPTION("SUNXI Remote Firmware Loader Helper");
MODULE_AUTHOR("lijiajian <lijiajian@allwinnertech.com>");
MODULE_LICENSE("GPL v2");