linuxOS_D21X/source/uboot-2021.10/common/spl/artinchip_spl_fat.c
2024-11-29 16:13:46 +08:00

695 lines
17 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2022 ArtInChip Technology Co.,Ltd
* Author: Hao Xiong <hao.xiong@artinchip.com>
*/
#include <common.h>
#include <fat.h>
#include <spl.h>
#include <log.h>
#include <blk.h>
#include <asm/unaligned.h>
#include <config_parse.h>
#include <artinchip_spl_fat.h>
static struct blk_desc *cur_dev;
static struct fat32_bpb *cur_bpb;
static struct spl_image_info *cur_image;
static int check_identifier(u8 *header)
{
struct mbr_dpt_entry part;
/* Last two bytes, MBR identifier */
if ((header[510] != 0x55) || (header[511] != 0xAA)) {
debug("Not MBR");
return -1;
}
/* Check FAT32 string, 8 characters */
if (strncmp((char *)&header[BS_FilSysType32], "FAT32 ", 8) == 0)
return TYPE_RAW_VOLUME_FAT32;
/* Check partition type flag */
memcpy(&part, header + DPT_START, sizeof(struct mbr_dpt_entry));
if (part.lba_start == 0 || part.lba_cnt == 0) {
debug("partition info not correct.\n");
return -1;
}
switch (part.part_type) {
case 0x0B: /* Win95+FAT32 */
case 0x0C: /* Win95+FAT32(using LBA-mode INT 13 extensions) */
case 0x1B: /* Hidden Win95+FAT32 */
case 0x1C: /* Hidden Win95+FAT32(using LBA-mode INT 13 extensions) */
return TYPE_MBR_PART_FAT32;
case 0xEE: /* GPT */
return TYPE_GPT_PART_FAT32;
}
debug("Unknown header.\n");
return -1;
}
static int search_boot_cfg_file(u32 *cluster, ulong *filesiz, u8 *buf)
{
u32 blkcnt, dirent_cnt, dirent_start, clus_siz;
u32 sect_idx = 0, i = 0, status = 0;
u8 *pdirent;
char shname[12];
dirent_start = cur_bpb->data_start;
clus_siz = cur_bpb->cluster_size;
if (cur_bpb->root_dirent_cluster != 2) {
debug("Root directory entry should use cluster 2.\n");
return -1;
}
/* Step1: Search File in dirent sectors */
for (sect_idx = 0; sect_idx < clus_siz; sect_idx++) {
pdirent = (u8 *)buf; /* Read and search */
blkcnt = blk_dread(cur_dev, dirent_start + sect_idx, 1,
pdirent);
if (blkcnt != 1) {
debug("Read dirent sector failed.\n");
return -1;
}
dirent_cnt = (LBA_SIZE >> 5);
shname[11] = 0;
/* Search in Short DIRENTs only */
for (i = 0; i < dirent_cnt; i++, pdirent += DIR_ENTRY_SIZE) {
if (pdirent[0] == DIRENT_END) { /* End of dirent list */
debug("No dirent\n");
return -1;
}
if (pdirent[0] == DIRENT_DEL) /* Deleted item */
continue;
if (LFN_DIR_Flag(pdirent)) /* Long DIRENT */
continue;
memcpy(shname, pdirent, 11);
if (SHT_DIR_Filesize(pdirent) != 0) {
status = strncmp(IMAGE_NAME,
(const char *)pdirent,
IMAGE_NAME_SIZ);
status += strncmp(IMAGE_SUFFIX,
(const char *)&pdirent[8],
IMAGE_SUFFIX_SIZ);
if (status != 0)
continue;
*cluster = SHT_DIR_StartCluster(pdirent);
debug("Start cluster: %d\n", *cluster);
*filesiz = SHT_DIR_Filesize(pdirent);
debug("bootcfg file size: %lu\n", *filesiz);
return 0;
}
}
}
debug("search bootcfg file failed.\n");
return -1;
}
static int read_file_data(u32 *clusters, ulong filesiz, ulong sect_off,
u32 byte_off, u8 *filebuf)
{
u32 idx = 0, sect = 0, sect_cnt = 0, cnt = 0, cur = 0;
u32 to_read = 0, read_len = 0, data_sector = 0, byte_len = 0;
u8 *p;
u8 temp[LBA_SIZE];
p = (u8 *)filebuf;
for (idx = 0; idx < IMG_MAX_CLUSTER; idx++) {
cur = clusters[idx];
debug("Read cluster id %d\n", cur);
if (cur == 0)
break;
data_sector = cur_bpb->data_start + (cur - 2)
* cur_bpb->cluster_size;
sect_cnt = cur_bpb->cluster_size;
/*
* sect_off: The first data's sector offset in the first
* cluster
*/
if (sect_off) {
/* Only set offset for the first data */
data_sector += sect_off;
sect_cnt -= sect_off;
sect_off = 0;
}
/*
* byte_off: The first data's byte offset in the first sector
*/
if (byte_off) {
cnt = blk_dread(cur_dev, data_sector, 1, temp);
if (cnt != 1) {
debug("Read file failed.\n");
return -1;
}
byte_len = LBA_SIZE - byte_off;
memcpy(p, temp+byte_off, byte_len);
p += byte_len;
read_len += byte_len;
data_sector += 1;
sect_cnt -= 1;
byte_off = 0;
if (read_len >= filesiz)
return 0;
}
to_read = sect_cnt * LBA_SIZE;
if ((read_len + to_read) <= filesiz) {
cnt = blk_dread(cur_dev, data_sector, sect_cnt, p);
if (cnt != sect_cnt) {
debug("Read file failed.\n");
return -1;
}
p += to_read;
read_len += to_read;
if (read_len >= filesiz)
return 0;
} else {
for (sect = 0; sect < sect_cnt; sect++) {
cnt = blk_dread(cur_dev, data_sector + sect,
1, p);
if (cnt != 1) {
debug("Read file failed.\n");
return -1;
}
p += LBA_SIZE;
read_len += LBA_SIZE;
if (read_len >= filesiz)
return 0;
}
}
}
debug("Read file data failed.\n");
return -1;
}
static int search_boot_image_file(const char *filename, u32 *cluster,
ulong *actread, u8 *buf)
{
u32 i = 0, j = 0, blkcnt, chidx = 0, do_flag = 0, sect_idx = 0;
u32 dirent_cnt, dirent_start, clus_siz;
u8 *pdirent;
char lngname[IMG_NAME_MAX_SIZ];
dirent_start = cur_bpb->data_start;
clus_siz = cur_bpb->cluster_size;
if (cur_bpb->root_dirent_cluster != 2) {
debug("Root directory entry should use cluster 2.\n");
return -1;
}
/* Step1: Search File in dirent sectors */
for (sect_idx = 0; sect_idx < clus_siz; sect_idx++) {
pdirent = (u8 *)buf; /* Read and search */
blkcnt = blk_dread(cur_dev, dirent_start + sect_idx, 1,
pdirent);
if (blkcnt != 1) {
debug("Read dirent sector failed.\n");
return -1;
}
dirent_cnt = (LBA_SIZE >> 5);
/* Search in Long DIRENT only
*
* Step:
* 1. Get parse long DIRENT to get long name first
* 2. Compare long name
* 3. If long name is match, then parse Short DIRENT
* to get start cluster and file size
*
* Note:
* Long DIRENTs is in Reverse Order.
*/
/* Long DIRENT and the last one. OK begin parse it */
for (i = 0; i < dirent_cnt; i++, pdirent += DIR_ENTRY_SIZE) {
if (pdirent[0] == 0x0) { /* End of dirent list */
debug("No dirent\n");
return -1;
}
if (pdirent[0] == 0xe5) /* Deleted item */
continue;
/* Long DIRENT and the last one. OK begin parse it */
if (LFN_DIR_Flag(pdirent) &&
LFN_DIR_Attr_Last(pdirent)) {
do_flag = F_PARSE_LONG;
memset(lngname, 0, IMG_NAME_MAX_SIZ);
}
if ((do_flag == F_PARSE_LONG) &&
LFN_DIR_Flag(pdirent)) {
/* Get file name. Only support ASCII */
chidx = (LFN_DIR_Attr_Number(pdirent) - 1) * 13;
/* Part 1 */
for (j = 0x1; j < 0xB; j += 2) {
if (pdirent[j] != 0xFF)
lngname[chidx++] = pdirent[j];
}
/* Part 2 */
for (j = 0xE; j < 0x1A; j += 2) {
if (pdirent[j] != 0xFF)
lngname[chidx++] = pdirent[j];
}
/* Part 3 */
for (j = 0x1C; j < 0x20; j += 2) {
if (pdirent[j] != 0xFF)
lngname[chidx++] = pdirent[j];
}
}
if ((do_flag == F_PARSE_LONG) &&
LFN_DIR_Flag(pdirent) &&
(LFN_DIR_Attr_Number(pdirent) == 1)) {
debug("Find name:%s\n", filename);
debug("Got name:%s\n", lngname);
/* The last Long DIRENT is parsed, need to
* compare file name
*/
if (!strncmp(filename, lngname,
strlen(filename)))
do_flag = F_PARSE_SHORT;
else
do_flag = F_PARSE_NONE;
}
if ((do_flag == F_PARSE_SHORT) &&
(LFN_DIR_Flag(pdirent) == 0) &&
(SHT_DIR_Filesize(pdirent) != 0)) {
/* Usually Short DIRENT following after Long
* DIRENT for the same file, so here don't
* verify it.
*/
*cluster = SHT_DIR_StartCluster(pdirent);
debug("Start cluster: %d\n", *cluster);
return 0;
}
}
}
debug("Search image file failed..\n");
return -1;
}
static u32 get_fat_sector_offset(u32 cluster)
{
u32 curmax = 0, offset = 0;
while (1) {
curmax += MAX_FAT_ITEM_PER_SECT;
if (curmax > cluster)
break;
offset++;
}
return offset;
}
static int update_start_cluster(u32 start_idx, u32 *cstart, void *buf)
{
u32 cnt = 0, idx = 1, offset = 0, cur, next = 0;
u32 *pfat;
/* Reuse BOOTLOADER_BUF_ADDR to read image */
pfat = (u32 *)buf;
cur = *cstart;
while (1) {
offset = get_fat_sector_offset(cur);
/* Read FAT sector which cur cluster id located, to find out
* next cluster id.
*/
cnt = blk_dread(cur_dev, cur_bpb->fat_start + offset, 1,
(u8 *)pfat);
if (cnt != 1) {
debug("Read FAT sector failed.\n");
return -1;
}
next = *(pfat + (cur - (offset * MAX_FAT_ITEM_PER_SECT)));
/* File data end in this cluster. */
if (next == 0x0FFFFFFF)
break;
if (idx == start_idx) {
*cstart = next;
break;
}
idx++;
cur = next;
}
return 0;
}
/*
* Get all cluster ids which image is using.
*
* Cluster value:
* 0x0FFFFFF8, FAT start, cluster 0 use it
* 0xFFFFFFFF, cluster 1 use it, mark unused
* 0x0FFFFFFF, File data end in this cluster.
*/
static int get_file_cluster_list(u32 cstart, u32 *clusters, void *buf)
{
u32 cnt = 0, idx = 1, offset = 0, cur, next = 0;
u32 *pfat;
/* Reuse BOOTLOADER_BUF_ADDR to read image */
pfat = (u32 *)buf;
cur = cstart;
clusters[0] = cur;
while (1) {
offset = get_fat_sector_offset(cur);
/* Read FAT sector which cur cluster id located, to find out
* next cluster id.
*/
cnt = blk_dread(cur_dev, cur_bpb->fat_start + offset, 1,
(u8 *)pfat);
if (cnt != 1) {
debug("Read FAT sector failed.\n");
return -1;
}
next = *(pfat + (cur - (offset * MAX_FAT_ITEM_PER_SECT)));
if (next == 0x0FFFFFFF)
break;
clusters[idx++] = next;
cur = next;
if (idx >= IMG_MAX_CLUSTER)
break;
}
return idx;
}
static int aic_fat_read_file(const char *filename, void *buf, ulong offset,
ulong maxsize, ulong *actread)
{
int err = 0;
u32 sect_off, byte_off = 0;
u32 cluster_start, clusters[IMG_MAX_CLUSTER];
if (memcmp("bootcfg.txt", filename, 11) == 0) {
/* Search bootcfg.txt */
err = search_boot_cfg_file(&cluster_start, actread, buf);
if (err != 0) {
debug("Search bootcfg.txt failed.\n");
return err;
}
/* Get all clusters for bootcfg.txt file. */
memset(clusters, 0, IMG_MAX_CLUSTER << 2);
err = get_file_cluster_list(cluster_start, clusters, buf);
if (err <= 0) {
debug("Get cluster list failed.\n");
return err;
}
/* Read bootcfg.txt to buffer: buf */
err = read_file_data(clusters, *actread, 0, 0, buf);
if (err != 0) {
debug("Read bootcfg.txt failed.\n");
return err;
}
} else {
/* SPL start offset should alignment with sector size */
byte_off = offset % cur_bpb->sector_size;
/* Change unit from byte to sector, not sect_off is
* the SPL start sector offset in image file
*/
sect_off = offset / cur_bpb->sector_size;
/* Search image file */
err = search_boot_image_file(filename, &cluster_start,
actread, buf);
if (err != 0) {
debug("Search image file failed.\n");
return err;
}
/* If SPL is not located at the first cluster, need to
* update the start cluster.
*/
if (sect_off > cur_bpb->cluster_size) {
err = update_start_cluster(
sect_off / cur_bpb->cluster_size,
&cluster_start, buf);
if (err != 0) {
debug("Update cluster failed.\n");
return err;
}
/* Update start sector offset (now is in cluster) */
sect_off = (sect_off % cur_bpb->cluster_size);
}
/* Get all clusters for SPL image. */
memset(clusters, 0, IMG_MAX_CLUSTER << 2);
err = get_file_cluster_list(cluster_start, clusters, buf);
if (err <= 0) {
debug("Get cluster list failed.\n");
return err;
}
err = read_file_data(clusters, maxsize, sect_off,
byte_off, buf);
if (err != 0) {
debug("Read image data failed.\n");
return err;
}
}
return err;
}
static ulong spl_fit_read(struct spl_load_info *load, ulong file_offset,
ulong size, void *buf)
{
int ret;
char *filename = (char *)load->filename;
#ifdef CONFIG_UPDATE_ARTINCHIP_BOOTCFG
file_offset += (ulong)load->priv;
#endif
ret = aic_fat_read_file(filename, buf, file_offset, size, NULL);
if (ret)
return ret;
return size;
}
static int load_image_file(void *buf, const char *cfgtxt)
{
int err = 0;
struct image_header *header;
ulong offset;
ulong actread, maxsize = 0;
char imgname[IMG_NAME_MAX_SIZ];
err = aic_fat_read_file(cfgtxt, (u8 *)buf, 0, 1024, &actread);
if (err != 0) {
debug("read bootcfg.txt failed!\n");
return err;
}
/* Get boot image file name and SPL start offset (unit is byte) */
err = boot_cfg_parse_file((u8 *)buf, actread, "boot1", imgname,
IMG_NAME_MAX_SIZ, &offset, &maxsize);
if (err <= 0) {
debug("Parse boot cfg file failed.\n");
return err;
}
header = buf;
err = aic_fat_read_file(imgname, header, offset,
sizeof(struct image_header), &actread);
if (err != 0) {
debug("read image header failed!\n");
return err;
}
if (IS_ENABLED(CONFIG_SPL_LOAD_FIT) &&
image_get_magic(header) == FDT_MAGIC) {
struct spl_load_info load;
debug("Found FIT\n");
load.read = spl_fit_read;
load.bl_len = 1;
load.filename = (void *)imgname;
load.priv = NULL;
#ifdef CONFIG_UPDATE_ARTINCHIP_BOOTCFG
load.priv = (void *)offset;
#endif
return spl_load_simple_fit(cur_image, &load, 0, header);
} else {
err = spl_parse_image_header(cur_image, header);
if (err != 0) {
debug("Image header parse failed.\n");
return err;
}
err = aic_fat_read_file(imgname,
(u8 *)(uintptr_t)cur_image->load_addr,
offset, maxsize, &actread);
if (err != 0) {
debug("Read image file failed.\n");
return err;
}
}
return err;
}
static int load_image_from_raw_volume_fat32(u32 start_lba, u8 *header,
const char *filename)
{
int ret;
struct fat32_bpb bpb;
cur_bpb = &bpb;
cur_bpb->sector_size = BPB_SECT_SIZE(header);
cur_bpb->cluster_size = BPB_CLUS_SIZE(header);
cur_bpb->fat_start = start_lba + BPB_RESV_SECT_CNT(header);
cur_bpb->fat_size = BPB_FAT32_SIZE(header);
cur_bpb->data_start = cur_bpb->fat_start + cur_bpb->fat_size
* BPB_FAT_NUM(header);
cur_bpb->root_dirent_cluster = BPB_ROOT_START_CLUS32(header);
debug("FAT32 volume start from LBA 0x%x\n", start_lba);
if (cur_bpb->sector_size != LBA_SIZE) {
debug("Unsupportted LBA size: 0x%X\n", cur_bpb->sector_size);
return -1;
}
ret = load_image_file(header, filename);
if (ret) {
debug("Load image file from FAT32 failed.\n");
return -1;
}
return ret;
}
static int load_image_from_mbr_part_fat32(u8 *header, const char *filename)
{
struct mbr_dpt_entry part;
u32 blkcnt = 0;
int type;
/* Try the first partition only */
memcpy(&part, header + DPT_START, sizeof(struct mbr_dpt_entry));
blkcnt = blk_dread(cur_dev, part.lba_start, 1, header);
if (blkcnt != 1) {
debug("Read partition first sector failed.\n");
return -1;
}
type = check_identifier(header);
if (type != TYPE_RAW_VOLUME_FAT32) {
debug("Partition is not FAT32.\n");
return -1;
}
return load_image_from_raw_volume_fat32(part.lba_start, header,
filename);
}
static int load_image_from_gpt_part_fat32(u8 *header, const char *filename)
{
struct gpt_header gpthead;
struct gpt_entry gptent;
u32 blkcnt = 0;
int type;
/* Read LBA1 for GPT header */
blkcnt = blk_dread(cur_dev, 1, 1, header);
if (blkcnt != 1) {
debug("Read LBA1 failed.\n");
return -1;
}
memcpy(&gpthead, header, sizeof(struct gpt_header));
if (strncmp((char *)gpthead.signature, "EFI PART", 8)) {
debug("LBA1 is not GPT header.\n");
return -1;
}
/* Read the first partition entry, only check the first partition */
blkcnt = blk_dread(cur_dev, gpthead.partition_entry_lba, 1, header);
if (blkcnt != 1) {
debug("Read the first partition entry failed.\n");
return -1;
}
memcpy(&gptent, header, sizeof(struct gpt_entry));
/* Read first LBA of the first GPT partition */
blkcnt = blk_dread(cur_dev, gptent.starting_lba, 1, header);
if (blkcnt != 1) {
debug("Read the first partition entry failed.\n");
return -1;
}
type = check_identifier(header);
if (type != TYPE_RAW_VOLUME_FAT32) {
debug("Partition is not FAT32.\n");
return -1;
}
return load_image_from_raw_volume_fat32(gptent.starting_lba, header,
filename);
}
int aic_spl_load_image_fat(struct spl_image_info *spl_image,
struct blk_desc *block_dev, const char *filename)
{
int ret = 0, type;
u32 blkcnt = 0;
u8 *header;
cur_image = spl_image;
cur_dev = block_dev;
debug("%s:%d\n", __func__, __LINE__);
header = (u8 *)BOOTLOADER_BUF_ADDR;
blkcnt = blk_dread(cur_dev, 0, 1, header);
if (blkcnt != 1) {
debug("IO error, cann't read data from SD.\n");
return -1;
}
type = check_identifier(header);
switch (type) {
case TYPE_RAW_VOLUME_FAT32:
debug("TYPE_RAW_VOLUME_FAT32\n");
/* There is no partition table in SD card, just FAT32 FS */
ret = load_image_from_raw_volume_fat32(0, header, filename);
break;
case TYPE_MBR_PART_FAT32:
debug("TYPE_MBR_PART_FAT32\n");
/* MBR partition exist, try to boot from the first partition */
ret = load_image_from_mbr_part_fat32(header, filename);
break;
case TYPE_GPT_PART_FAT32:
debug("TYPE_GPT_PART_FAT32\n");
/* GPT partition exist, try to boot from the first partition */
ret = load_image_from_gpt_part_fat32(header, filename);
break;
default:
debug("No FAT32.\n");
return -1;
}
return ret;
}