ATF的IO抽象层比较复杂,主要是为了优良的移植性而抽象出这么一层,可以方便地移植到支持不同IO方式的设备上。
下面这个目录在/atf/drivers/io/目录下
存储类型
针对各个存储类型,使用枚举类型进行定义
IO_TYPE_SEMIHOSTING 半主机设备,如加载镜像通过文件名读取数据
IO_TYPE_MEMMAP 内存文件设备,如加载镜像从内存中读取数据
IO_TYPE_BLOCK 块设备,以块为单位读写设备,如SD、EMMC等
IO_TYPE_MTD MTD设备,如NAND/NOR FLASH,通常基于扇区进行访问
IO_TYPE_ENCRYPTED 加密设备,TF-A把加密的镜像抽象层加密设备
描述规范:
io_file_spec 用户访问类似文件的实体,比如Flash上的文件系统中的文件
io_uuid_spec 用户访问使用UUID访问的数据
io_block_spec 用于访问块设备
/* File specification - used to refer to data on a device supporting file-like
* entities */
typedef struct io_file_spec {
const char *path;
unsigned int mode;
} io_file_spec_t;
/* UUID specification - used to refer to data accessed using UUIDs (i.e. FIP
* images) */
typedef struct io_uuid_spec {
uuid_t uuid;
} io_uuid_spec_t;
/* Block specification - used to refer to data on a device supporting
* block-like entities */
typedef struct io_block_spec {
size_t offset;
size_t length;
} io_block_spec_t;
在IO实体上的操作
操作 描述
io_open 打开IO实体
io_seek 寻找IO实体中的特定位置
io_size 确定IO实体的长度
io_read 从IO实体中读取数据
io_write 向IO实体中写入数据
io_close 关闭IO实体
函数原型:
/* Synchronous operations */
int io_open(uintptr_t dev_handle, const uintptr_t spec, uintptr_t *handle);
int io_seek(uintptr_t handle, io_seek_mode_t mode, signed long long offset);
int io_size(uintptr_t handle, size_t *length);
int io_read(uintptr_t handle, uintptr_t buffer, size_t length,
size_t *length_read);
int io_write(uintptr_t handle, const uintptr_t buffer, size_t length,
size_t *length_written);
int io_close(uintptr_t handle);
ATF中对IO存储框架的基于上面这些概念,实际抽象来自与以下的数据结构和操作
IO实体 io_entity
表示一个可访问的IO构造,例如一个文件,包含一个指向设备信息结构体的指针和一些设备相关的信息
/* Generic IO entity structure,representing an accessible IO construct on the
* device, such as a file */
typedef struct io_entity {
struct io_dev_info *dev_handle;
uintptr_t info;
} io_entity_t;
设备信息
io_dev_info结构体提供了设备特定的函数和添加驱动程序特定状态的方法。它包含一个指向设备函数结构体的指针和一些设备相关的信息。
/* Device info structure, providing device-specific functions and a means of
* adding driver-specific state */
typedef struct io_dev_info {
const struct io_dev_funcs *funcs;
uintptr_t info;
} io_dev_info_t;
设备连接
io_dev_connector结构体用于创建到设备的连接,它包含一个指向设备驱动程序打开函数的指针,该函数接受一个设备规范参数和一个指向设备信息结构体指针的指针。
/* Structure used to create a connection to a type of device */
typedef struct io_dev_connector {
/* dev_open opens a connection to a particular device driver */
int (*dev_open)(const uintptr_t dev_spec, io_dev_info_t **dev_info);
} io_dev_connector_t;
程序中预定义了几个不同存储类型设备的IO链接器的指针:
设备驱动的操作集
io_dev_funcs结构体包含了一些设备驱动程序的函数指针,用于操作设备,例如打开、读取、写入、关闭等。
/* Structure to hold device driver function pointers */
typedef struct io_dev_funcs {
io_type_t (*type)(void);
int (*open)(io_dev_info_t *dev_info, const uintptr_t spec,
io_entity_t *entity);
int (*seek)(io_entity_t *entity, int mode, signed long long offset);
int (*size)(io_entity_t *entity, size_t *length);
int (*read)(io_entity_t *entity, uintptr_t buffer, size_t length,
size_t *length_read);
int (*write)(io_entity_t *entity, const uintptr_t buffer,
size_t length, size_t *length_written);
int (*close)(io_entity_t *entity);
int (*dev_init)(io_dev_info_t *dev_info, const uintptr_t init_params);
int (*dev_close)(io_dev_info_t *dev_info);
} io_dev_funcs_t;
设备注册
io_register_device函数用于注册一个IO设备,它接受一个指向设备信息结构体的指针作为参数,并返回注册是否成功。
这个函数定义在io_storage.c中
/* Register an IO device */
int io_register_device(const io_dev_info_t *dev_info);
/* Exported API */
/* Register a device driver */
int io_register_device(const io_dev_info_t *dev_info)
{
int result = -ENOMEM;
assert(dev_info != NULL);
if (dev_count < MAX_IO_DEVICES) {
devices[dev_count] = dev_info;
dev_count++;
result = 0;
}
return result;
}
其中devices静态数组的定义也在io_storage中:
/* Array of fixed maximum of registered devices, definable by platform */
static const io_dev_info_t *devices[MAX_IO_DEVICES];
还有其他两个一个数组也是在同一文件中定义:
/* Storage for a fixed maximum number of IO entities, definable by platform */
static io_entity_t entity_pool[MAX_IO_HANDLES];
/* Simple way of tracking used storage - each entry is NULL or a pointer to an
* entity */
static io_entity_t *entity_map[MAX_IO_HANDLES];
一次实际的对IO存储框架调试的经历:
在bl1_platform_setup中会调用各个芯片平台独有的IO初始化
bl1_main -->
bl1_platform_setup -->
plat_tsm_io_setup -->
plat_tsm_io_setup 函数的定义
tsm_boot_io_init会读取bootsel模块,获取外部PCB上的拨码开关的状态,了解到引导方式后,给对应的引导方式下的存储设备做初始化。并且将全局的boot_storage进行赋值,方便后续跟踪。
所有平台都会注册fip,所以register_io_dev_fip函数是一定会调用的,其他方式按boot_storage的不同而调用不同register_io_dev_xxx。
目录/atf/drivers/io/下的每一个文件都代表一种存储类型,每一个文件内都定义了私有的register_io_dev_xxx函数
dev_info_pool数组,或者以一个私有的静态变量来表示
xxx_dev_connector链接器
io_dev_info_t类型的io设备信息(包含了设备相关的操作函数集以及设备信息)
对应IO设备的allocate_dev_info函数
register_io_dev_xxx做的事情:
- 所有的register_io_dev_xxx都会调用io_register_device函数,将dev_info_pool数组的第一个元素的地址注册到devices全局数组中去(这时第一个元素还没有初始化)
- 将register_io_dev_xxx传入的预定义的dev_con指针指向对应的存储类型的链接器,比如说对于fip: fip_dev_con ->fip_dev_connector
- 返回注册结果
注册好对应的IO设备后,执行io_dev_open,打开这个设备,同样的,fip是必定打开的,其他的设备根据boot_storage的不同调用不同的register_io_dev_xxx后会随后调用io_dev_open,传入不同的xxx_dev_connector
io_dev_open会调用xxx_dev_connector的dev_open函数,从而调用到不同设备的open操作。
fip设备的open操作作为示例
static int fip_dev_open(const uintptr_t dev_spec,
io_dev_info_t **dev_info)
每种设备的open操作要做的事情也是类似的:
- 调用它自己的allocate_dev_info(&info)(因为这个函数时static修饰的,只能访问到文件内的)
- allocate_dev_info会将本设备对应的_dev_funcs即操作函数集注册到本设备的dev_info_pool对应的index上去,然后将index++,还记得register_io_dev_xxx做过的事情吗?将dev_info_pool数组的第一个元素的地址注册到devices全局数组中去,那个时候还没初始化,现在对其做填充了。(dev_info_pool[0].funcs = &fip_dev_funcs)
- allocate_dev_info还会将dev_info_pool对应的index位置上的info成员,指向本设备的state_pool[index]的位置,也只是一个指向的更改,并不会做其上元素的初始化。
- *info = &dev_info_pool[0],传出了设备信息池的第一个元素,dev_info_pool类型
- xxx_dev_count++
到此做一个总结:
rigister_io_dev_xxx(&xxx_dev_con);
*xxx_dev_con = xxx_dev_connector (in /atf/drivers/io/io_xxx.c中定义);
device[index] = &dev_info_pool[0];
index++;
io_dev_open(xxx_dev_con, spec, &xxx_dev_handle);
io_storage_dev_open(xxx_dev_con, spec, &xxx_dev_handle);
xxx_dev_con->dev_open(spec, &xxx_dev_handle);
allocate_dev_info(&info);
dev_info_pool[0].func = &xxx_dev_funcs;
dev_info_pool[0].info= &state_poo[index];
*info = &dev_info_pool[0];
*xxx_dev_handle = info; //重点在这里xxx_dev_handle 得到了初始化
最终做了两大件事情:
- *xxx_dev_con = xxx_dev_connector
- *xxx_dev_handle =&dev_info_pool[0]
点击图片放大看看:
void plat_tsm_io_setup(void)
{
int io_result;
tsm_boot_io_init();
io_result = register_io_dev_fip(&fip_dev_con);
assert(io_result == 0);
io_result = io_dev_open(fip_dev_con, (uintptr_t)NULL,
&fip_dev_handle);
assert(io_result == 0);
switch (boot_storage) {
case BOOT_STORAGE_TYPE_MEMMAP:
io_result = register_io_dev_memmap(&memmap_dev_con);
assert(io_result == 0);
io_result = io_dev_open(memmap_dev_con, (uintptr_t)NULL,
&memmap_dev_handle);
assert(io_result == 0);
break;
case BOOT_STORAGE_TYPE_BLOCK:
io_result = register_io_dev_block(&block_dev_con);
assert(io_result == 0);
io_result = io_dev_open(block_dev_con, (uintptr_t)&mmc_block_dev_spec,
&block_dev_handle);
assert(io_result == 0);
break;
case BOOT_STORAGE_TYPE_MTD:
io_result = register_io_dev_mtd(&mtd_dev_con);
assert(io_result == 0);
io_result = io_dev_open(mtd_dev_con, (uintptr_t)p_mtd_dev_spec,
&mtd_dev_handle);
assert(io_result == 0);
break;
default:
assert(0);
break;
}
/* Ignore improbable errors in release builds */
(void)io_result;
}
总览:
镜像加载
tsm定义的IO策略
每个索引,如FIP_IMAGE_ID,BL2_IMAGE_ID都是宏定义,从上往下分别是1,2,3…
struct plat_io_policy {
uintptr_t *dev_handle;
uintptr_t image_spec;
int (*check)(const uintptr_t spec);
};
/* Block specification - used to refer to data on a device supporting block-like entities*/
static const io_block_spec_t fip_raw_nand_block_spec = {
.offset = 0,
.length = PLAT_TSM_FIP_OSPI_MAX_SIZE
};
static const io_block_spec_t fip_spi_nand_block_spec = {
.offset = 0,
.length = PLAT_TSM_FIP_OSPI_MAX_SIZE
};
#endif
static const io_block_spec_t fip_memmap_spec = {
.offset = PLAT_TSM_FIP_OSPI_BASE,
.length = PLAT_TSM_FIP_OSPI_MAX_SIZE
};
static const io_block_spec_t fip_memmap_uart_spec = {
.offset = YMODEM_SRAM_IMAGE_ADDR,
.length = YMODEM_SRAM_IMAGE_MAX_SIZE
};
static const io_block_spec_t fip_memmap_uart_spec_2 = {
.offset = YMODEM_DDR_IMAGE_ADDR,
.length = YMODEM_DDR_IMAGE_MAX_SIZE
};
/* UUID SPEC*/
static const io_uuid_spec_t bl2_uuid_spec = {
.uuid = UUID_TRUSTED_BOOT_FIRMWARE_BL2,
};
/* policies */
static struct plat_io_policy policies[] = {
[FIP_IMAGE_ID] = {
&memmap_dev_handle,
(uintptr_t)&fip_memmap_spec,
open_memmap /* modify for diffirent boot select */
},
[BL2_IMAGE_ID] = {
&fip_dev_handle,
(uintptr_t)&bl2_uuid_spec, //image_id = BL2_IMAGE_ID back here
open_fip
},
#endif /* TRUSTED_BOARD_BOOT */
};
bl_common.c定义的load_image加载镜像函数,其通过标准的IO接口加载镜像
load_image -->
plat_get_image_source -->
io_open -->
io_size -->
io_read -->
io_close-->
io_dev_close-->
load_image函数总览:
static int load_image(unsigned int image_id, image_info_t *image_data)
{
uintptr_t dev_handle;
uintptr_t image_handle;
uintptr_t image_spec;
uintptr_t image_base;
size_t image_size;
size_t bytes_read = 0;
int io_result;
assert(image_data != NULL);
assert(image_data->h.version >= VERSION_2);
image_base = image_data->image_base;
/* Obtain a reference to the image by querying the platform layer */
io_result = plat_get_image_source(image_id, &dev_handle, &image_spec);
if (io_result != 0) {
WARN("Failed to obtain reference to image id=%u (%i)\n",
image_id, io_result);
return io_result;
}
/* Attempt to access the image */
io_result = io_open(dev_handle, image_spec, &image_handle);
if (io_result != 0) {
WARN("Failed to access image id=%u (%i)\n",
image_id, io_result);
return io_result;
}
INFO("Loading image id=%u at address 0x%lx\n", image_id, image_base);
/* Find the size of the image */
io_result = io_size(image_handle, &image_size);
if ((io_result != 0) || (image_size == 0U)) {
WARN("Failed to determine the size of the image id=%u (%i)\n",
image_id, io_result);
goto exit;
}
/* Check that the image size to load is within limit */
if (image_size > image_data->image_max_size) {
WARN("Image id=%u size out of bounds\n", image_id);
io_result = -EFBIG;
goto exit;
}
/*
* image_data->image_max_size is a uint32_t so image_size will always
* fit in image_data->image_size.
*/
image_data->image_size = (uint32_t)image_size;
/* We have enough space so load the image now */
/* TODO: Consider whether to try to recover/retry a partially successful read */
io_result = io_read(image_handle, image_base, image_size, &bytes_read);
if ((io_result != 0) || (bytes_read < image_size)) {
WARN("Failed to load image id=%u (%i)\n", image_id, io_result);
goto exit;
}
INFO("Image id=%u loaded: 0x%lx - 0x%lx\n", image_id, image_base,
(uintptr_t)(image_base + image_size));
exit:
(void)io_close(image_handle);
/* Ignore improbable/unrecoverable error in 'close' */
/* TODO: Consider maintaining open device connection from this bootloader stage */
(void)io_dev_close(dev_handle);
/* Ignore improbable/unrecoverable error in 'dev_close' */
return io_result;
}
plat_get_image_source总览:
获取镜像源,假设传入的image_id = BL2_IMAGE_ID,从policies查找该镜像的IO策略,会调用open_fip
/*
* Return an IO device handle and specification which can be used to access
* an image. Use this to enforce platform load policy
*/
int plat_get_image_source(unsigned int image_id, uintptr_t *dev_handle,
uintptr_t *image_spec)
{
int result;
const struct plat_io_policy *policy;
assert(image_id < ARRAY_SIZE(policies));
policy = &policies[image_id];
result = policy->check(policy->image_spec); //--> open_fip
if (result == 0) {
*image_spec = policy->image_spec;
*dev_handle = *(policy->dev_handle); //<-- fip_dev_handler
}
return result;
}
查看open_fip
这是个platform about函数:
这个平台实现不同
static int open_fip(const uintptr_t spec)
{
int result;
uintptr_t local_image_handle;
/* See if a Firmware Image Package is available */
result = io_dev_init(fip_dev_handle, (uintptr_t)FIP_IMAGE_ID);
if (result == 0) {
result = io_open(fip_dev_handle, spec, &local_image_handle);
if (result == 0) {
VERBOSE("Using FIP\n");
io_close(local_image_handle);
}
}
return result;
}
io_dev_init -->
dev->funcs->dev_init(dev, init_params); (.dev_init = fip_dev_init,) -->
fip_dev_init0 -->
plat_get_image_source (image_id = FIP_IMAGE_ID
&backend_dev_handle,
&backend_image_spec) 获取镜像源 -->
open_memmap -->
io_dev_init初始化 -->
io_open -->
allocate_entity -->
memmap_block_open -->
io_close -->
backend_dev_handle = memmap_dev_handle;
backend_image_spec = fip_block_spec
这两个参数句柄和描述即是FIP如何从memmap取数据的操作信息集合
io_open 打开 -->
io_read(backend_handle, (uintptr_t)&header, sizeof(header),&bytes_read); 读取头 -->
if(!is_valid_header)校验头是否正确