编写裸机驱动,一般都比较随意。这通常导致代码不统一,移植性差。
本例以SPI FLASH来分析一种优秀的裸机驱动编写模板。
首先定义一个设备类。
/*
*flash device class
*/
typedef struct flash_dev_s{
const struct flash_dev_funs *funs; /* Function pointers */
uint32 flags; /* Devicecharacteristics */
hsaddr_t start; /* First address*/
hsaddr_t end; /* Last address */
uint32 num_block_infos;/* Number of entries */
const flash_block_info_t *block_info; /* Info about oneblock size */
const void *priv; /* Devices privatedata */
} flash_dev_t;
/* Structure of pointers to functions inthe device driver */
Struct flash_dev_funs {
sys_error_t (*flash_init) (struct flash_dev_s *dev) REENTRANT;
sys_error_t (*flash_erase_block) (struct flash_dev_s *dev,
hsaddr_tblock_base) REENTRANT;
sys_error_t (*flash_program) (struct flash_dev_s *dev,
hsaddr_t base,
const void*ram_base, size_t len) REENTRANT;
sys_error_t (*flash_read) (struct flash_dev_s *dev,
const hsaddr_tbase,
void* ram_base,size_t len) REENTRANT;
};
同一种设备类型可能有不同厂家和不同参数的差异,但它的大体功能属性是一致的。我们首先把一致的属性剥离出来,构建一个设备类。类似C++的类,包含数据和操作方法。一个设备驱动模板的雏形就出来了。
我们来看一个设备实例来看它的方便之处。
struct flash_dev_funs mx25l_funs = {
mspi_init,
m25lxx_erase_block,
m25lxx_program,
m25lxx_read,
NULL
};
flash_dev_t n25q256_dev = {
&mx25l_funs,
FLAG_FSR_POLL,
CFG_FLASH_START_ADDRESS,
0,
1,
&n25q256_info,
NULL };
flash_dev_t w25q16_dev = {
&mx25l_funs,
0,
CFG_FLASH_START_ADDRESS,
0,
1,
&w25q16cv_info,
NULL };
/*end*/
flash_dev_t w25q64cv_dev = {
&mx25l_funs,
0,
CFG_FLASH_START_ADDRESS,
0,
1,
&w25q64cv_info,
NULL };
这里用一个通用设备结构体定义和初始化了多个不同的spi设备实例。我们看到每个设备的不同之处在它们的功能函数和设备属性。当我们需要新增一个设备时,只需定义一个通用设备类,再去添加它自己的操作方法和数据属性就行了。实现这些之后,对外部调用来说就简单了,直接操作这个设备类的统一接口,上层代码不需要做任何改动,提高了驱动移植性。
我们还可以做进一步的函数封装。
例如:把设备操作完全屏蔽,上层代码不需要管是什么设备。
sys_error_t
flash_read(hsaddr_t flash_base, void *ram_base, size_t len)
{
dev = board_get_flash_dev();
if (!dev) {
return SYS_ERR;
}
rv = (*dev->funs->flash_read)(dev, addr, ram, this_read);
......
}