块设备驱动模型
本章将介绍 MS-RTOS 块设备驱动模型。
为了避免对同一类的设备编写多个相似的驱动,驱动在实现上应该要考虑能够同时应用于多个同类设备上。对于某一系列的驱动,在实现时应尽量使用现有 HAL 库代码,以提高可移植性。
1. 块设备基础知识
1.1 SPI Flash 硬件接口
SPI 串行 Flash 硬件连接图:
SPI Flash 芯片自定义了很多指令,通过控制 SPI 总线向 Flash 芯片发送指令,Flash 芯片收到后就会执行相应的操作。读 ID 指令 JEDEC ID 可以获取这两个编号,该指令编码为 9F h
,紧跟指令编码的三个字节分别为 Flash 芯片输出的(M7 - M0)、(ID15 - ID8)及(ID7 - ID0),分别表示:生产厂商、存储器类型、存储器容量。
1.2 NAND Flash 硬件接口
要访问 NAND Flash 中的数据,必须通过 NAND Flash 控制器发送命令才能完成。NAND Flash 控制器在其专用寄存器区(SFR)地址空间中映射有属于自己的特殊功能寄存器,就是通过将 NAND Flash 芯片的内设命令写到其特殊功能寄存器中,从而实现对 NAND Flash 芯片读、检验和编程控制。特殊功能寄存器有:NFCONF、NFCMD、NFADDR、NFDATA、NFSTAT、NFECC。NAND Flash 与 S3C2410 连接电路:
一片 NAND Flash 为一个设备,其数据存储分层为:1 设备 = 4096 块;1 块 = 32 页;1 页 = 528 字节 = 数据块大小(512 字节)+ OOB 块大小(16 字节)。在每一页中,最后 16 字节(又称 OOB,Out of Band)用于 NAND Flash 命令执行完后设置状态用,剩余 512 字节又分为前半部分和后半部分。可以通过 NAND Flash 命令 00h/01h/50h 分别对前半部、后半部、OOB 进行定位,通过 NAND Flash 内置的指针指向各自的首地址。
存储操作特点有: 擦除操作的最小单位是块;NAND Flash 芯片每一位只能从 1 变为 0,而不能从 0 变为 1,所以在对其进行写入操作之前一定要将相应块擦除(擦除即是将相应块的位全部变为 1);OOB 部分的第 6 字节(即 517 字节)标志是否是坏块,值为 FF 时不是坏块,否则为坏块。除 OOB 第 6 字节外,通常至少把 OOB 的前 3 字节用来存放 NAND Flash 硬件 ECC 码。Nand Flash Layout:Chip - Plane - Block - Page - Byte。
1.3 SD Card 硬件接口
SD 卡(安全数码卡),它是在 MMC 的基础上发展而来, 是一种基于半导体快闪记忆器的新一代记忆设备。按容量分类,可以将 SD 卡分为 3 类: SD 卡、SDHC 卡、SDXC 卡。SD 卡(SDSC):0 - 2G,SDHC卡:2 - 32G,SDXC 卡:32G - 2T。SD 卡一般支持 2 种操作模式:
- SD 卡模式(通过 SDIO 通信):允许 4 线的高速数据传输,只能使用 3.3V 的 IO 电平,所以, MCU 一定要能够支持 3.3V 的 IO 端口输出。
- SPI 模式:同 SD 卡模式相比就是丧失了速度,在 SPI 模式下, CS/MOSI/MISO/CLK 都需要加 10 ~ 100K 左右的上拉电阻。
2. 文件系统框架
下图描绘了挂载文件系统和访问文件时的部分 IO 流程,仅为示意图:
在 MS-RTOS 上适配新的文件系统时,需要实现/填充 ms_io_fs_t
结构体,即相当于实例化一个文件系统对象,然后调用 ms_io_fs_register(&ms_io_xxxfs)
将文件系统注册到 MS-RTOS 中。
3. 块设备驱动程序
块设备是 I/O 设备中的一类,将信息存储在固定大小的块中,每个块都有自己的地址。数据块的大小通常在 512 字节到 32768 字节之间。块设备的基本特征是每个块都能独立于其他块而读写。磁盘是最常见的块设备。
块设备驱动需要实现 ms_io_driver_ops_t
的以下三个成员函数:
typedef struct ms_io_driver_ops {
// 其它成员变量
int (*ioctl)(ms_ptr_t ctx, ms_io_file_t *file, int cmd, ms_ptr_t arg);
ms_ssize_t (*readblk )(ms_ptr_t ctx, ms_io_file_t *file,
ms_size_t blk_no, ms_size_t blk_cnt, ms_ptr_t buf);
ms_ssize_t (*writeblk)(ms_ptr_t ctx, ms_io_file_t *file,
ms_size_t blk_no, ms_size_t blk_cnt, ms_const_ptr_t buf);
} const ms_io_driver_ops_t;
块设备驱动需要支持的 ioctl 命令:
命令 | 描述 | 参数 |
---|---|---|
MS_IO_BLKDEV_CMD_INIT | 初始化 | 无 |
MS_IO_BLKDEV_CMD_STATUS | 获得设备状态 | ms_uint32_t 指针 |
MS_IO_BLKDEV_CMD_SYNC | 同步 | 无 |
MS_IO_BLKDEV_CMD_SECT_NR | 获得扇区数 | ms_uint32_t 指针 |
MS_IO_BLKDEV_CMD_SECT_SZ | 获得扇区大小 | ms_uint16_t 指针 |
MS_IO_BLKDEV_CMD_BLK_SZ | 获得块大小 | ms_uint32_t 指针 |
MS_IO_BLKDEV_CMD_TRIM | TRIM | 无 |
设备状态可以使用以下的宏定义:
设备状态 | 描述 |
---|---|
MS_IO_BLKDEV_STA_OK | OK |
MS_IO_BLKDEV_STA_NOINIT | 未初始化 |
MS_IO_BLKDEV_STA_NODISK | 没有磁盘 |
MS_IO_BLKDEV_STA_PROTECT | 磁盘写保护 |
块设备驱动示例,仅作为参考:
/*
* Copyright (c) 2015-2020 ACOINFO, Inc.
*/
#define __MS_IO
#include "config.h"
#include "ms_kern.h"
#include "ms_io_core.h"
#include "ms_fatfs.h"
#include "stm32f7xx.h"
#include "stm32746g_discovery.h"
#include "stm32746g_discovery_sd.h"
/**
* @brief stm32f7 sd driver.
*/
#define SD_DEFAULT_BLOCK_SIZE 512U
#define SD_TIMEOUT (2U * 1000U)
#define SD_DMA_CACHE_MAINTENANCE_EN 1U
static ms_handle_t stm32_sd_sync_semid;
extern SD_HandleTypeDef uSdHandle;
/*
* Control device
*/
static int stm32_sd_ioctl(ms_ptr_t ctx, ms_io_file_t *file, int cmd, void *arg)
{
BSP_SD_CardInfo CardInfo;
int ret;
switch (cmd) {
case MS_IO_BLKDEV_CMD_INIT:
if (BSP_SD_Init() == MSD_OK) {
ret = 0;
} else {
ret = -1;
}
break;
case MS_IO_BLKDEV_CMD_STATUS:
if (BSP_SD_IsDetected()) {
*(ms_uint32_t *)arg = MS_IO_BLKDEV_STA_OK;
} else {
*(ms_uint32_t *)arg = MS_IO_BLKDEV_STA_NOINIT;
}
ret = 0;
break;
case MS_IO_BLKDEV_CMD_SYNC:
ret = 0;
break;
case MS_IO_BLKDEV_CMD_SECT_NR:
BSP_SD_GetCardInfo(&CardInfo);
*(ms_uint32_t *)arg = CardInfo.LogBlockNbr;
ret = 0;
break;
case MS_IO_BLKDEV_CMD_SECT_SZ:
BSP_SD_GetCardInfo(&CardInfo);
*(ms_uint16_t *)arg = CardInfo.LogBlockSize;
ret = 0;
break;
case MS_IO_BLKDEV_CMD_BLK_SZ:
BSP_SD_GetCardInfo(&CardInfo);
*(ms_uint32_t *)arg = CardInfo.LogBlockSize / SD_DEFAULT_BLOCK_SIZE;
ret = 0;
break;
case MS_IO_BLKDEV_CMD_TRIM:
ret = 0;
break;
default:
ms_thread_set_errno(EINVAL);
ret = -1;
break;
}
return ret;
}
/*
* Read block of device
*/
ms_ssize_t stm32_sd_readblk(ms_ptr_t ctx, ms_io_file_t *file, ms_size_t blk_no, ms_size_t blk_cnt, ms_ptr_t buf)
{
ms_ssize_t len;
#if (SD_DMA_CACHE_MAINTENANCE_EN == 1)
ms_uint32_t aligned_addr;
/*
* the SCB_CleanDCache_by_Addr() requires a 32-Byte aligned address
* adjust the address and the D-Cache size to clean accordingly.
*/
aligned_addr = (ms_uint32_t)buf & ~0x1fU;
SCB_CleanInvalidateDCache_by_Addr((ms_uint32_t *)aligned_addr,
blk_cnt * SD_DEFAULT_BLOCK_SIZE + ((ms_uint32_t)buf - aligned_addr));
#endif
if (BSP_SD_ReadBlocks_DMA(buf, blk_no, blk_cnt) == MSD_OK) {
if (ms_semc_wait(stm32_sd_sync_semid, SD_TIMEOUT) == MS_ERR_NONE) {
while (BSP_SD_GetCardState() != SD_TRANSFER_OK) {
ms_thread_sleep(1);
}
len = blk_cnt * SD_DEFAULT_BLOCK_SIZE;
} else {
len = -1;
}
} else {
len = -1;
}
return len;
}
/*
* Write block of device
*/
ms_ssize_t stm32_sd_writeblk(ms_ptr_t ctx, ms_io_file_t *file, ms_size_t blk_no, ms_size_t blk_cnt, ms_const_ptr_t buf)
{
ms_ssize_t len;
#if (SD_DMA_CACHE_MAINTENANCE_EN == 1)
ms_uint32_t aligned_addr;
/*
* the SCB_CleanDCache_by_Addr() requires a 32-Byte aligned address
* adjust the address and the D-Cache size to clean accordingly.
*/
aligned_addr = (ms_uint32_t)buf & ~0x1fU;
SCB_CleanDCache_by_Addr((ms_uint32_t *)aligned_addr,
blk_cnt * SD_DEFAULT_BLOCK_SIZE + ((ms_uint32_t)buf - aligned_addr));
#endif
if (BSP_SD_WriteBlocks_DMA(buf, blk_no, blk_cnt) == MSD_OK) {
if (ms_semc_wait(stm32_sd_sync_semid, SD_TIMEOUT) == MS_ERR_NONE) {
while (BSP_SD_GetCardState() != SD_TRANSFER_OK) {
ms_thread_sleep(1);
}
len = blk_cnt * SD_DEFAULT_BLOCK_SIZE;
} else {
len = -1;
}
} else {
len = -1;
}
return len;
}
/**
* @brief Tx Transfer completed callbacks
* @param hsd: SD handle
* @retval None
*/
void BSP_SD_WriteCpltCallback(void)
{
ms_semc_post(stm32_sd_sync_semid);
}
/**
* @brief Rx Transfer completed callbacks
* @param hsd: SD handle
* @retval None
*/
void BSP_SD_ReadCpltCallback(void)
{
ms_semc_post(stm32_sd_sync_semid);
}
/**
* @brief This function handles SDMMC1 global interrupt.
*/
void SDMMC1_IRQHandler(void)
{
(void)ms_int_enter();
HAL_SD_IRQHandler(&uSdHandle);
(void)ms_int_exit();
}
/**
* @brief This function handles DMA2 stream3 global interrupt.
*/
void DMA2_Stream3_IRQHandler(void)
{
(void)ms_int_enter();
HAL_DMA_IRQHandler(uSdHandle.hdmarx);
(void)ms_int_exit();
}
/**
* @brief This function handles DMA2 stream6 global interrupt.
*/
void DMA2_Stream6_IRQHandler(void)
{
(void)ms_int_enter();
HAL_DMA_IRQHandler(uSdHandle.hdmatx);
(void)ms_int_exit();
}
/*
* Device operating function set
*/
static const ms_io_driver_ops_t stm32_sd_drv_ops = {
.type = MS_IO_DRV_TYPE_BLK,
.ioctl = stm32_sd_ioctl,
.readblk = stm32_sd_readblk,
.writeblk = stm32_sd_writeblk,
};
/*
* Device driver
*/
static ms_io_driver_t stm32_sd_drv = {
.nnode = {
.name = "stm32_sd",
},
.ops = &stm32_sd_drv_ops,
};
/*
* Register sdcard block device driver
*/
ms_err_t stm32_sd_drv_register(void)
{
return ms_io_driver_register(&stm32_sd_drv);
}
/*
* Create sdcard block device file
*/
ms_err_t stm32_sd_dev_create(void)
{
static ms_io_device_t sd_blk_dev[4];
while (!BSP_SD_IsDetected()) {
ms_thread_sleep_s(1);
}
ms_io_device_register(&sd_blk_dev[0], "/dev/sd_blk0", "stm32_sd", MS_NULL);
ms_io_device_register(&sd_blk_dev[1], "/dev/sd_blk1", "stm32_sd", MS_NULL);
ms_io_device_register(&sd_blk_dev[2], "/dev/sd_blk2", "stm32_sd", MS_NULL);
ms_io_device_register(&sd_blk_dev[3], "/dev/sd_blk3", "stm32_sd", MS_NULL);
ms_semc_create("sd_semc", 0, UINT32_MAX, MS_WAIT_TYPE_PRIO, &stm32_sd_sync_semid);
return MS_ERR_NONE;
}
/*
* Mount sdcard block device
*/
ms_err_t stm32_sd_dev_mount(void)
{
static ms_io_mnt_t sd_mnt[4];
ms_io_mount(&sd_mnt[0], "/sd0", "/dev/sd_blk0", MS_FATFS_NAME, (ms_ptr_t)1);
ms_io_mount(&sd_mnt[1], "/sd1", "/dev/sd_blk1", MS_FATFS_NAME, (ms_ptr_t)2);
ms_io_mount(&sd_mnt[2], "/sd2", "/dev/sd_blk2", MS_FATFS_NAME, (ms_ptr_t)3);
ms_io_mount(&sd_mnt[3], "/sd3", "/dev/sd_blk3", MS_FATFS_NAME, (ms_ptr_t)4);
return MS_ERR_NONE;
}
BSP 需要调用 stm32_sd_drv_register
函数完成 stm32_sd
驱动的注册,然后调用 stm32_sd_dev_create
函数完成多个块设备(一个磁盘分区一个块设备)的创建,最后调用 stm32_sd_dev_mount
函数完成多个磁盘分区的挂载。
4. 文件读写应用程序
4.1 读写文件内容
#include <ms_rtos.h>
#include <string.h>
#include "test/include/greatest.h"
#define TEST_FILE_PATH1 "/nor/1.txt"
#define TEST_FILE_PATH2 "/nor/2.txt"
#define TEST_FILE_PATH3 "/nor/3.txt"
#define TEST_DIR_PATH "/nor/workspace"
#define TEST_LINK_FILE "/nor/link_file"
#define TEST_BUFFER_SIZE (64)
#define TEST_FILE_SIZE (4*1024)
int test_file_read(void)
{
int fd;
ms_ssize_t ret;
ms_uint8_t read_buf[10];
ms_uint8_t write_buf[10] = {11, 12, 13, 14, 15, 16, 17, 18, 19, 20};
fd = ms_io_open(TEST_FILE_PATH2, O_CREAT | O_RDWR, 0666);
if (fd < 0) {
ms_printf("[error]: open file %s failed!\n", TEST_FILE_PATH2);
return (-1);
}
ret = ms_io_write(fd, write_buf, sizeof(write_buf));
if (ret != 10) {
ms_printf("[error]: write failed! errno = %d\n", errno);
ms_io_close(fd);
return (-1);
}
ret = ms_io_lseek(fd, 0, SEEK_SET);
if (ret != 0) {
ms_printf("[error]: lseek failed! errno = %d\n", errno);
ms_io_close(fd);
return (-1);
}
ret = ms_io_read(fd, read_buf, sizeof(read_buf));
if (ret != 10) {
ms_printf("[error]: read failed! errno = %d\n", errno);
ms_io_close(fd);
return (-1);
}
if (memcmp(write_buf, read_buf, sizeof(read_buf)) != 0) {
ms_io_close(fd);
return (-1);
}
ms_io_close(fd);
return (0);
}
4.2 获取目录信息
#include <ms_rtos.h>
#include <string.h>
#include "test/include/greatest.h"
#define TEST_DIR_PATH "/nor/test_dir"
#define TEST_DIR_MODE (0666)
int test_file_mk_dir(void)
{
int ret;
MS_DIR *dir;
ms_dirent_t dirent;
ms_dirent_t *result; //point to dirent
long loc;
ret = ms_io_mkdir(TEST_DIR_PATH, TEST_DIR_MODE);
if (ret < 0) {
ms_printf("[error]: ms_io_mkdir failed! errno = %d\n", errno);
return (-1);
}
ret = ms_io_access(TEST_DIR_PATH, TEST_DIR_MODE);
if (ret < 0) {
ms_printf("[error]: ms_io_access failed! errno = %d\n", errno);
return (-1);
}
dir = ms_io_opendir(TEST_DIR_PATH);
if (dir == NULL) {
ms_printf("[error]: ms_io_opendir failed! errno = %d\n", errno);
return (-1);
}
ret = ms_io_readdir_r(dir, &dirent, &result);
if (ret < 0) {
ms_printf("[error]: ms_io_readdir_r failed! errno = %d\n", errno);
return (-1);
}
ms_printf("dirent name: %s\n", dirent.d_name);
ret = ms_io_rewinddir(dir);
if (ret < 0) {
ms_printf("[error]: ms_io_rewinddir failed! errno = %d\n", errno);
return (-1);
}
loc = ms_io_telldir(dir);
if (loc < 0) {
ms_printf("[error]: ms_io_telldir failed! errno = %d\n", errno);
return (-1);
}
ret = ms_io_seekdir(dir, loc);
if (ret < 0) {
ms_printf("[error]: ms_io_seekdir failed! errno = %d\n", errno);
return (-1);
}
ret = ms_io_closedir(dir);
if (ret < 0) {
ms_printf("[error]: ms_io_closedir failed! errno = %d\n", errno);
return (-1);
}
ret = ms_io_rmdir(TEST_DIR_PATH);
if (ret < 0) {
ms_printf("[error]: ms_io_rmdir failed! errno = %d\n", errno);
return (-1);
}
return (0);
}
附录(Appendix)
1. Reference
https://www.cnblogs.com/amanlikethis/p/3757876.html
https://www.cnblogs.com/amanlikethis/p/3757876.html
https://segmentfault.com/a/1190000015995506
2. FAQ
(暂无)