Driver:LED灯操作、内核空间和用户空间的数据交互、ioctl函数、设备文件安装与销毁
2017-03-04 21:33
459 查看
1、LED灯操作
用户空间执行open时,led1亮;执行close时,led1灭。
电路原理图:
led1 ---> GPIOC12
如何控制:
cpu datasheet 特殊功能寄存器
在linux下操作GPIO管脚有两种方式:
1)像裸板开发一样通过'指针 + 位操作'来完成特殊功能寄存器的设置。
问题在于:/* 要将特殊功能寄存器的物理地址转换成虚拟地址。*/
2)通过GPIO库函数的方式
在内核中提供了操作GPIO管脚的库函数,其使用有固定的套路:
a)申请管脚;
intgpio_request(unsigned gpio, const char *label);
功能:向内核申请GPIO资源
gpio:GPIO软件编号
name:标识
b)使用管脚;
intgpio_direction_output(int gpio, int value);
功能:设置GPIO为输出口,并且输出value值
gpio:GPIO软件编号
value:管脚状态,1和0代表高低电平
intgpio_direction_inputput(int gpio);
功能:设置GPIO为输入口
gpio:GPIO软件编号
voidgpio_set_value(int gpio, int value);
功能:设置GPIO的状态为value
gpio:GPIO软件编号
value:管脚状态
intgpio_get_value(int gpio);
功能:获取GPIO的状态,返回值为状态信息
gpio:GPIO软件编号
返回值:非0 - 高电平;0 - 低电平
c)释放管脚;
voidgpio_free(unsigned gpio);
功能:释放GPIO资源
gpio:GPIO软件编号
<GPIO软件编号>
验证:
把内核自带的led驱动去掉,重新烧写内核到开发板,再验证新的led驱动。
$:' make menuconfig
Device Drivers --->
Character devices --->
< > My led driver // 没有就不用管
-*- LED Support --->
< > LED Support for GPIO connected LEDs
< > PWM driven LED Support
[ ] LED Trigger support
$:' make uImage -j4
$:'cp arch/arm/boot/uImage /tftpboot
#:'tftp 48000000 uImage
#:'mmc write 48000000 800 3000
#:'re
#:'insmod char_drv.ko
#:'mknod /dev/leds c 244 100
// 主设备号要进行查看:cat /proc/devices
#:'./test
2、内核空间和用户空间的数据交互
用户空间不能直接操作内核空间地址中的数据;
内核空间不能直接操作用户空间地址中的数据。
write (fd, buf, len);
buf:源数据区,用户空间地址(0~3G)
xxx write(struct file *, const char __user *buf, size_t, loff_t *) {
char tmp = *buf; // 不允许的操作。
}
有需求时,可以'通过以下函数,将用户空间数据拷贝到 → 内核空间':
'copy_from_user'
intcopy_from_user(void *to, const void __user *from, int n);
功能:用户空间 → 内核空间
参数:
@to:数据拷到哪里去
@from:数据从哪里拷
@n:字节数
返回值: 成功 - 0 ,失败 - 未拷贝成功的字节数。
有需求时,可以'通过以下函数,将内核空间数据拷贝到 → 用户空间':
'copy_to_user'
intcopy_to_user(void *to, const void __user *from, int n);
功能:内核空间 → 用户空间
参数:
@to:数据拷到哪里去
@from:数据从哪里拷
@n:字节数
返回值: 成功 - 0 ,失败 - 未拷贝成功的字节数。
还有一套函数,可以实现 用户空间 ←→ 内核空间:
'get_user'
intget_user(data, ptr);
data: 可以是字节、半字、字、双字类型的内核变量
ptr: 用户空间内存指针
返回: 成功返回0,失败返回非0
'put_user'
unsigned longput_user(data, ptr);
data: 可以是字节、半字、字、双字类型的内核变量
ptr: 用户空间内存指针
返回: 成功返回0,失败返回非0
3、设备控制操作函数 ioctl
'ioctl:设置/获取设备的属性'
uart驱动程序:
uart_puts ---> write
uart_gets ---> read
如果想把uart控制器的波特率调整为9600
用户空间函数:
#include <sys/ioctl.h>
intioctl(int d, int request, ...);
内核中在file_operations中设计了此函数:
'unlocked_ioctl'
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
功能:内核中控制设备属性
参数:
@inode: 待操作的设备文件inode结构体指针
@filp: 待操作的设备文件file结构体指针
@cmd: 接收到的设备控制命令
@arg: 控制命令可能携带的参数
返回值: 成功返回0,失败返回负值;如果access_ok失败,ioctl操作应该返回-EFAULT
// -EFAULT / -EINVAL 内核中的错误信息宏。
用户空间:
ioctl (fd, cmd, val);
ioctl (fd, cmd);
-----------------------------------
内核空间:
unlocked_ioctl ();
4、设备文件的安装与销毁
模块安装成功,自动创建对应的设备文件;
模块卸载成功,自动销毁对应的设备文件。
需要的步骤:
1)通过busybox中生成的命令中要确认有 mdev
#:'which mdev
2)etc/inid.d/rcS
mount -a
3)etc/fstab
proc /proc proc defaults 0 0
sysfs /sys sysfs defaults 0 0
4)etc/inid.d/rcS // 规定了产生热插拔事件执行哪个命令
#热插拔事件处理 创建设备文件
echo /sbin/mdev >/proc/sys/kernel/hotplug
热插拔事件:
1)U盘的插入和拔出;
2)/sys/目录下文件的变化。
mdev会根据/sys/目录下文件的变化自动创建设备文件。
5)让/sys/产生变化
'class_create'
#defineclass_create(owner, name);
功能:创建树形结构中的一个树枝(设备文件)
参数:
@owner:THIS_MODULE
@name:类的名称
eg: struct class *mycls = class_create(THIS_MODULE,“tarena”);
'device_create'
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...);
功能:创建树形结构上的果实(设备节点)
参数:
@class:该设备属于哪个类,该果实结在哪个树枝上
@parent:父设备
@devt:设备号
@drvdata:创建设备时传递的参数,通常给NULL
@*fmt:...
创建的果实的名称,将来mdev帮助自动创建的设备文件的名称
"led%d", i
假如i=0 "led0"
"led%s", str
假如str="aaa" "ledaaa"
eg: device_create(mycls, NULL, MKDEV(major, minor), NULL,"myled");
'device_destroy'
voiddevice_destroy(struct class *class, dev_t devt);
'class_destroy'
voidclass_destroy(struct class *cls);
// 逆序销毁设备文件。
练习:
./test <1/2/3/4> <on/off>
用户空间执行open时,led1亮;执行close时,led1灭。
电路原理图:
led1 ---> GPIOC12
如何控制:
cpu datasheet 特殊功能寄存器
在linux下操作GPIO管脚有两种方式:
1)像裸板开发一样通过'指针 + 位操作'来完成特殊功能寄存器的设置。
问题在于:/* 要将特殊功能寄存器的物理地址转换成虚拟地址。*/
2)通过GPIO库函数的方式
在内核中提供了操作GPIO管脚的库函数,其使用有固定的套路:
a)申请管脚;
intgpio_request(unsigned gpio, const char *label);
功能:向内核申请GPIO资源
gpio:GPIO软件编号
name:标识
b)使用管脚;
intgpio_direction_output(int gpio, int value);
功能:设置GPIO为输出口,并且输出value值
gpio:GPIO软件编号
value:管脚状态,1和0代表高低电平
intgpio_direction_inputput(int gpio);
功能:设置GPIO为输入口
gpio:GPIO软件编号
voidgpio_set_value(int gpio, int value);
功能:设置GPIO的状态为value
gpio:GPIO软件编号
value:管脚状态
intgpio_get_value(int gpio);
功能:获取GPIO的状态,返回值为状态信息
gpio:GPIO软件编号
返回值:非0 - 高电平;0 - 低电平
c)释放管脚;
voidgpio_free(unsigned gpio);
功能:释放GPIO资源
gpio:GPIO软件编号
<GPIO软件编号>
/* gpio group pad start num. */ enum { PAD_GPIO_A = (0 * 32), PAD_GPIO_B = (1 * 32), PAD_GPIO_C = (2 * 32), // GPIOC12 == PAD_GPIO_C + 12 == 64+12 == 76 PAD_GPIO_D = (3 * 32), PAD_GPIO_E = (4 * 32), PAD_GPIO_ALV = (5 * 32), };
/** 代码演示 - led驱动 **/ #include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/gpio.h> #include <mach/platform.h> MODULE_LICENSE ("GPL"); dev_t dev; // 存储设备号 unsigned int major = 0; // 主设备号 unsigned int minor = 100; // 次设备号 /* 1. 定义cdev类型的变量 */ struct cdev led_cdev; int led_open (struct inode* inode, struct file* filp) { gpio_set_value (PAD_GPIO_C + 12, 0); // 低电平:亮 return 0; } int led_release (struct inode* inode, struct file* filp) { gpio_set_value (PAD_GPIO_C + 12, 1); // 高电平:灭 return 0; } struct file_operations led_fops = { .owner = THIS_MODULE, .open = led_open, .release = led_release, }; int __init char_drv_init (void) { if (major) { /* 静态注册 */ dev = MKDEV (major, minor); register_chrdev_region (dev, 1, "jiangyuan-1610"); } else { /* 动态注册 */ alloc_chrdev_region (&dev, minor, 1, "jiangyuan-alloc-1610"); } /* 2. 初始化cdev */ cdev_init (&led_cdev, &led_fops); /* 3. 注册cdev */ cdev_add (&led_cdev, dev, 1); /* 申请GPIO管脚 */ gpio_request (PAD_GPIO_C + 12, "LED1"); // name自定 /* 配置管脚为输出模式 */ gpio_direction_output (PAD_GPIO_C + 12, 1); // 高电平:灭 return 0; } void __exit char_drv_exit (void) { /* 注销cdev */ cdev_del (&led_cdev); /* 注销设备号 */ unregister_chrdev_region (dev, 1); /* 释放GPIO管脚 */ gpio_free (PAD_GPIO_C + 12); } module_init (char_drv_init); module_exit (char_drv_exit);
验证:
把内核自带的led驱动去掉,重新烧写内核到开发板,再验证新的led驱动。
$:' make menuconfig
Device Drivers --->
Character devices --->
< > My led driver // 没有就不用管
-*- LED Support --->
< > LED Support for GPIO connected LEDs
< > PWM driven LED Support
[ ] LED Trigger support
$:' make uImage -j4
$:'cp arch/arm/boot/uImage /tftpboot
#:'tftp 48000000 uImage
#:'mmc write 48000000 800 3000
#:'re
#:'insmod char_drv.ko
#:'mknod /dev/leds c 244 100
// 主设备号要进行查看:cat /proc/devices
#:'./test
2、内核空间和用户空间的数据交互
用户空间不能直接操作内核空间地址中的数据;
内核空间不能直接操作用户空间地址中的数据。
write (fd, buf, len);
buf:源数据区,用户空间地址(0~3G)
xxx write(struct file *, const char __user *buf, size_t, loff_t *) {
char tmp = *buf; // 不允许的操作。
}
有需求时,可以'通过以下函数,将用户空间数据拷贝到 → 内核空间':
'copy_from_user'
intcopy_from_user(void *to, const void __user *from, int n);
功能:用户空间 → 内核空间
参数:
@to:数据拷到哪里去
@from:数据从哪里拷
@n:字节数
返回值: 成功 - 0 ,失败 - 未拷贝成功的字节数。
有需求时,可以'通过以下函数,将内核空间数据拷贝到 → 用户空间':
'copy_to_user'
intcopy_to_user(void *to, const void __user *from, int n);
功能:内核空间 → 用户空间
参数:
@to:数据拷到哪里去
@from:数据从哪里拷
@n:字节数
返回值: 成功 - 0 ,失败 - 未拷贝成功的字节数。
/** 代码演示 - char_dev.c **/ #include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/gpio.h> #include <mach/platform.h> #include <linux/uaccess.h> MODULE_LICENSE ("GPL"); dev_t dev; // 存储设备号 unsigned int major = 0; // 主设备号 unsigned int minor = 100; // 次设备号 int led_status = 0; /* 1. 定义cdev类型的变量 */ struct cdev led_cdev; int led_open (struct inode* inode, struct file* filp) { return 0; } int led_release (struct inode* inode, struct file* filp) { return 0; } /* write (fd, buf, len) */ ssize_t led_write (struct file* filp, const char __user* buf, size_t len, loff_t* offset) { int cmd = 0; int ret = 0; /* 拷贝用户空间的值,到内核空间 */ ret = copy_from_user (&cmd, buf, len); printk ("write - ret = %d\n", ret); gpio_set_value (PAD_GPIO_C + 12, cmd); led_status = cmd; // 记录灯的状态 return len; } /* read (fd, buf, len); */ ssize_t led_read (struct file* filp, char __user* buf, size_t len, loff_t* offset) { int ret = 0; ret = copy_to_user (buf, &led_status, len); return len; } struct file_operations led_fops = { .owner = THIS_MODULE, .open = led_open, .release = led_release, .write = led_write, .read = led_read, }; int __init char_drv_init (void) { if (major) { /* 静态注册 */ dev = MKDEV (major, minor); register_chrdev_region (dev, 1, "jiangyuan-1610"); } else { /* 动态注册 */ alloc_chrdev_region (&dev, minor, 1, "jiangyuan-alloc-1610"); } /* 2. 初始化cdev */ cdev_init (&led_cdev, &led_fops); /* 3. 注册cdev */ cdev_add (&led_cdev, dev, 1); /* 申请GPIO管脚 */ gpio_request (PAD_GPIO_C + 12, "LED1"); // name自定 gpio_request (PAD_GPIO_C + 11, "LED2"); // name自定 gpio_request (PAD_GPIO_C + 7, "LED3"); // name自定 gpio_request (PAD_GPIO_B + 26, "LED4"); // name自定 /* 配置管脚为输出模式 */ gpio_direction_output (PAD_GPIO_C + 12, 1); // 高电平:灭 gpio_direction_output (PAD_GPIO_C + 11, 1); // 高电平:灭 gpio_direction_output (PAD_GPIO_C + 7, 1); // 高电平:灭 gpio_direction_output (PAD_GPIO_B + 26, 1); // 高电平:灭 return 0; } void __exit char_drv_exit (void) { /* 注销cdev */ cdev_del (&led_cdev); /* 注销设备号 */ unregister_chrdev_region (dev, 1); /* 释放GPIO管脚 */ gpio_free (PAD_GPIO_C + 12); gpio_free (PAD_GPIO_C + 11); gpio_free (PAD_GPIO_C + 7); gpio_free (PAD_GPIO_B + 26); } module_init (char_drv_init); module_exit (char_drv_exit); /** 测试代码 - test.c **/ #include <stdio.h> #include <fcntl.h> /* ./test on/off */ int main (int argc, char* argv[]) { int fd = 0; int cmd = 0; int status = 0; int res = 0; if (argc != 2){ printf ("usage: %s <on/off>\n", argv[0]); return -1; } fd = open ("/dev/leds", O_RDWR); // 注意此处的权限问题 if (fd < 0) { perror ("open /dev/leds failed...\n"); return -1; } printf ("open /dev/leds successed...\n"); if (! strcmp (argv[1], "on")) { cmd = 0; res = write (fd, &cmd, sizeof (int)); // 亮灯 if (-1 == res) { perror ("write failed:\n"); return 0; } } else if (! strcmp (argv[1], "off")) { cmd = 1; res = write (fd, &cmd, sizeof (int)); // 灭灯 if (-1 == res) { perror ("write failed:\n"); return 0; } } read (fd, &status, sizeof (int)); if (status == 0) { printf ("now led1~4 are on...\n"); } else { printf ("now led1~4 are off...\n"); } sleep (1); printf ("now is closing device!\n"); sleep (3); close (fd); return 0; }
还有一套函数,可以实现 用户空间 ←→ 内核空间:
'get_user'
intget_user(data, ptr);
data: 可以是字节、半字、字、双字类型的内核变量
ptr: 用户空间内存指针
返回: 成功返回0,失败返回非0
'put_user'
unsigned longput_user(data, ptr);
data: 可以是字节、半字、字、双字类型的内核变量
ptr: 用户空间内存指针
返回: 成功返回0,失败返回非0
3、设备控制操作函数 ioctl
'ioctl:设置/获取设备的属性'
uart驱动程序:
uart_puts ---> write
uart_gets ---> read
如果想把uart控制器的波特率调整为9600
用户空间函数:
#include <sys/ioctl.h>
intioctl(int d, int request, ...);
内核中在file_operations中设计了此函数:
'unlocked_ioctl'
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
功能:内核中控制设备属性
参数:
@inode: 待操作的设备文件inode结构体指针
@filp: 待操作的设备文件file结构体指针
@cmd: 接收到的设备控制命令
@arg: 控制命令可能携带的参数
返回值: 成功返回0,失败返回负值;如果access_ok失败,ioctl操作应该返回-EFAULT
// -EFAULT / -EINVAL 内核中的错误信息宏。
用户空间:
ioctl (fd, cmd, val);
ioctl (fd, cmd);
-----------------------------------
内核空间:
unlocked_ioctl ();
4、设备文件的安装与销毁
模块安装成功,自动创建对应的设备文件;
模块卸载成功,自动销毁对应的设备文件。
需要的步骤:
1)通过busybox中生成的命令中要确认有 mdev
#:'which mdev
2)etc/inid.d/rcS
mount -a
3)etc/fstab
proc /proc proc defaults 0 0
sysfs /sys sysfs defaults 0 0
4)etc/inid.d/rcS // 规定了产生热插拔事件执行哪个命令
#热插拔事件处理 创建设备文件
echo /sbin/mdev >/proc/sys/kernel/hotplug
热插拔事件:
1)U盘的插入和拔出;
2)/sys/目录下文件的变化。
mdev会根据/sys/目录下文件的变化自动创建设备文件。
5)让/sys/产生变化
'class_create'
#defineclass_create(owner, name);
功能:创建树形结构中的一个树枝(设备文件)
参数:
@owner:THIS_MODULE
@name:类的名称
eg: struct class *mycls = class_create(THIS_MODULE,“tarena”);
'device_create'
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...);
功能:创建树形结构上的果实(设备节点)
参数:
@class:该设备属于哪个类,该果实结在哪个树枝上
@parent:父设备
@devt:设备号
@drvdata:创建设备时传递的参数,通常给NULL
@*fmt:...
创建的果实的名称,将来mdev帮助自动创建的设备文件的名称
"led%d", i
假如i=0 "led0"
"led%s", str
假如str="aaa" "ledaaa"
eg: device_create(mycls, NULL, MKDEV(major, minor), NULL,"myled");
'device_destroy'
voiddevice_destroy(struct class *class, dev_t devt);
'class_destroy'
voidclass_destroy(struct class *cls);
// 逆序销毁设备文件。
/** 代码演示 - char_drv.c **/ #include <linux/init.h> #include <linux/module.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/gpio.h> #include <mach/platform.h> #include <linux/uaccess.h> #include <linux/device.h> MODULE_LICENSE ("GPL"); #define CMD_LED_ON 0x10001 #define CMD_LED_OFF 0x10002 dev_t dev; // 存储设备号 unsigned int major = 0; // 主设备号 unsigned int minor = 100; // 次设备号 int led_status = 0; struct class *cls = NULL; // 保存class_create的返回值 /* 1. 定义cdev类型的变量 */ struct cdev led_cdev; int led_open (struct inode* inode, struct file* filp) { return 0; } int led_release (struct inode* inode, struct file* filp) { return 0; } /* write (fd, buf, len) */ ssize_t led_write (struct file* filp, const char __user* buf, size_t len, loff_t* offset) { return 0; } /* read (fd, buf, len); */ ssize_t led_read (struct file* filp, char __user* buf, size_t len, loff_t* offset) { return 0; } /* ioctl (fd, cmd, arg) */ long led_ioctl (struct file* filp, unsigned int cmd, unsigned long arg) { int ret = 0; int index = 0; ret = copy_from_user (&index, (void*)arg, 4); switch (cmd) { case CMD_LED_ON: if (index == 1) gpio_set_value (PAD_GPIO_C + 12, 0); else if (index == 2) gpio_set_value (PAD_GPIO_C + 7, 0); break; case CMD_LED_OFF: if (index == 1) gpio_set_value (PAD_GPIO_C + 12, 1); else if (index == 2) gpio_set_value (PAD_GPIO_C + 7, 1); break; default: return -EINVAL; } return 0; } struct file_operations led_fops = { .owner = THIS_MODULE, .open = led_open, .release = led_release, .write = led_write, .read = led_read, .unlocked_ioctl = led_ioctl, }; int __init char_drv_init (void) { if (major) { /* 静态注册 */ dev = MKDEV (major, minor); register_chrdev_region (dev, 1, "jiangyuan-1610"); } else { /* 动态注册 */ alloc_chrdev_region (&dev, minor, 1, "jiangyuan-alloc-1610"); } /* 2. 初始化cdev */ cdev_init (&led_cdev, &led_fops); /* 3. 注册cdev */ cdev_add (&led_cdev, dev, 1); /* 影响/sys/class/目录 */ cls = class_create (THIS_MODULE, "myleds"); // 创建树枝 /* 影响/sys/class/myleds/目录 */ device_create (cls, NULL, dev, NULL, "leds"); // 创建果实 /* 申请GPIO管脚 */ gpio_request (PAD_GPIO_C + 12, "LED1"); // name自定 gpio_request (PAD_GPIO_C + 7, "LED3"); // name自定 /* 配置管脚为输出模式 */ gpio_direction_output (PAD_GPIO_C + 12, 1); // 高电平:灭 gpio_direction_output (PAD_GPIO_C + 7, 1); // 高电平:灭 return 0; } void __exit char_drv_exit (void) { /* 注销cdev */ cdev_del (&led_cdev); /* 注销设备号 */ unregister_chrdev_region (dev, 1); /* 释放GPIO管脚 */ gpio_free (PAD_GPIO_C + 12); gpio_free (PAD_GPIO_C + 7); /* 销毁果实 */ device_destroy (cls, dev); /* 销毁树枝 */ class_destroy (cls); } module_init (char_drv_init); module_exit (char_drv_exit);
练习:
./test <1/2/3/4> <on/off>
相关文章推荐
- 用户空间和内核空间文件操作 file_operations
- linux驱动开发之字符设备--内核和用户空间数据的交换(read write)
- linux驱动开发之字符设备--内核和用户空间数据的交换(sysfs)
- 用户空间与内核空间数据交互
- linux驱动开发之字符设备--内核和用户空间数据的交换(ioctl)
- 内核、设备、驱动、文件系统、内核空间、用户空间
- 如何使用proc文件系统让用户空间和内核空间进行交互
- oracle基本操作语法(建表空间,建用户,授权,数据导入导出)
- 用户空间和内核空间传递数据:get_user;put_user;copy_to_user;copy_from_user
- 在 Linux 下用户空间与内核空间数据交换的方式: procfs、seq_file、debugfs和relayfs
- mmap--最简单的测试程序(用户空间与内核空间数据交换&&用户态和内核态的数据交换用例)
- 在 Linux 下用户空间与内核空间数据交换的方式
- linux下用户空间与内核空间数据交换方式
- 创建Oracle表空间,指定用户默认表空间,增加数据文件的步骤
- 在 Linux 下用户空间与内核空间数据交换的方式,第 2 部分: procfs、seq_file、debugfs和relayfs
- 【转载】在 Linux 下用户空间与内核空间数据交换的方式,第 2 部分: procfs、seq_file、debugfs和relayfs
- 用户空间与内核空间数据交换的方式(1)------debugfs
- 对Linux用户空间与内核空间数据传递的几点理解和总结(ZZ)
- 在 Linux 下用户空间与内核空间数据交换的方式
- 在 Linux 下用户空间与内核空间数据交换的方式,第 2 部分: procfs、seq_file、debugfs和relayfs