字符设备驱动
〇、基本知识
设备驱动分类
(按共性分类方便管理)
1.字符设备驱动
字符设备指那些必须按字节流传输,以串行顺序依次进行访问的设备。它们是我们日常最常见的驱动了,像鼠标、键盘、打印机、触摸屏,还有点灯以及I2C、SPI、音视频都属于字符设备驱动。
字符设备不经过系统快速缓冲。
2.块设备驱动
就是存储器设备的驱动,比如 EMMC、NAND、SD 卡和 U 盘等存储设备,因为这些存储设备的特点是以存储块为基础,按块随机访问,可以用任意顺序进行访问,以块为单位进行操作,因此叫做块设备。数据的读写只能以块(通常是512B)的倍数 进行。与字符设备不同,块设备并不支持基于字符的寻址。
块设备经过设备缓冲
3.网络设备驱动
就是网络驱动,不管是有线的还是无线的,都属于网络设备驱动的范畴。按TCP/IP协议栈传输。
网络设备面向数据包的接受和发送而设计,它并不对应文件系统的节点
注意:
块设备和网络设备驱动要比字符设备驱动复杂,就是因为其复杂所以半导体厂商一般都编写好了,大多数情况下都是直接可以使用的。
一个设备可以属于多种设备驱动类型,比如USB WIFI,其使用 USB 接口,属于字符设备,但是其又能上网,所以也属于网络设备驱动。
设备驱动框架
为了安全
一切皆文件
为了标准化操作函数,方便对接工作
open read write close
字符设备框架
字符设备驱动编写三部曲
- 注册设备号
- 初始化字符设备
- 实现需要的文件操作
一、注册设备号
为了让内核知道这个设备是合法的,将构造的设备号注册到内核中,表明该设备号已经被占用,如果有其他驱动随后要注册该设备号,将会失败。
- 主次设备号
- MKDEV
- register_chrdev_region
驱动部分
00_头文件
#include <linux/fs.h> //for MKDEV register_chrdev_region
01_主次设备号
#define LED_MA 500 //主设备号 用于区分不同种类的设备
//某些主设备号已经静态地分配给了大部分公用设备。见Documentation/devices.txt 。
#define LED_MI 0 //次设备号 用于区分同一类型的多个设备
#define LED_NUM 1 //有多少个设备
02_注册字符设备号
dev_t devno = MKDEV(LED_MA, LED_MI);
int ret;
ret = register_chrdev_region(devno, LED_NUM, "yhai_led"); /*注册字符设备号(静态分配),为了让内核认可
为一个字符驱动获取一个或多个设备编号
dev_id: 分配的起始设备编号(常常是0)
DEVICE_NUM: 请求的连续设备编号的总数(不能太大,避免别的主设备号冲突)
DEVICE_NAME: 是应当连接到这个编号范围的设备的名字
alloc_chrdev_region 可进行动态分配
*/
if (ret < 0) {
printk("register_chrdev_region\n");
return ret;
}
03_取消注册
dev_t devno = MKDEV(LED_MA, LED_MI);
unregister_chrdev_region(devno, LED_NUM); //取消注册
总程序
//led.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h> //for MKDEV register_chrdev_region
#define LED_MA 500 //主设备号 用于区分不同种类的设备
//某些主设备号已经静态地分配给了大部分公用设备。见Documentation/devices.txt 。
#define LED_MI 0 //次设备号 用于区分同一类型的多个设备
#define LED_NUM 1 //有多少个设备
static int led_init(void)
{
dev_t devno = MKDEV(LED_MA, LED_MI);
int ret;
ret = register_chrdev_region(devno, LED_NUM, "yhai_led"); /*注册字符设备号(静态分配)
为一个字符驱动获取一个或多个设备编号
dev_id: 分配的起始设备编号(常常是0)
DEVICE_NUM: 请求的连续设备编号的总数(不能太大,避免别的主设备号冲突)
DEVICE_NAME: 是应当连接到这个编号范围的设备的名字
alloc_chrdev_region 可进行动态分配
*/
if (ret < 0) {
printk("register_chrdev_region\n");
return ret;
}
printk("led init\n");
return 0; //返回值 0:成功 负值:失败
}
static void led_exit(void)
{
dev_t devno = MKDEV(LED_MA, LED_MI);
unregister_chrdev_region(devno, LED_NUM); //取消注册
printk("led exit\n");
}
module_init(led_init); //模块加载入口声明
module_exit(led_exit); //模块卸载入口声明
MODULE_LICENSE("GPL"); //模块免费开源声明
验证测试
# insmod led.ko /*加载模块
# rmmod led //卸载模块
二、初始化字符设备
连接设备号对应的操作
- file_operations
- cdev_init 连接设备号对应的操作
- cdev_add 添加到散列表,里面放着一堆字符设备。应用层open时根据设备号在散列表中找到设备,open返回的fd找到对应file结构,然后调用相应操作
驱动部分
00_头文件
#include <linux/cdev.h> //字符设备头文件
01_字符设备初始化
struct file_operations led_fops 这部分全是函数指针
struct cdev cdev; //定义字符设备
static int led_open(struct inode *inode, struct file *file)
{
printk("driver led open\n");
return 0;
}
static int led_release(struct inode *inode, struct file *file)
{
printk("driver led close\n");
return 0;
}
struct file_operations led_fops = { //文件操作(一切皆文件)
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
};
cdev_init(&cdev, & led_fops);//字符设备初始化
ret = cdev_add(&cdev, devno, LED_NUM); //添加字符设备到系统中
if (ret < 0) {
printk("cdev_add\n");
return ret;
}
02_字符设备删除
这个删完,再取消注册,相当于把空间中的内容都清掉,再把空间释放
cdev_del(&cdev)
应用部分
交叉编译aarch64-linux-gnu-gcc app.c
//app.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
int main(int argc, char **argv)
{
int fd;
fd = open("/dev/led", O_RDWR);
if (fd < 0) {
perror("open");
exit(1);
}
printf("open led ok\n"); //注意要加\n 否则打印信息可能没有
return 0;
}
总程序
//led.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h> //for MKDEV register_chrdev_region
#include <linux/cdev.h> //字符设备头文件
#define LED_MA 500 //主设备号 用于区分不同种类的设备
//某些主设备号已经静态地分配给了大部分公用设备。见Documentation/devices.txt。
#define LED_MI 0 //次设备号 用于区分同一类型的多个设备
#define LED_NUM 1 //有多少个设备
struct cdev cdev; //定义字符设备
static int led_open(struct inode *inode, struct file *file)
{
printk("driver led open\n");
return 0;
}
static int led_release(struct inode *inode, struct file *file)
{
printk("driver led close\n");
return 0;
}
struct file_operations led_fops = { //文件操作(一切皆文件)
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
};
static int led_init(void)
{
dev_t devno = MKDEV(LED_MA, LED_MI);
int ret;
ret = register_chrdev_region(devno, LED_NUM, "yhai_led"); /*注册字符设备号(静态分配)
为一个字符驱动获取一个或多个设备编号
dev_id: 分配的起始设备编号(常常是0)
DEVICE_NUM: 请求的连续设备编号的总数(不能太大,避免别的主设备号冲突)
DEVICE_NAME: 是应当连接到这个编号范围的设备的名字
alloc_chrdev_region 可进行动态分配
*/
if (ret < 0) { //要进行异常判断
printk("register_chrdev_region\n");
return ret;
}
cdev_init(&cdev, & led_fops);//字符设备初始化
ret = cdev_add(&cdev, devno, LED_NUM); //添加字符设备到系统中
if (ret < 0) {
printk("cdev_add\n");
return ret;
}
printk("led init\n");
return 0; //返回值 0:成功 负值:失败
}
static void led_exit(void)
{
dev_t devno = MKDEV(LED_MA, LED_MI);
cdev_del(&cdev)
unregister_chrdev_region(devno, LED_NUM); //取消注册
printk("led exit\n");
}
module_init(led_init); //模块加载入口声明
module_exit(led_exit); //模块卸载入口声明
MODULE_LICENSE("GPL"); //模块免费开源声明
验证测试
$ make
$ aarch64-linux-gnu-gcc app.c //编译应用程序,生成a.out
$ cp led.ko a.out /nfs/rootfs
$ insmod led.ko
$ mknod /dev/led c 500 0 //创建设备文件,应用才能访问它. ( ls -l /dev 可以看到很多其它设备文件)
$./a.out //运行 成功可看到 open led ok
$ rmmod led.ko
三、实现定制文件操作
- 幻数加密定义命令,防止不同驱动间命令错乱(内核与应用层间)
- ioremap(内核与硬件间),不能直接操作硬件
- goto语句,跳到对应err位置实现逆序释放
驱动部分
00_头文件
#include <asm/io.h> //io操作的头文件(for ioremap readl)
01_定制ioctrl操作命令部分
#define LED_MAGIC 'L' //幻数:0~0xff的数。用于区分不同的驱动, 见Documentation/ioctl/ioctl-number.txt
#define LED_ON _IOW(LED_MAGIC, 0, int) //加幻数方式来定义命令,防止不同驱动间命令错乱
#define LED_OFF _IOW(LED_MAGIC, 1, int)
//ioctl 用于定制操作
static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
switch (cmd) {
case LED_ON:
led_on();
break;
case LED_OFF:
led_off();
break;
default: //异常处理
printk("no found this cmd =%d",cmd);
return -1;
}
return 0;
}
struct file_operations led_fops = { //文件操作(一切皆文件)
.unlocked_ioctl = led_ioctl,
};
02_硬件控制部分
//电路连接
地线 //接 40pin 接口的40脚 gnd
控制线 //接 40pin 接口的12脚 -> 管脚转换表 -> 电路图 -> 芯片手册
#define GPIO3 0x6000D200 // 第3个Bank GPIO 的基地址
#define CNF 0x04 //配置寄存器 (0:GPIO 1:SFIO) 偏移量
#define OE 0x14 //输出使能寄存器 (1:使能 0:关闭)
#define OUT 0x24 //输出寄存器(1:高电平 0:低电平)
#define MSK_CNF 0x84 //配置屏蔽寄存器(高位1:屏蔽 高位0:不屏蔽 低位1:GPIO模式 低位0:SFIO模式)
#define MSK_OE 0x94 //输出使能屏蔽寄存器(高位1:禁止写 低位1:使能)
#define MSK_OUT 0xA4 //输出屏蔽寄存器(高位1:禁止写 低位1:高电平)
#define PINMUX_AUX_DAP4_SCLK_0 0x70003150 //管脚复用设置
unsigned char *gpio_base;
unsigned char *gpio_pinmux;
//开灯
void led_on(void)
{
writel(readl(gpio_base+OUT) | 1 << 7, gpio_base+OUT); //引脚输出高电平,点亮灯
printk("out put high ,led on 输出高电平,点亮灯\n");
}
//关灯
void led_off(void)
{
writel(readl(gpio_base+OUT) & ~(1 << 7), gpio_base+OUT); //引脚输出低电平,灭灯
printk("out put low, led off 输出低电平,灭灯\n");
}
static int led_init(void)
{
//硬件初始化(成功可看到灯亮)
//a.管脚复用的设置,设置做GPIO功能
gpio_pinmux = ioremap(PINMUX_AUX_DAP4_SCLK_0, 8);
/*从物理地址PINMUX_AUX_DAP4_SCLK_0开始,映射8字节长度的空间到内核空间
动态映射 物理地址 到内核虚拟地址
phys_addr 起始物理地址
size 映射范围大小,单位字节
返回值 映射后的内核虚拟地址
*/
if (gpio_pinmux == NULL) {
printk("ioremap gpio_pinmux error\n");
goto err3;
}
writel((readl(gpio_pinmux) & ~(1 << 4))|1, gpio_pinmux);
/*管脚复用配置用于 GPIO
1:0 I2S4B PM: 0 = I2S4B 1 = RSVD1 2 = RSVD2 3 = RSVD3
设为非0,表示不用作I2S功能,则默认用做GPIO功能
4 TRISTATE TRISTATE: 0 = PASSTHROUGH 1 = TRISTATE
设为0,设为直通状态才能驱动外面的设备
见 9.5.1 Per Pad Options
Tristate 高阻态 -> 与外界是断开的,默认启动设为高阻太,避免驱动影响外面的设备
passthrough 直通态 -> 才能驱动外面设备
*/
//b. 做GPIO功能时的内部配置
gpio_base = ioremap(GPIO3, 0xFF);
if (gpio_base == NULL) {
printk("ioremap gpio_base error\n");
goto err2;
}
writel(readl(gpio_base+CNF) | 1 << 7, gpio_base+CNF); //配置引脚GPIO3_PJ.07 为 GPIO模式
writel(readl(gpio_base+OE) | 1 << 7, gpio_base+OE); //使能引脚(7号)
writel(readl(gpio_base+OUT) | 1 << 7, gpio_base+OUT); //输出高电平,点亮灯
writel(readl(gpio_base+MSK_CNF) | 1 << 7, gpio_base+MSK_CNF); //取消对GPIO模下引脚的屏蔽
writel(readl(gpio_base+MSK_OE) | 1 << 7, gpio_base+MSK_OE); //取消引脚 使能屏蔽
}
03_顺序申请,逆序释放
static int led_init(void)
{
ret = cdev_add(&cdev, devno, LED_NUM);
if (ret < 0) {
printk("cdev_add\n");
goto err1;
}
gpio_base = ioremap(GPIO3, 0xFF);
if (gpio_base == NULL) {
printk("ioremap gpio_base error\n");
goto err2;
}
gpio_pinmux = ioremap(PINMUX_AUX_DAP4_SCLK_0, 8);
if (gpio_pinmux == NULL) {
printk("ioremap gpio_pinmux error\n");
goto err3;
}
err3: //跳过来后就顺序执行下面的顺序释放
iounmap(gpio_base);
err2:
cdev_del(&cdev);
err1: //报错就释放上一步做完的
unregister_chrdev_region(devno, LED_NUM);
return ret;
}
应用部分
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#define LED_MAGIC 'L' //幻数,一般一个驱动一个幻数,和驱动部分幻数一致
//用幻数加密后避免程序误操作,有写错的时候有安全问题
#define LED_ON _IOW(LED_MAGIC, 0, int) //用幻数加密控制命令
#define LED_OFF _IOW(LED_MAGIC, 1, int)
int main(int argc, char **argv)
{
int fd;
fd = open("/dev/led", O_RDWR); //打开设备文件
if (fd < 0) {
perror("open");
exit(1);
}
while(1)
{
ioctl(fd, LED_ON); //发送控制命令 LED_ON
usleep(100000);
ioctl(fd, LED_OFF); //发送控制命令 LED_OFF
usleep(100000);
}
return 0;
}
struct file_operations led_fops = { //文件操作(一切皆文件)
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
.unlocked_ioctl = led_ioctl,
};
总程序
#include <linux/kernel.h>
#include <linux/module.h> //模块的头文件 (for module_init MODULE_LICENSE)
#include <linux/fs.h> //for MKDEV register_chrdev_region
#include <linux/cdev.h> //字符设备头文件
#include <asm/io.h> //io操作的头文件(for ioremap readl)
#define LED_MA 500 //主设备号 用于区分不同种类的设备
//某些主设备号已经静态地分配给了大部分公用设备。见Documentation/devices.txt 。
#define LED_MI 0 //次设备号 用于区分同一类型的多个设备
#define LED_NUM 1 //有多少个设备
struct cdev cdev; //定义字符设备
#define LED_MAGIC 'L' //幻数:0~0xff的数。用于区分不同的驱动, 见Documentation/ioctl/ioctl-number.txt
#define LED_ON _IOW(LED_MAGIC, 0, int) //加幻数方式来定义命令,防止不同驱动间命令错乱
#define LED_OFF _IOW(LED_MAGIC, 1, int)
#define GPIO3 0x6000D200 //第3个Bank GPIO 的基地址 (GPIO3_PJ.07)
#define CNF 0x04 //配置寄存器 (0:GPIO 1:SFIO) 偏移量
#define OE 0x14 //输出使能寄存器 (1:使能 0:关闭)
#define OUT 0x24 //输出寄存器(1:高电平 0:低电平)
#define MSK_CNF 0x84 //配置屏蔽寄存器(高位1:屏蔽 高位0:不屏蔽 低位1:GPIO模式 低位0:SFIO模式)
#define MSK_OE 0x94 //输出使能屏蔽寄存器(高位1:禁止写 低位1:使能)
#define MSK_OUT 0xA4 //输出屏蔽寄存器(高位1:禁止写 低位1:高电平)
#define PINMUX_AUX_DAP4_SCLK_0 0x70003150 //管脚复用设置
unsigned char *gpio_base;
unsigned char *gpio_pinmux;
//查看相关寄存器的内容->方便查BUG
void show_reg(void)
{
printk(" cnf =%x\n",readl(gpio_base+CNF)); //通过基地址加偏移量,来访问对应的配置寄存器
printk(" oe =%x\n",readl(gpio_base+OE));
printk(" out =%x\n",readl(gpio_base+OUT));
printk("mask cnf =%x\n",readl(gpio_base+MSK_CNF));
printk("mask oe =%x\n",readl(gpio_base+MSK_OE));
printk("mask out =%x\n",readl(gpio_base+MSK_OUT));
printk("gpio_pinmux =%x\n",readl(gpio_pinmux));
}
static int led_open(struct inode *inode, struct file *file)
{
printk("driver led open ok\n");
show_reg();
return 0;
}
static int led_release(struct inode *inode, struct file *file)
{
printk("driver led close ok\n");
show_reg();
return 0;
}
//开灯
void led_on(void)
{
writel(readl(gpio_base+OUT) | 1 << 7, gpio_base+OUT); //引脚输出高电平,点亮灯
printk("out put high ,led on 输出高电平,点亮灯\n");
}
//关灯
void led_off(void)
{
writel(readl(gpio_base+OUT) & ~(1 << 7), gpio_base+OUT); //引脚输出低电平,灭灯
printk("out put low, led off 输出低电平,灭灯\n");
}
//ioctl 用于定制操作
static long led_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
switch (cmd) {
case LED_ON:
led_on();
break;
case LED_OFF:
led_off();
break;
default: //异常处理
printk("no found this cmd =%d",cmd);
return -1;
}
return 0;
}
//3.实现需要的文件操作
// file_operations 中 定义了针对文件的一系列操作方法 不是每个都需实现
struct file_operations led_fops = { //文件操作(一切皆文件)
.owner = THIS_MODULE,
.open = led_open,
.release = led_release,
.unlocked_ioctl = led_ioctl,
};
static int led_init(void)
{
dev_t devno = MKDEV(LED_MA, LED_MI);
int ret;
//1.注册设备号
ret = register_chrdev_region(devno, LED_NUM, "yhai_led"); /*注册字符设备号(静态分配)
为一个字符驱动获取一个或多个设备编号
dev_id: 分配的起始设备编号(常常是0)
DEVICE_NUM: 请求的连续设备编号的总数(不能太大,避免别的主设备号冲突)
DEVICE_NAME: 是应当连接到这个编号范围的设备的名字
alloc_chrdev_region 可进行动态分配
*/
if (ret < 0) { //要进行异常判断
printk("register_chrdev_region\n");
return ret;
}
//2.初始化字符设备
cdev_init(&cdev, & led_fops);//字符设备初始化
ret = cdev_add(&cdev, devno, LED_NUM); //添加字符设备到系统中
if (ret < 0) {
printk("cdev_add\n");
goto err1;
}
//硬件初始化(成功可看到灯亮)
//a.管脚复用的设置,设置做GPIO功能
gpio_pinmux = ioremap(PINMUX_AUX_DAP4_SCLK_0, 8); /*从物理地址PINMUX_AUX_DAP4_SCLK_0开始,映射 8字节长度的空间到内核空间
动态映射 物理地址 到内核虚拟地址
phys_addr 起始物理地址
size 映射范围大小,单位字节
返回值 映射后的内核虚拟地址
*/
if (gpio_pinmux == NULL) {
printk("ioremap gpio_pinmux error\n");
goto err3;
}
writel((readl(gpio_pinmux) & ~(1 << 4))|1, gpio_pinmux); /*管脚复用配置用于 GPIO
1:0 I2S4B PM: 0 = I2S4B 1 = RSVD1 2 = RSVD2 3 = RSVD3
设为非0,表示不用作I2S功能,则默认用做GPIO功能
4 TRISTATE TRISTATE: 0 = PASSTHROUGH 1 = TRISTATE
设为0,设为直通状态才能驱动外面的设备
见 9.5.1 Per Pad Options
Tristate 高阻态 -> 与外界是断开的,默认启动设为高阻太,避免驱动影响外面的设备
passthrough 直通态 -> 才能驱动外面设备
*/
//b. 做GPIO功能时的内部配置
gpio_base = ioremap(GPIO3, 0xFF);
if (gpio_base == NULL) {
printk("ioremap gpio_base error\n");
goto err2;
}
writel(readl(gpio_base+CNF) | 1 << 7, gpio_base+CNF); //配置引脚GPIO3_PJ.07 为 GPIO模式
writel(readl(gpio_base+OE) | 1 << 7, gpio_base+OE); //使能引脚(7号)
writel(readl(gpio_base+OUT) | 1 << 7, gpio_base+OUT); //输出高电平,点亮灯
writel(readl(gpio_base+MSK_CNF) | 1 << 7, gpio_base+MSK_CNF); //取消对GPIO模下引脚的屏蔽
writel(readl(gpio_base+MSK_OE) | 1 << 7, gpio_base+MSK_OE); //取消引脚 使能屏蔽
printk("led init ok\n");
return 0; //返回值 0:成功 负值:失败
//goto 出错处理, 顺序申请,逆序释放,避免资源回收不完全(如内存泄露)
err3:
iounmap(gpio_base);
err2:
cdev_del(&cdev);
err1:
unregister_chrdev_region(devno, LED_NUM);
return ret;
}
static void led_exit(void)
{
//要配对释放资源,逆序释放资源
dev_t devno = MKDEV(LED_MA, LED_MI);
iounmap(gpio_base); //取消映射
iounmap(gpio_pinmux);
cdev_del(&cdev); //从系统中移除该设备
unregister_chrdev_region(devno, LED_NUM); //取消注册
printk("led exit ok\n");
}
module_init(led_init); //模块加载入口声明
module_exit(led_exit); //模块卸载入口声明
MODULE_LICENSE("GPL"); //模块免费开源声明
验证测试
$ make
$ aarch64-linux-gnu-gcc app.c //编译应用程序,生成a.out
$ cp led.ko a.out /nfs/rootfs
# insmod led.ko
# mknod /dev/led c 500 0 //创建设备文件
#./a.out //运行 成功可看到 灯闪烁
四、实现读写文件操作
- 应用空间的buf不能直接拷贝到内核空间,采用copy_from_user
- 错误码,数据长度等问题
驱动部分
//led.c
#include <asm/uaccess.h> //for read write
#define C_BUF_LEN 64
char c_buf[C_BUF_LEN];
//返回值 正数:成功写入的字节数 负值:错误码 0:无数据成功写入
static ssize_t led_write (struct file *file, const char __user *buf, //file: 文件指针 buf:用户空间的缓冲区
size_t count, loff_t * f_pos) //count: 数据长度 f_pos: 文件位置
{
ssize_t ret = 0;
printk ("Writing %ld bytes\n", count);
if (count > C_BUF_LEN -1)
return -ENOMEM;
if (count<0)
return -EINVAL;
/*应用空间的buf不能直接拷贝到内核空间
while(count--)
{
*c_buf++ = buf++
}
*/
if (copy_from_user (c_buf, buf, count)) { /*从用户空间拷贝数据到内核空间
unsigned long copy_from_user(void * to, const void __user * from, unsigned long n)
to:内核空间的目标缓冲区
from: 应用空间源缓冲区
n: 拷贝的长度
返回值 0: 成功 正数:没有拷贝成功的字节数
*/
ret = -EFAULT;
} else {
c_buf[63]='\0';
printk ("Received: %s\n", c_buf);
ret = count;
}
return ret;
}
static ssize_t led_read(struct file *file, char *buff,
size_t count, loff_t *offp)
{
ssize_t result = 0;
if(count > C_BUF_LEN -1 )
count = C_BUF_LEN -1;
if(count < 0)
return -EINVAL;
if (copy_to_user(buff,c_buf, count))
result = -EFAULT;
else
printk ("read %ld bytes\n", count);
result = count;
return result;
}
struct file_operations led_fops ={
.write = led_write,
.read = led_read,
};
应用部分
//app.c
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <string.h>
int main(int argc, char **argv)
{
int fd;
char buff[]=" let's go ";
fd = open("/dev/led", O_RDWR);
if (fd < 0) {
perror("open");
exit(1);
}
write (fd, buff, sizeof(buff));
memset(buff,'\0',sizeof(buff));
read (fd, buff, sizeof(buff) - 1);
printf("read buf is %s\n",buff);
return 0;
}
验证测试
$ make
$ aarch64-linux-gnu-gcc app.c
$ cp a.out led.ko /nfs/rootfs
# setenv bootargs root=/dev/nfs rw nfsroot=192.168.9.119:/nfs/rootfs,v3 console=ttyS0,115200 init=/linuxrc ip=192.168.9.9
# setenv nfsboot ext4load mmc 1:1 0x84000000 /boot/Image \; ext4load mmc 1:1 83100000 /boot/tegra210-p3448-0002-p3449-0000-b00.dtb \; booti 0x84000000 - 83100000
# run nfsboot //成功 可看到 read buf is let's go ,即读出的数据和写入的一致