【嵌入式Linux内核驱动】02_字符设备驱动

news2024/11/28 18:53:19

字符设备驱动

〇、基本知识

设备驱动分类

image-20220930092437853
(按共性分类方便管理)

1.字符设备驱动
字符设备指那些必须按字节流传输,以串行顺序依次进行访问的设备。它们是我们日常最常见的驱动了,像鼠标、键盘、打印机、触摸屏,还有点灯以及I2C、SPI、音视频都属于字符设备驱动。

字符设备不经过系统快速缓冲。

2.块设备驱动
就是存储器设备的驱动,比如 EMMC、NAND、SD 卡和 U 盘等存储设备,因为这些存储设备的特点是以存储块为基础,按块随机访问,可以用任意顺序进行访问,以块为单位进行操作,因此叫做块设备。数据的读写只能以块(通常是512B)的倍数 进行。与字符设备不同,块设备并不支持基于字符的寻址。

块设备经过设备缓冲

3.网络设备驱动
就是网络驱动,不管是有线的还是无线的,都属于网络设备驱动的范畴。按TCP/IP协议栈传输

网络设备面向数据包的接受和发送而设计,它并不对应文件系统的节点

注意:
块设备和网络设备驱动要比字符设备驱动复杂,就是因为其复杂所以半导体厂商一般都编写好了,大多数情况下都是直接可以使用的。

一个设备可以属于多种设备驱动类型,比如USB WIFI,其使用 USB 接口,属于字符设备,但是其又能上网,所以也属于网络设备驱动。

设备驱动框架

为了安全

image-20220930093630161

一切皆文件

为了标准化操作函数,方便对接工作

open read write close

字符设备框架

字符设备驱动编写三部曲

  1. 注册设备号
  2. 初始化字符设备
  3. 实现需要的文件操作

一、注册设备号

为了让内核知道这个设备是合法的,将构造的设备号注册到内核中,表明该设备号已经被占用,如果有其他驱动随后要注册该设备号,将会失败。

  • 主次设备号
  • 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 ,即读出的数据和写入的一致

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/402173.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MATLAB——信号的采样与恢复

**题目&#xff1a;**已知一个连续时间信号 其中&#xff1a;f01HZ&#xff0c;取最高有限带宽频率fm5f0。分别显示原连续时间信号波形和 3种情况下抽样信号的波形。并画出它们的幅频特性曲线&#xff0c;并对采样后的信号进行恢复。 step1.绘制出采样信号 这部分相对简单…

面试知识点梳理及相关面试题(十一)-- docker

1. Docker和虚拟机的区别 容器不需要捆绑一整套操作系统&#xff0c;它只需要满足软件运行的最小内核就行了。 传统虚拟机技术是虚拟出一整套硬件后&#xff0c;在其上运行一个完成操作系统&#xff0c;在该系统上再运行所需应用进程容器内的应用进程直接运行于宿主的内核&am…

TCP

TCP 流量控制 一般来说,我们希望数据传输的快一些,但如果对方把数据发送的过快,接收方就可能来不及接收,这就会造成数据的丢失 流量控制就是让发送方的发送速率不要太快,让接收方来得及接收 利用滑动窗口机制可以在TCP连接上实现对发送方的流量控制 TCP接收方利用自己的接收…

青岛OJ(QingdaoU/OnlineJudge)部署如何直连数据库批量修改

1.postgres数据库QingdaoU/OnlineJudge用的数据库是postgreSQL&#xff0c;一个关系型数据库。默认端口是5432&#xff0c;我们下载一个navcat 15以上的版本&#xff0c;用来连数据库。2.修改docker-compose.yml文件修改docker-compose.yml&#xff0c;手动添加一个端口&#x…

一三四——一六七

一三四、JavaScript——_DOM简介 MDNq前端参考文档&#xff1a;DOM 概述 - Web API 接口参考 | MDN (mozilla.org) 一三五、JavaScript——HelloWorld <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta h…

【JSON文件解析】JSON文件

文章目录概要&#xff1a;本期主要介绍Qt解析JSON数据格式文件的方式。一、JSON数据格式1.JSON类似于XML&#xff0c;在JSON文件中&#xff0c;有且只有一个根节点2.JSON有两种主流包含型构造字符&#xff1a;{对象}、[数组]3.JSON的值主要包括&#xff1a;对象、数组、数字、字…

你还在调戏AI,有的公司已经用ChatGPT开展业务了

近日&#xff0c;OpenAI 正式宣布开放 ChatGPT 和 Whisper 两个模型的 API&#xff0c;API 版本的ChatGPT 不仅功能更多、性能更强&#xff0c;而且还更便宜一一相当于目前 GPT-3 模型价格打一折!划重点OpenAl正式开放 ChatGPT 和 Whisper 模型的 API&#xff0c;目前 SnapChat…

运营商大数据的发展现状和趋势

互联网时代&#xff0c;流量困局始终是困扰企业的一大难题。信息杂&#xff0c;无效投入多&#xff0c;商业性营销色彩浓厚&#xff0c;都在企业和客户之间树立起一层层厚厚的障壁。选择优秀的营销手段&#xff0c;对于一个企业来说至关重要&#xff0c;反之&#xff0c;如若在…

[2.2.4]进程管理——FCFS、SJF、HRRN调度算法

文章目录第二章 进程管理FCFS、SJF、HRRN调度算法&#xff08;一&#xff09;先来先服务&#xff08;FCFS, First Come First Serve&#xff09;&#xff08;二&#xff09;短作业优先&#xff08;SJF, Shortest Job First&#xff09;对FCFS和SJF两种算法的思考&#xff08;三…

Go语言之条件判断循环语句(if-else、switch-case、for、goto、break、continue)

一、if-else条件判断语句 Go中的if-else条件判断语句跟C差不多。但是需要注意的是&#xff0c;Go中强制规定&#xff0c;关键字if和else之后的左边的花括号"{“必须和关键字在同一行&#xff0c;若使用了else if结构&#xff0c;则前段代码快的右花括号”}"必须和关…

Navicat连接centos7 mysql失败解决思路

Navicat连接centos7 mysql失败&#xff0c;可以从一下的几个问题进行逐个排查。1、远程登录权限查看远程登录权限root用户的host值为localhost时&#xff0c;说明只能进行本地登录&#xff0c;需要将host改为“%”&#xff1b;UPDATE mysql.user SET host % WHERE user root;…

Java -数据结构,Map Set

一、搜索 1.1、概念及场景 Map和set是一种专门用来进行搜索的容器或者数据结构&#xff0c;其搜索的效率与其具体的实例化子类有关。以前常见的 搜索方式有&#xff1a; 直接遍历&#xff0c;时间复杂度为O(N)&#xff0c;元素如果比较多效率会非常慢二分查找&#xff0c;时间…

Mysql多数据库之间表简单同步

方案&#xff1a;触发器优点&#xff1a; 工作效率和开发效率上有很大的提高缺点&#xff1a; 增加数据库服务器的开销在同一个mysql实例中&#xff1a;在数据库sakila中创建insert触发器use sakila; mysql> delimiter $$ mysql> create trigger insert_trigger after in…

奇安信天眼系统——探针/分析平台部署及联动

奇安信天眼系统——探针/分析平台部署及联动一 概述二 探针/分析平台部署及联动流量传感器&#xff08;探针&#xff09;分析平台一 概述 奇安信天眼主要包括威胁情报、分析平台、传感器和文件威胁鉴定器四个模块组成。 一般仅需分析平台&#xff0c;流量传感器&#xff08;探…

MySQL专题(学会就毕业)

MySQL专题0.准备sql设计一张员工信息表&#xff0c;要求如下&#xff1a;编号&#xff08;纯数字&#xff09;员工工号 (字符串类型&#xff0c;长度不超过10位)员工姓名&#xff08;字符串类型&#xff0c;长度不超过10位&#xff09;性别&#xff08;男/女&#xff0c;存储一…

研讨会回顾 | Perforce发布数字资产管理工具Helix DAM,帮助您按时按预算交付虚拟产品

2023年2月28日&#xff0c;龙智联合全球领先的数字资产管理和DevSecOps工具厂商Perforce共同举办Perforce on Tour网络研讨会——“赋能‘大’研发&#xff0c;助力‘快’交付”。 研讨会上&#xff0c;Perforce解决方案工程师Kory Luo分享了Perforce版本控制软件Helix Core的产…

Transformer:Attention is All You Need

【Transformer论文逐段精读【论文精读】】 https://www.bilibili.com/video/BV1pu411o7BE/?share_sourcecopy_web&vd_source30e93e9c70e5a43ae75d42916063bc3b论文地址&#xff1a;[1706.03762] Attention Is All You Need (arxiv.org)Transformer第一个完全依靠自我注意来…

0-1背包问题(二维数组压缩为一维数组)

0-1背包不懂的&#xff0c;可以看这篇 https://donglin.blog.csdn.net/article/details/129412502 一维dp数组 对于背包问题其实状态都是可以压缩的。 在使用二维数组的时候&#xff0c;递推公式&#xff1a;dp[i][j] max(dp[i - 1][j], dp[i - 1][j - weight[i]] value[i]…

leetcode 875. Koko Eating Bananas(koko吃香蕉)

piles数组里面是每堆香蕉里面有多少个香蕉&#xff0c; 现在有h小时可以吃香蕉&#xff0c;每小时只能吃一个堆&#xff0c; 定义每小时能吃k个香蕉&#xff0c;k > piles[i], 那么第 i 堆一次吃完&#xff0c;否则下一小时继续吃。 问k为多少时可以在h小时内把香蕉吃完。 …

Ubantu docker学习笔记(一) docker安装

文章目录一、安装准备工作二、安装docker三、问题四、docker镜像配置五、docker常见命令1.新建启动容器2.列出当前所有正在运行的容器3.退出容器4.启动已停止运行的容器5.重启容器6.停止容器7.强制停止容器 &#xff08;还在运行的&#xff09;8.删除已停止的容器重要的后台守护…