# MS-RTOS 块设备驱动模型

本章将介绍 MS-RTOS 块设备驱动模型。

为了避免对同一类的设备编写多个相似的驱动,驱动在实现上应该要考虑能够同时应用于多个同类设备上。对于某一系列的驱动,在实现时应尽量使用现有 HAL 库代码,以提高可移植性。

# 1. 块设备基础知识

1.1 SPI Flash 硬件接口

SPI 串行 Flash 硬件连接图:

drv_blk_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连接电路:

drv_blk_nand_flash

一片NAND Flash为一个设备,其数据存储分层为:1设备=4 096块;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。

drv_blk_nand_internal

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 左右的上拉电阻。

drv_blk_sd_interface

# 2. 文件系统框架

下图描绘了挂载文件系统和访问文件时的部分 IO 流程,仅为示意图:

drv_blk_fs_system

在 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

(暂无)