439 lines
13 KiB
C
439 lines
13 KiB
C
|
#ifdef __RTOS__
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/types.h>
|
||
|
#include <linux/skbuff.h>
|
||
|
#else
|
||
|
#include <linux/version.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/sched.h>
|
||
|
#include <linux/net.h>
|
||
|
#include <linux/etherdevice.h>
|
||
|
#include <linux/wireless.h>
|
||
|
#include <linux/firmware.h>
|
||
|
#endif
|
||
|
|
||
|
#include "../hgic_def.h"
|
||
|
#include "fwinfo.h"
|
||
|
#include "fwctrl.h"
|
||
|
#include "fwdl.h"
|
||
|
#include "utils.h"
|
||
|
|
||
|
#define BOOT_CMD_KEY "@huge-ic"
|
||
|
#define BOOT_CMD_KEY_SIZE 8
|
||
|
|
||
|
void hgic_bootdl_release(struct hgic_bootdl *hg, int mode)
|
||
|
{
|
||
|
if (hg == NULL) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
hgic_dbg("Enter ...\n");
|
||
|
|
||
|
hgic_dbg("Leave ...\n");
|
||
|
}
|
||
|
|
||
|
u32 hgic_bootdl_init(struct hgic_bootdl *hg_fwdl, struct hgic_bus *bus, struct hgic_fwctrl *ctrl)
|
||
|
{
|
||
|
if (hg_fwdl == NULL) {
|
||
|
printk("%s:%d:Input para error!\n", __FUNCTION__, __LINE__);
|
||
|
return EINVAL;
|
||
|
}
|
||
|
|
||
|
hgic_dbg("Enter ...\n");
|
||
|
memset(hg_fwdl, 0, sizeof(struct hgic_bootdl));
|
||
|
hg_fwdl->checksum_mode = bus->bootdl_cksum;
|
||
|
hg_fwdl->frag_size = bus->bootdl_pktlen;
|
||
|
hg_fwdl->ctrl = ctrl;
|
||
|
hg_fwdl->bus = bus;
|
||
|
hgic_dbg("Leave ...each packet len:%d\n", hg_fwdl->frag_size);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static u8 crc8(char *p_buf, u32 len)
|
||
|
{
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static struct sk_buff *hgic_bootdl_alloc_cmd_skb(struct hgic_bootdl *hg, u8 cmd_id)
|
||
|
{
|
||
|
struct sk_buff *skb = NULL;
|
||
|
struct hgic_bootdl_cmd_hdr *cmd_hdr = NULL;
|
||
|
|
||
|
skb = dev_alloc_skb(sizeof(struct hgic_bootdl_cmd_hdr) + 4);
|
||
|
if (!skb) {
|
||
|
return skb;
|
||
|
}
|
||
|
cmd_hdr = (struct hgic_bootdl_cmd_hdr *)skb->data;
|
||
|
cmd_hdr->cmd = cmd_id;
|
||
|
cmd_hdr->cmd_len = 12;
|
||
|
cmd_hdr->cmd_flag = hg->checksum_mode;
|
||
|
skb_put(skb, sizeof(struct hgic_bootdl_cmd_hdr));
|
||
|
return skb;
|
||
|
}
|
||
|
|
||
|
static u8 hgic_bootdl_cmd_check_val(u8 check_mode, u8 *p_buf, u32 len)
|
||
|
{
|
||
|
u8 check_value = 0;
|
||
|
u32 i = 0;
|
||
|
|
||
|
if (HGIC_BUS_BOOTDL_CHECK_SUM == check_mode) {
|
||
|
for (i = 0; i < len; i++) {
|
||
|
check_value += *p_buf++;
|
||
|
}
|
||
|
} else if (HGIC_BUS_BOOTDL_CHECK_CRC8 == check_mode) {
|
||
|
check_value = crc8(p_buf, len);
|
||
|
} else if (HGIC_BUS_BOOTDL_CHECK_0XFD == check_mode) {
|
||
|
check_value = 0xFD;
|
||
|
} else {
|
||
|
return 0;
|
||
|
}
|
||
|
return check_value;
|
||
|
}
|
||
|
|
||
|
static int hgic_bootdl_cmd_rsp_handle(unsigned char cmd_flag, unsigned char cmd, struct hgic_bootdl_resp_hdr *resp)
|
||
|
{
|
||
|
if (resp->check != hgic_bootdl_cmd_check_val(cmd_flag, (u8 *)&resp->cmd, 8)) {
|
||
|
hgic_dbg("Checksum error,return HG_BOOTDL_ERR_RSP_PACKET!\r\n");
|
||
|
return HG_BOOTDL_ERR_RSP_PACKET;
|
||
|
}
|
||
|
if (resp->cmd != cmd) {
|
||
|
hgic_dbg("Cmd not match,resp_cmd:<%d>,cmd:<%d>\n", resp->cmd, cmd);
|
||
|
return HG_BOOTDL_ERR_RSP_PACKET;
|
||
|
}
|
||
|
|
||
|
return resp->rsp;
|
||
|
}
|
||
|
|
||
|
|
||
|
struct sk_buff *hgic_bootdl_send_cmd(struct hgic_bootdl *hg_fwdl, struct sk_buff *skb, u32 timeout)
|
||
|
{
|
||
|
struct hgic_bootdl_cmd_hdr *hdr = NULL;
|
||
|
struct hgic_cmd_response resp;
|
||
|
|
||
|
hdr = (struct hgic_bootdl_cmd_hdr *)skb->data;
|
||
|
hdr->hdr.magic = HGIC_HDR_TX_MAGIC;
|
||
|
hdr->hdr.type = HGIC_HDR_TYPE_BOOTDL;
|
||
|
hdr->hdr.length = skb->len;
|
||
|
hdr->hdr.cookie = hgic_ctrl_cookie(hg_fwdl->ctrl);
|
||
|
|
||
|
//printk("hg_fwdl->ctrl=%p\r\n", hg_fwdl->ctrl);
|
||
|
memset(&resp, 0, sizeof(resp));
|
||
|
resp.cookie = hdr->hdr.cookie;
|
||
|
hg_fwdl->last_cookie = resp.cookie;
|
||
|
return hgic_fwctrl_send_data(hg_fwdl->ctrl, skb, &resp, timeout);
|
||
|
}
|
||
|
|
||
|
struct sk_buff *hgic_bootdl_send_data(struct hgic_bootdl *hg_fwdl, struct sk_buff *skb, u32 timeout)
|
||
|
{
|
||
|
struct hgic_bootdl_cmd_hdr *hdr = NULL;
|
||
|
struct hgic_cmd_response resp;
|
||
|
|
||
|
hdr = (struct hgic_bootdl_cmd_hdr *)skb_push(skb, sizeof(struct hgic_bootdl_cmd_hdr));
|
||
|
hdr->hdr.magic = HGIC_HDR_TX_MAGIC;
|
||
|
hdr->hdr.type = HGIC_HDR_TYPE_BOOTDL_DATA;
|
||
|
memset(&resp, 0, sizeof(struct hgic_cmd_response));
|
||
|
resp.cookie = hg_fwdl->last_cookie;
|
||
|
return hgic_fwctrl_send_data(hg_fwdl->ctrl, skb, &resp, timeout);
|
||
|
}
|
||
|
|
||
|
static int hgic_bootdl_parse_fw(struct hgic_bootdl *hg, const char *fw_name)
|
||
|
{
|
||
|
const struct firmware *fw;
|
||
|
int ret = 0;
|
||
|
s32 err_code = 0;
|
||
|
|
||
|
ret = request_firmware(&fw, fw_name, hg->ctrl->dev);
|
||
|
if (ret) {
|
||
|
printk("%s,%d:request_firmware failed!ret:%d\n", __FUNCTION__, __LINE__, ret);
|
||
|
return ret;
|
||
|
}
|
||
|
hg->fw = (struct firmware *)fw;
|
||
|
|
||
|
hg->fw_info.write_addr = hgic_get_fw_dl_addr(fw->data, &err_code);
|
||
|
if (err_code) {
|
||
|
hgic_dbg("hgic_get_fw_dl_addr error!\n");
|
||
|
return err_code;
|
||
|
}
|
||
|
|
||
|
hg->fw_info.aes_en = hgic_get_fw_aes_en(fw->data, &err_code);
|
||
|
if (err_code) {
|
||
|
hgic_dbg("hgic_get_aes_en error!\n");
|
||
|
return err_code;
|
||
|
}
|
||
|
|
||
|
hg->fw_info.crc_en = hgic_get_fw_crc_en(fw->data, &err_code);
|
||
|
if (err_code) {
|
||
|
hgic_dbg("hgic_get_fw_crc_en error!\n");
|
||
|
return err_code;
|
||
|
}
|
||
|
|
||
|
hg->fw_info.local_crc32 = hgic_get_fw_local_crc32(fw->data, &err_code);
|
||
|
if (err_code) {
|
||
|
hgic_dbg("hgic_get_fw_local_crc32 error!\n");
|
||
|
return err_code;
|
||
|
}
|
||
|
|
||
|
hg->fw_info.hdr_len = hgic_get_fw_code_offset(fw->data, &err_code);
|
||
|
if (err_code) {
|
||
|
hgic_dbg("hgic_get_fw_code_offset error!\n");
|
||
|
return err_code;
|
||
|
}
|
||
|
|
||
|
hg->fw_info.sp_cfg = 1;
|
||
|
hg->fw_info.fw_len = fw->size - hg->fw_info.hdr_len;
|
||
|
|
||
|
hgic_dbg("firmware hdr len : %d\r\n", hg->fw_info.hdr_len);
|
||
|
hgic_dbg("firmware run addr : %x\r\n", hg->fw_info.write_addr);
|
||
|
hgic_dbg("firmware size : %d\r\n", hg->fw_info.fw_len);
|
||
|
hgic_dbg("firmware aes_en:%d,crc_en:%d\r\n", hg->fw_info.aes_en, hg->fw_info.crc_en);
|
||
|
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int hgic_bootdl_send_fw(struct hgic_bootdl *hg, struct sk_buff *skb, u32 tmo)
|
||
|
{
|
||
|
struct sk_buff *resp_skb = NULL;
|
||
|
struct hgic_bootdl_resp_hdr *data_resp = NULL;
|
||
|
int ret = 0;
|
||
|
|
||
|
if (skb == NULL || hg == NULL) {
|
||
|
hgic_dbg("input para error\n");
|
||
|
return EINVAL;
|
||
|
}
|
||
|
|
||
|
resp_skb = hgic_bootdl_send_data(hg, skb, tmo);
|
||
|
if (resp_skb) {
|
||
|
data_resp = (struct hgic_bootdl_resp_hdr *)resp_skb->data;
|
||
|
ret = data_resp->rsp;
|
||
|
if (ret != 0) {
|
||
|
hgic_dbg("Send fw data error!ret:<%d>\n", ret);
|
||
|
} else {
|
||
|
hgic_dbg("Send fw data success!\n");
|
||
|
}
|
||
|
dev_kfree_skb_any(resp_skb);
|
||
|
return ret;
|
||
|
} else {
|
||
|
hgic_dbg("send fw data error, no resp!\n");
|
||
|
return EFAULT;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int hgic_bootdl_send_cmd_tmo(struct hgic_bootdl *hg,
|
||
|
struct sk_buff *skb,
|
||
|
u32 timeout,
|
||
|
u32 *resp_data)
|
||
|
{
|
||
|
struct sk_buff *resp = NULL;
|
||
|
struct hgic_bootdl_cmd_hdr *cmd_hdr = (struct hgic_bootdl_cmd_hdr *)skb->data;
|
||
|
struct hgic_bootdl_resp_hdr *boot_resp = NULL;
|
||
|
int ret = 0;
|
||
|
unsigned char cmd_flag = 0;
|
||
|
unsigned char cmd = 0;
|
||
|
|
||
|
if (hg == NULL || skb == NULL) {
|
||
|
return -EINVAL;
|
||
|
}
|
||
|
|
||
|
cmd_flag = cmd_hdr->cmd_flag;
|
||
|
cmd = cmd_hdr->cmd;
|
||
|
resp = hgic_bootdl_send_cmd(hg, skb, timeout);
|
||
|
if (resp) {
|
||
|
boot_resp = (struct hgic_bootdl_resp_hdr *)resp->data;
|
||
|
ret = hgic_bootdl_cmd_rsp_handle(cmd_flag, cmd, boot_resp);
|
||
|
if (ret != 0 && ret != HG_BOOTDL_RSP_ERR_IN_FW) {
|
||
|
hgic_err("cmd %d, error:%d\n", cmd, ret);
|
||
|
dev_kfree_skb_any(resp);
|
||
|
return ret;
|
||
|
}
|
||
|
if (resp_data != NULL) {
|
||
|
*resp_data = get_unaligned_le32(boot_resp->rsp_data);
|
||
|
}
|
||
|
dev_kfree_skb_any(resp);
|
||
|
} else {
|
||
|
hgic_err("cmd: %d, no responce!!\r\n", cmd);
|
||
|
ret = -1;
|
||
|
}
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
int hgic_bootdl_cmd_enter(struct hgic_bootdl *hg)
|
||
|
{
|
||
|
struct sk_buff *skb = NULL;
|
||
|
struct hgic_bootdl_cmd_hdr *cmd_hdr = NULL;
|
||
|
int ret = 0;
|
||
|
|
||
|
skb = hgic_bootdl_alloc_cmd_skb(hg, HG_BOOTDL_CMD_ENTER);
|
||
|
if (skb) {
|
||
|
cmd_hdr = (struct hgic_bootdl_cmd_hdr *)skb->data;
|
||
|
memcpy((void *)cmd_hdr->addr, (void *)BOOT_CMD_KEY, BOOT_CMD_KEY_SIZE);
|
||
|
cmd_hdr->check = hgic_bootdl_cmd_check_val(hg->checksum_mode, (u8 *)&cmd_hdr->cmd, 11);
|
||
|
} else {
|
||
|
hgic_err("malloc skb failed!\n");
|
||
|
return ENOMEM;
|
||
|
}
|
||
|
ret = hgic_bootdl_send_cmd_tmo(hg, skb, HG_BOOTDL_CMD_NORMAL_TMO, NULL);
|
||
|
if (ret == 0 || ret == HG_BOOTDL_RSP_ERR_IN_FW) {
|
||
|
if (ret == HG_BOOTDL_RSP_ERR_IN_FW) {
|
||
|
//hgic_err("In firmware state\n");
|
||
|
return STATE_FW;
|
||
|
} else {
|
||
|
//hgic_err("In bootdl state\n");
|
||
|
return STATE_BOOT;
|
||
|
}
|
||
|
} else {
|
||
|
hgic_err("failed! ret:%d\n", ret);
|
||
|
return ret;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static int hgic_bootdl_cmd_write_memory(struct hgic_bootdl *hg, u32 write_addr, u32 data_len)
|
||
|
{
|
||
|
struct sk_buff *skb = NULL;
|
||
|
struct hgic_bootdl_cmd_hdr *cmd_hdr = NULL;
|
||
|
|
||
|
skb = hgic_bootdl_alloc_cmd_skb(hg, HG_BOOTDL_CMD_WRITE_MEM);
|
||
|
if (skb) {
|
||
|
cmd_hdr = (struct hgic_bootdl_cmd_hdr *)skb->data;
|
||
|
put_unaligned_le32(write_addr, cmd_hdr->addr);
|
||
|
if (hg->bus->type == HGIC_BUS_SDIO) {
|
||
|
put_unaligned_le32(data_len, cmd_hdr->len);
|
||
|
} else {
|
||
|
put_unaligned_le32((data_len + 1024), cmd_hdr->len);
|
||
|
}
|
||
|
cmd_hdr->check = hgic_bootdl_cmd_check_val(hg->checksum_mode, (u8 *)&cmd_hdr->cmd, 11);
|
||
|
} else {
|
||
|
hgic_dbg("malloc skb failed!\n");
|
||
|
return ENOMEM;
|
||
|
}
|
||
|
return hgic_bootdl_send_cmd_tmo(hg, skb, HG_BOOTDL_CMD_NORMAL_TMO, NULL);
|
||
|
}
|
||
|
|
||
|
static int hgic_bootdl_cmd_run(struct hgic_bootdl *hg)
|
||
|
{
|
||
|
struct sk_buff *skb = NULL;
|
||
|
struct hgic_bootdl_cmd_hdr *cmd_hdr = NULL;
|
||
|
u32 temp = 0;
|
||
|
|
||
|
skb = hgic_bootdl_alloc_cmd_skb(hg, HG_BOOTDL_CMD_RUN);
|
||
|
if (skb) {
|
||
|
cmd_hdr = (struct hgic_bootdl_cmd_hdr *)skb->data;
|
||
|
put_unaligned_le32(hg->fw_info.write_addr, cmd_hdr->addr);
|
||
|
|
||
|
temp = hg->fw_info.fw_len;
|
||
|
if (hg->fw_info.aes_en) {
|
||
|
temp |= HG_BOOT_CMD_RUN_PREACT_AES_DEC;
|
||
|
}
|
||
|
if (hg->fw_info.crc_en) {
|
||
|
temp |= HG_BOOT_CMD_RUN_PREACT_CRC_CHK;
|
||
|
}
|
||
|
if (hg->fw_info.sp_cfg) {
|
||
|
temp |= HG_BOOT_CMD_RUN_PREACT_SP_CFG;
|
||
|
}
|
||
|
put_unaligned_le32(temp, cmd_hdr->len);
|
||
|
|
||
|
cmd_hdr->check = hgic_bootdl_cmd_check_val(hg->checksum_mode, (u8 *)&cmd_hdr->cmd, 11);
|
||
|
} else {
|
||
|
hgic_dbg("malloc skb failed!\n");
|
||
|
return ENOMEM;
|
||
|
}
|
||
|
return hgic_bootdl_send_cmd_tmo(hg, skb, HG_BOOTDL_CMD_NORMAL_TMO, NULL);
|
||
|
}
|
||
|
|
||
|
static unsigned int hgic_bootdl_fragment_proc(struct hgic_bootdl *hg, unsigned int copy_len)
|
||
|
{
|
||
|
unsigned int xfer_len = 0;
|
||
|
unsigned int blk = 0;
|
||
|
|
||
|
if (hg->bus->type == HGIC_BUS_SDIO) {
|
||
|
if (copy_len < hg->frag_size) {
|
||
|
blk = copy_len / hg->bus->blk_size;
|
||
|
if (blk) {
|
||
|
xfer_len = blk * hg->bus->blk_size;
|
||
|
} else {
|
||
|
//xfer_len = copy_len;
|
||
|
xfer_len = ALIGN(copy_len, 4);
|
||
|
}
|
||
|
} else {
|
||
|
xfer_len = hg->frag_size;
|
||
|
}
|
||
|
} else {
|
||
|
xfer_len = copy_len > hg->frag_size ? hg->frag_size : copy_len;
|
||
|
}
|
||
|
return xfer_len;
|
||
|
}
|
||
|
|
||
|
int hgic_bootdl_download(struct hgic_bootdl *hg, const char *fw_path)
|
||
|
{
|
||
|
int ret = 0;
|
||
|
struct sk_buff *skb = NULL;
|
||
|
u32 write_addr = 0;
|
||
|
u32 copy_len = 0;
|
||
|
u32 xfer_len = 0;
|
||
|
u32 addr_offset = 0;
|
||
|
char *data = NULL;
|
||
|
|
||
|
if (hg == NULL || fw_path == NULL) {
|
||
|
printk("%s,%d:input para error!\n", __FUNCTION__, __LINE__);
|
||
|
return EINVAL;
|
||
|
}
|
||
|
|
||
|
hgic_enter();
|
||
|
ret = hgic_bootdl_parse_fw(hg, fw_path);
|
||
|
if (ret != 0 || !hg->fw) {
|
||
|
hgic_dbg("hgic_bootdl_parse_fw error,ret:<%d>,path:%s\n", ret, fw_path);
|
||
|
goto __finish;
|
||
|
}
|
||
|
|
||
|
copy_len = hg->fw_info.fw_len;
|
||
|
write_addr = hg->fw_info.write_addr;
|
||
|
data = (char *)(hg->fw->data + hg->fw_info.hdr_len);
|
||
|
while (copy_len) {
|
||
|
//xfer_len = copy_len > hg->frag_size ? hg->frag_size : copy_len;
|
||
|
xfer_len = hgic_bootdl_fragment_proc(hg, copy_len);
|
||
|
skb = dev_alloc_skb(xfer_len + sizeof(struct hgic_bootdl_cmd_hdr));
|
||
|
if (skb == NULL) {
|
||
|
printk("%s: no memory\r\n", __FUNCTION__);
|
||
|
ret = -ENOMEM;
|
||
|
goto __finish;
|
||
|
}
|
||
|
|
||
|
skb_reserve(skb, sizeof(struct hgic_bootdl_cmd_hdr));
|
||
|
memcpy(skb->data, data + addr_offset, xfer_len);
|
||
|
skb_put(skb, xfer_len);
|
||
|
|
||
|
ret = hgic_bootdl_cmd_write_memory(hg, write_addr, skb->len);
|
||
|
if (ret != 0) {
|
||
|
hgic_dbg("hgic_bootdl_cmd_write_memory error!\n");
|
||
|
dev_kfree_skb_any(skb);
|
||
|
goto __finish;
|
||
|
}
|
||
|
|
||
|
ret = hgic_bootdl_send_fw(hg, skb, HG_BOOTDL_CMD_WRITE_MEM_TMO);
|
||
|
if (ret != 0) {
|
||
|
hgic_dbg("hgic_bootdl_send_fw error!\n");
|
||
|
goto __finish;
|
||
|
}
|
||
|
write_addr += xfer_len;
|
||
|
copy_len -= xfer_len;
|
||
|
addr_offset += xfer_len;
|
||
|
skb = NULL;
|
||
|
}
|
||
|
|
||
|
ret = hgic_bootdl_cmd_run(hg);
|
||
|
if (ret) {
|
||
|
printk("%s: Cmd run failed:%d\r\n", __FUNCTION__, ret);
|
||
|
}
|
||
|
__finish:
|
||
|
if (hg->fw) {
|
||
|
hgic_dbg("Release boot download firmware...\n");
|
||
|
release_firmware(hg->fw);
|
||
|
hg->fw = NULL;
|
||
|
}
|
||
|
hgic_leave();
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
|
||
|
|