写点什么

Linux 下驱动开发 _ 块设备驱动开发 (硬件上采用 SD 卡 +SPI 协议)

作者:DS小龙哥
  • 2022-10-19
    重庆
  • 本文字数:10141 字

    阅读完需:约 1 分钟

一、前言

块设备主要为存储设备设计的框架。 在前面章节 里介绍了块设备驱动编写思路,并且利用内存模拟了硬件存储,完成了块设备驱动开发测试。这一篇文章将采用 SD 卡作为存储硬件,利用 SPI 协议与 SD 卡通信,完成块设备驱动开发测试。 SD 卡可以更加形象的表示块设备开发过程,明白硬件如何交互,完成数据读写。


SD 卡本身支持 SPI 协议、SDIO 协议,一般能跑 Linux 系统的 CPU 都在硬件上支持 SPI 协议、SDIO 协议,如果要提高读写速度,肯定是采用 SDIO 协议最合适。这篇文章主要是介绍 SD 卡+块设备框架的驱动开发思路,代码里选择了 SPI 协议来进行通信,读写 SD 卡的速度比较慢(与 SDIO 比数据线都少了几条)。  SPI 协议比较简单,学习过单片机的都比较熟悉,并且 SPI 协议还可以自己模拟时序,不一定要硬件上支持的,在难度上就降低了不少。


块设备驱动的思路是: 处理应用层的请求。


我们知道操块设备,都是通过文件系统读写访问比如:(U 盘、SD 卡、磁盘)这些设备。


读写操作块设备的常用命令:


dd、fdisk、mount
复制代码


比如以下的文件操作代码:


mount /dev/sdb /mntfopen("/mnt/123.c","wb");fwrite("1233445656");fclose();
复制代码


代码执行之后的请求反应到驱动层的接口就是:  读扇区(从哪里读,读多少扇区)、写扇区(从哪里写、写多少扇区)。


对于应用层而言,对块设备并发读写是经常的事情,比如:在同一个磁盘上,我们可以同时拷贝文件的同时,还可以删除其他文件,读写某个文件。 那么驱动层如何处理呢? 因为站在 SD 卡的角度,每一刻 SPI 通信肯定是只能处理一个指令,不能并发的。 所以在块设备的驱动层有一个读写 IO 队列,应用层的请求都会插入到队里里,然后驱动层在一个一个的进行处理。 这个队列在内核里也会涉及到算法排序,可以根据优先级进行排序的。比如:音乐文件读写要实时,不然播放器播放音乐卡顿。这些都可以进行排序,把实时性高的请求可以放在前面处理。



这是安装块设备之后,分区、格式化文件系统,挂载的测试流程: 通过这一系列操作,可以验证驱动是否正常。


[root@wbyq code]# insmod block_dev.ko [   20.070000]  tiny4412_block_dev: unknown partition table[   20.075000] 块设备驱动安装成功.[root@wbyq code]# [root@wbyq code]# fdisk /dev/[   34.120000] nf_conntrack: automatic helper assignment is deprecated and it will be removed soon. Use the iptables CT target to attach helpers instead.[root@wbyq code]# fdisk /dev/tiny4412_block_devDevice contains neither a valid DOS partition table, nor Sun, SGI, OSF or GPT disklabelBuilding a new DOS disklabel. Changes will remain in memory only,until you decide to write them. After that the previous contentwon't be recoverable.
Command (m for help): nCommand action e extended p primary partition (1-4)pPartition number (1-4): 1First cylinder (1-1024, default 1): Using default value 1Last cylinder or +size or +sizeM or +sizeK (1-1024, default 1024): Using default value 1024
Command (m for help): wThe partition table has been altered.Calling ioctl() to re-read partition table[ 46.200000] tiny4412_block_dev: tiny4412_block_dev1[root@wbyq code]# mkfs.mkfs.ext2 mkfs.minix mkfs.vfat[root@wbyq code]# mkfs.vfat /dev/tiny4412_block_dev1 [root@wbyq code]# mount /dev/tiny4412_block_dev1 /mnt/[ 113.755000] FAT-fs (tiny4412_block_dev1): utf8 is not a recommended IO charset for FAT filesystems, filesystem will be case sensitive![root@wbyq code]# cd /mnt/[root@wbyq mnt]# ls[root@wbyq mnt]# touch 1.c 2.c 3.c [root@wbyq mnt]# ls1.c 2.c 3.c[root@wbyq mnt]# cd ..[root@wbyq ]# umount /mnt/
复制代码

二、SD 卡介绍

这是 SD 卡的引脚功能说明:



SD 卡支持两种总线方式:SD 方式与 SPI 方式。其中 SD 方式采用 6 线制,使用 CLK、CMD、DAT0~DAT3 进行数据通信。而 SPI 方式采用 4 线制,使用 CS、CLK、DataIn、DataOut 进行数据通信。SD 方式时的数据传输速度与 SPI 方式要快,采用单片机对 SD 卡进行读写时一般都采用 SPI 模式。采用不同的初始化方式可以使 SD 卡工作于 SD 方式或 SPI 方式。


SD 卡协会官网: https://www.sdcard.org/




SD 卡底层 SPI 时序:




从图上得知:  SD 卡在 SPI 模式下读写数据:   时钟的下降沿写数据、时钟的上升沿读数据。


注意: SD 卡在 SPI 模式下数据高位先发。


SD 卡的 SPI 方式读写:


   SD 卡读写一次的数据量必须为 512 字节的整数倍,亦即,对 SD 卡读写操作的最少数据量为 512 字节。我们也可以通过向 SD 卡写修改扇区大小的指令 CMD16(CMD16—为接下来的块操作指令设置块长度)以使每次读写的数据量变为(n×512)字节(n≥1),本文中我们使用 SD 卡默认的一次操作 512 字节的模式对 SD 卡进行读写操作。


对 SD 卡读操作的时序为:


(1) 写入读单数据块命令 CMD17(0x11|0x40=0x51)。


(2) 写 4 个地址参数,4 个字节凑成一个 32 位的地址值,第一个字节是 32 位地址值的最高 8 位数据,第 4 个字节是 32 位地址值的最低 8 位数据。(与写操作不同)


(3) 写 CRC 校验位 0xFF。


(4) 写若干个 0xFF 的空操作。


(5) SD 卡发送 0x00 响应。


(6) 写若干个 0xFF 的空操作(等待)。


(7) SD 卡发送 0x FE 数据头。


(8) SD 卡发送指令指定地址的 512 字节数据块。


(9) SD 卡发送两字节的 CRC 校验码,由于 SPI 模式默认不需要 CRC 校验,因此这两个字节的数据可丢弃不用。


(10)拉高 CS,发送 8 个空时钟。


 至此,完成了对 SD 卡指定地址数据块的读操作。**


**



三、Linux 下块设备框架+SD 卡驱动开发

3.1 硬件连接


这是驱动安装之后测试效果:


3.2 驱动代码

#include <linux/kernel.h> #include <linux/module.h>#include <linux/miscdevice.h>#include <linux/fs.h>#include <linux/io.h>#include <asm/uaccess.h>#include <linux/gpio.h>#include <mach/gpio.h>#include <plat/gpio-cfg.h>#include <linux/device.h>
#include <linux/vmalloc.h>#include <linux/init.h>#include <linux/blkdev.h>#include <linux/bitops.h>#include <linux/mutex.h>#include <linux/slab.h>#include <linux/hdreg.h>

volatile unsigned int *GPBCON;volatile unsigned int *GPBDAT;
/*----------------------------------------------初始化SD硬件硬件连接:GPB_0--时钟线GPB_3--数据线--MOSIGPB_2--数据线--MISOGPB_1--片选------------------------------------------------*/#define SDCardOut(x) {if(x){*GPBDAT|=1<<3;}else{*GPBDAT&=~(1<<3);}} #define SDCardInput (*GPBDAT&1<<2)#define SDCardSCLK(x) {if(x){*GPBDAT|=1<<0;}else{*GPBDAT&=~(1<<0);}} #define SDCardCS(x) {if(x){*GPBDAT|=1<<1;}else{*GPBDAT&=~(1<<1);}}

// SD卡类型定义 #define SDCard_TYPE_ERR 0X00 //卡类型错误#define SDCard_TYPE_MMC 0X01 //MMC卡#define SDCard_TYPE_V1 0X02#define SDCard_TYPE_V2 0X04#define SDCard_TYPE_V2HC 0X06
// SD卡指令表 #define SDCard_CMD0 0 //卡复位#define SDCard_CMD1 1#define SDCard_CMD8 8 //命令8 ,SEND_IF_COND#define SDCard_CMD9 9 //命令9 ,读CSD数据#define SDCard_CMD10 10 //命令10,读CID数据#define SDCard_CMD12 12 //命令12,停止数据传输#define SDCard_CMD13 16 //命令16,设置扇区大小 应返回0x00#define SDCard_CMD17 17 //命令17,读扇区#define SDCard_CMD18 18 //命令18,读Multi 扇区#define SDCard_CMD23 23 //命令23,设置多扇区写入前预先擦除N个block#define SDCard_CMD24 24 //命令24,写扇区#define SDCard_CMD25 25 //命令25,写多个扇区#define SDCard_CMD41 41 //命令41,应返回0x00#define SDCard_CMD55 55 //命令55,应返回0x01#define SDCard_CMD58 58 //命令58,读OCR信息#define SDCard_CMD59 59 //命令59,使能/禁止CRC,应返回0x00、
/*SD卡回应标记字*/#define SDCard_RESPONSE_NO_ERROR 0x00 //正确回应#define SDCard_SD_IN_IDLE_STATE 0x01 //闲置状态#define SDCard_SD_ERASE_RESET 0x02 //擦除复位#define SDCard_RESPONSE_FAILURE 0xFF //响应失败 //函数声明 u8 SDCardReadWriteOneByte(u8 data); //底层接口,SPI读写字节函数u8 SDCardWaitBusy(void); //等待SD卡准备u8 SDCardGetAck(u8 Response); //获得应答u8 SDCardDeviceInit(void); //初始化u8 SDCardReadData(u8*buf,u32 sector,u32 cnt); //读块u8 SDCardWriteData(u8*buf,u32 sector,u32 cnt); //写块u32 GetSDCardSectorCount(void); //读扇区数u8 GetSDCardCISDCardOutnfo(u8 *cid_data); //读SD卡CIDu8 GetSDCardCSSDCardOutnfo(u8 *csd_data); //读SD卡CSD
static u8 SD_Type=0; //存放SD卡的类型static u32 sd_size; //存放SD卡返回的容量
/*函数功能:SD卡底层接口,通过SPI时序向SD卡读写一个字节函数参数:data是要写入的数据返 回 值:读到的数据说明:时序是第二个上升沿采集数据*/u8 SDCardReadWriteOneByte(u8 DataTx){ u8 DataRx=0; u8 i; for(i=0;i<8;i++) { SDCardSCLK(0); if(DataTx&0x80){SDCardOut(1);} else {SDCardOut(0);} DataTx<<=1; SDCardSCLK(1);//第二个上升沿采集数据 DataRx<<=1; if(SDCardInput)DataRx|=0x01; } return DataRx;}

/*函数功能:底层SD卡接口初始化
本程序SPI接口如下:PC11 片选 SDCardCSPC12 时钟 SDCardSCLKPD2 输出 SPI_MOSI--主机输出从机输入PC8 输入 SPI_MISO--主机输入从机输出*/void SDCardSpiInit(void){ /*1. 转换物理地址得到虚拟地址*/ GPBCON=ioremap(0x11400040,4); GPBDAT=ioremap(0x11400044,4);
/*2. 配置GPIO口模式*/ *GPBCON&=0xFFFF0000; *GPBCON|=0xFF001011; SDCardCS(1);}

/*函数功能:取消选择,释放SPI总线*/void SDCardCancelCS(void){ SDCardCS(1); SDCardReadWriteOneByte(0xff);//提供额外的8个时钟}
/*函数 功 能:选择sd卡,并且等待卡准备OK函数返回值:0,成功;1,失败;*/u8 SDCardSelectCS(void){ SDCardCS(0); if(SDCardWaitBusy()==0)return 0;//等待成功 SDCardCancelCS(); return 1;//等待失败}

/*函数 功 能:等待卡准备好函数返回值:0,准备好了;其他,错误代码*/u8 SDCardWaitBusy(void){ u32 t=0; do { if(SDCardReadWriteOneByte(0XFF)==0XFF)return 0;//OK t++; }while(t<0xFFFFFF);//等待 return 1;}

/*函数功能:等待SD卡回应函数参数: Response:要得到的回应值返 回 值: 0,成功得到了该回应值 其他,得到回应值失败*/u8 SDCardGetAck(u8 Response){ u16 Count=0xFFFF;//等待次数 while((SDCardReadWriteOneByte(0XFF)!=Response)&&Count)Count--;//等待得到准确的回应 if(Count==0)return SDCard_RESPONSE_FAILURE;//得到回应失败 else return SDCard_RESPONSE_NO_ERROR;//正确回应}

/*函数功能:从sd卡读取一个数据包的内容函数参数: buf:数据缓存区 len:要读取的数据长度.返回值: 0,成功;其他,失败; */u8 SDCardRecvData(u8*buf,u16 len){ if(SDCardGetAck(0xFE))return 1;//等待SD卡发回数据起始令牌0xFE while(len--)//开始接收数据 { *buf=SDCardReadWriteOneByte(0xFF); buf++; } //下面是2个伪CRC(dummy CRC) SDCardReadWriteOneByte(0xFF); SDCardReadWriteOneByte(0xFF); return 0;//读取成功}

/*函数功能:向sd卡写入一个数据包的内容 512字节函数参数: buf 数据缓存区 cmd 指令返 回 值:0表示成功;其他值表示失败;*/u8 SDCardSendData(u8*buf,u8 cmd){ u16 t; if(SDCardWaitBusy())return 1; //等待准备失效 SDCardReadWriteOneByte(cmd); if(cmd!=0XFD)//不是结束指令 { for(t=0;t<512;t++)SDCardReadWriteOneByte(buf[t]);//提高速度,减少函数传参时间 SDCardReadWriteOneByte(0xFF); //忽略crc SDCardReadWriteOneByte(0xFF); t=SDCardReadWriteOneByte(0xFF); //接收响应 if((t&0x1F)!=0x05)return 2; //响应错误 } return 0;//写入成功}


/*函数功能:向SD卡发送一个命令函数参数: u8 cmd 命令 u32 arg 命令参数 u8 crc crc校验值 返回值:SD卡返回的响应*/ u8 SendSDCardCmd(u8 cmd, u32 arg, u8 crc){ u8 r1; u8 Retry=0; SDCardCancelCS(); //取消上次片选 if(SDCardSelectCS())return 0XFF;//片选失效 //发送数据 SDCardReadWriteOneByte(cmd | 0x40);//分别写入命令 SDCardReadWriteOneByte(arg >> 24); SDCardReadWriteOneByte(arg >> 16); SDCardReadWriteOneByte(arg >> 8); SDCardReadWriteOneByte(arg); SDCardReadWriteOneByte(crc); if(cmd==SDCard_CMD12)SDCardReadWriteOneByte(0xff);//Skip a stuff byte when stop reading Retry=0X1F;
do { r1=SDCardReadWriteOneByte(0xFF); }while((r1&0X80) && Retry--); //等待响应,或超时退出 return r1; //返回状态值}

/*函数功能:获取SD卡的CID信息,包括制造商信息函数参数:u8 *cid_data(存放CID的内存,至少16Byte) 返 回 值: 0:成功,1:错误 */u8 GetSDCardCISDCardOutnfo(u8 *cid_data){ u8 r1; //发SDCard_CMD10命令,读CID r1=SendSDCardCmd(SDCard_CMD10,0,0x01); if(r1==0x00) { r1=SDCardRecvData(cid_data,16);//接收16个字节的数据 } SDCardCancelCS();//取消片选 if(r1)return 1; else return 0;}

/*函数说明: 获取SD卡的CSD信息,包括容量和速度信息函数参数: u8 *cid_data(存放CID的内存,至少16Byte) 返 回 值: 0:成功,1:错误 */u8 GetSDCardCSSDCardOutnfo(u8 *csd_data){ u8 r1; r1=SendSDCardCmd(SDCard_CMD9,0,0x01); //发SDCard_CMD9命令,读CSD if(r1==0) { r1=SDCardRecvData(csd_data, 16);//接收16个字节的数据 } SDCardCancelCS();//取消片选 if(r1)return 1; else return 0;}

/*函数功能:获取SD卡的总扇区数(扇区数) 返 回 值: 0表示容量检测出错,其他值表示SD卡的容量(扇区数/512字节)说 明: 每扇区的字节数必为512字节,如果不是512字节,则初始化不能通过. */u32 GetSDCardSectorCount(void){ u8 csd[16]; u32 Capacity; u8 n; u16 csize; if(GetSDCardCSSDCardOutnfo(csd)!=0) return 0; //取CSD信息,如果期间出错,返回0 if((csd[0]&0xC0)==0x40) //V2.00的卡,如果为SDHC卡,按照下面方式计算 { csize = csd[9] + ((u16)csd[8] << 8) + 1; Capacity = (u32)csize << 10;//得到扇区数 } else//V1.XX的卡 { n = (csd[5] & 15) + ((csd[10] & 128) >> 7) + ((csd[9] & 3) << 1) + 2; csize = (csd[8] >> 6) + ((u16)csd[7] << 2) + ((u16)(csd[6] & 3) << 10) + 1; Capacity= (u32)csize << (n - 9);//得到扇区数 } return Capacity;}
/*函数功能: 初始化SD卡返 回 值: 非0表示初始化失败!*/u8 SDCardDeviceInit(void){ u8 r1; // 存放SD卡的返回值 u16 retry; // 用来进行超时计数 u8 buf[4]; u16 i; SDCardSpiInit(); //初始化底层IO口 for(i=0;i<10;i++)SDCardReadWriteOneByte(0XFF); //发送最少74个脉冲 retry=20; do { r1=SendSDCardCmd(SDCard_CMD0,0,0x95);//进入IDLE状态 闲置 }while((r1!=0X01) && retry--); SD_Type=0; //默认无卡 if(r1==0X01) { if(SendSDCardCmd(SDCard_CMD8,0x1AA,0x87)==1) //SD V2.0 { for(i=0;i<4;i++)buf[i]=SDCardReadWriteOneByte(0XFF); //Get trailing return value of R7 resp if(buf[2]==0X01&&buf[3]==0XAA) //卡是否支持2.7~3.6V { retry=0XFFFE; do { SendSDCardCmd(SDCard_CMD55,0,0X01); //发送SDCard_CMD55 r1=SendSDCardCmd(SDCard_CMD41,0x40000000,0X01);//发送SDCard_CMD41 }while(r1&&retry--); if(retry&&SendSDCardCmd(SDCard_CMD58,0,0X01)==0)//鉴别SD2.0卡版本开始 { for(i=0;i<4;i++)buf[i]=SDCardReadWriteOneByte(0XFF);//得到OCR值 if(buf[0]&0x40)SD_Type=SDCard_TYPE_V2HC; //检查CCS else SD_Type=SDCard_TYPE_V2; } } } else//SD V1.x/ MMC V3 { SendSDCardCmd(SDCard_CMD55,0,0X01); //发送SDCard_CMD55 r1=SendSDCardCmd(SDCard_CMD41,0,0X01); //发送SDCard_CMD41 if(r1<=1) { SD_Type=SDCard_TYPE_V1; retry=0XFFFE; do //等待退出IDLE模式 { SendSDCardCmd(SDCard_CMD55,0,0X01); //发送SDCard_CMD55 r1=SendSDCardCmd(SDCard_CMD41,0,0X01);//发送SDCard_CMD41 }while(r1&&retry--); } else//MMC卡不支持SDCard_CMD55+SDCard_CMD41识别 { SD_Type=SDCard_TYPE_MMC;//MMC V3 retry=0XFFFE; do //等待退出IDLE模式 { r1=SendSDCardCmd(SDCard_CMD1,0,0X01);//发送SDCard_CMD1 }while(r1&&retry--); } if(retry==0||SendSDCardCmd(SDCard_CMD13,512,0X01)!=0)SD_Type=SDCard_TYPE_ERR;//错误的卡 } } SDCardCancelCS(); //取消片选 if(SD_Type)return 0; //初始化成功返回0 else if(r1)return r1; //返回值错误值 return 0xaa; //其他错误}

/*函数功能:读SD卡函数参数: buf:数据缓存区 sector:扇区 cnt:扇区数返回值: 0,ok;其他,失败.说 明: SD卡一个扇区大小512字节*/u8 SDCardReadData(u8*buf,u32 sector,u32 cnt){ u8 r1; if(SD_Type!=SDCard_TYPE_V2HC)sector<<=9;//转换为字节地址 if(cnt==1) { r1=SendSDCardCmd(SDCard_CMD17,sector,0X01);//读命令 if(r1==0) //指令发送成功 { r1=SDCardRecvData(buf,512); //接收512个字节 } }else { r1=SendSDCardCmd(SDCard_CMD18,sector,0X01);//连续读命令 do { r1=SDCardRecvData(buf,512);//接收512个字节 buf+=512; }while(--cnt && r1==0); SendSDCardCmd(SDCard_CMD12,0,0X01); //发送停止命令 } SDCardCancelCS();//取消片选 return r1;//}
/*函数功能:向SD卡写数据函数参数: buf:数据缓存区 sector:起始扇区 cnt:扇区数返回值: 0,ok;其他,失败.说 明: SD卡一个扇区大小512字节*/u8 SDCardWriteData(u8*buf,u32 sector,u32 cnt){ u8 r1; if(SD_Type!=SDCard_TYPE_V2HC)sector *= 512;//转换为字节地址 if(cnt==1) { r1=SendSDCardCmd(SDCard_CMD24,sector,0X01);//读命令 if(r1==0)//指令发送成功 { r1=SDCardSendData(buf,0xFE);//写512个字节 } } else { if(SD_Type!=SDCard_TYPE_MMC) { SendSDCardCmd(SDCard_CMD55,0,0X01); SendSDCardCmd(SDCard_CMD23,cnt,0X01);//发送指令 } r1=SendSDCardCmd(SDCard_CMD25,sector,0X01);//连续读命令 if(r1==0) { do { r1=SDCardSendData(buf,0xFC);//接收512个字节 buf+=512; }while(--cnt && r1==0); r1=SDCardSendData(0,0xFD);//接收512个字节 } } SDCardCancelCS();//取消片选 return r1;//}


static unsigned int major;static struct gendisk *tiny4412_block_gendisk;static struct request_queue *tiny4412_block_queue;
#define DEVICE_NAME "block_dev"static DEFINE_SPINLOCK(tiny4412_block_lock);
/*处理请求队列*/static void block_request_work_func(struct request_queue *q){ struct request *req; /*读取队列中一个请求*/ req=blk_fetch_request(q); while(req) { int err=0; /*得到当前起始扇区: 从哪里开始读写*/ unsigned long start = blk_rq_pos(req); /*得到当前读写的字节单位*/ unsigned long len = blk_rq_cur_bytes(req); /*判断当前读写方向*/ if(rq_data_dir(req) == READ) { SDCardReadData(req->buffer,start,len/512); //读块 } else { SDCardWriteData(req->buffer,start,len/512); //写块 } /*报告当前请求处理完毕*/ if (!__blk_end_request_cur(req, err)) /*继续读取队列里的下一个请求*/ req = blk_fetch_request(q); }}
/*获取磁盘的物理结构信息磁头、柱面(磁道)、扇区
fdisk /dev/xxx*/
static int tiny4412_block_getgeo(struct block_device *dev, struct hd_geometry *geo){ /* TINY4412_DISK_SIZE 容量字节单位 每个扇区是512字节
32GB SD卡的扇区数量: 62333952 */ geo->cylinders=sd_size/16/255; /*柱面--65535*/ geo->heads=16; /*磁头 255 */ geo->sectors=255; /*扇区 255 */ return 0;}
static const struct block_device_operations tiny4412_block_fops ={ .owner = THIS_MODULE, .getgeo = tiny4412_block_getgeo,};
static int __init tiny4412_SdCardDrv_init(void){ if(SDCardDeviceInit()) { printk("SD卡初始化失败!\r\n"); } sd_size=GetSDCardSectorCount(); //检测SD卡大小,返回值右移11位得到以M为单位的容量 printk("SD卡的容量:%d M\r\n",sd_size>>11); printk("SD卡的扇区数量:%d\r\n",sd_size);
/*1. 注册块设备:分配主设备号*/ major=register_blkdev(0, DEVICE_NAME);
/*2. 分配根磁盘结构体*/ tiny4412_block_gendisk=alloc_disk(3);/*sdc sdc1 sdc2 ....*/
/*3. 初始化请求队列*/ tiny4412_block_queue=blk_init_queue(block_request_work_func, &tiny4412_block_lock);
/*4. 填充根磁盘参数信息*/ tiny4412_block_gendisk->major = major; /*主设备号*/ tiny4412_block_gendisk->first_minor = 0; /*起始次设备号*/ tiny4412_block_gendisk->fops = &tiny4412_block_fops; /*块设备文件操作集合*/ sprintf(tiny4412_block_gendisk->disk_name,DEVICE_NAME); /*/dev/设备节点名称*/ tiny4412_block_gendisk->queue = tiny4412_block_queue; /*绑定请求队列*/ tiny4412_block_gendisk->part0.nr_sects=sd_size; /*设置扇区数量-20480*/ /*5. 向内核添加磁盘信息*/ add_disk(tiny4412_block_gendisk);
printk("SD卡驱动安装成功.\n"); return 0;}
static void __exit tiny4412_SdCardDrv_exit(void){ /*1. 注销主设备号*/ unregister_blkdev(major,DEVICE_NAME); /*2. 删除根磁盘*/ del_gendisk(tiny4412_block_gendisk); /*3. 减少磁盘计数*/ put_disk(tiny4412_block_gendisk); /*4. 释放队列*/ blk_cleanup_queue(tiny4412_block_queue);
/*取消映射*/ iounmap(GPBCON); iounmap(GPBDAT); printk("SD卡驱动卸载成功.\n");}
module_init(tiny4412_SdCardDrv_init); module_exit(tiny4412_SdCardDrv_exit);MODULE_LICENSE("GPL");MODULE_AUTHOR("wbyq");
复制代码


发布于: 刚刚阅读数: 4
用户头像

DS小龙哥

关注

之所以觉得累,是因为说的比做的多。 2022-01-06 加入

熟悉C/C++、51单片机、STM32、Linux应用开发、Linux驱动开发、音视频开发、QT开发. 目前已经完成的项目涉及音视频、物联网、智能家居、工业控制领域

评论

发布
暂无评论
Linux下驱动开发_块设备驱动开发(硬件上采用SD卡+SPI协议)_10月月更_DS小龙哥_InfoQ写作社区