1.字符设备编程框架
实现一个硬件字符设备的驱动程序
实则就是实例化一个struct cdev类型的对象
include/linux/cdev.h
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;//操作函数集合
struct list_head list;
dev_t dev; //设备号
unsigned int count;
} __randomize_layout;
这里面只需要初始化 dev 和 *ops 这俩对象即可,其他的由内核进行维护
1.1 设备号
dev_t, 本质上就是一个unsinged int 类型数据
设备号 = 主设备号 (高12bit) + 次设备号 (低20bit)
主设备号用于区分不同类型的设备
次设备号用于区分同一类设备中的不同个体
ls /dev/ttySAC* -l
SAC0, SAC1, SAC2 的主设备号相同都是204,它们都是uart控制器,属于同一类设备
ls /dev/fb0 -l
fb0是lcd屏,它的主设备号是29
1.1.1 设备号的申请与注册有两种方式
1) 静态注册
主设备号的范围 0~255
观察系统中主设备号哪些被占用了 然后从未被使用的里面选一个
那么,如何看哪些主设备号没有被占用呢?
cat /proc/devices --> 看里面有没有被注册的设备号,都可以拿来注册
如何向内核注册一个设备好呢?
内核中提供了一个接口:
/**
* register_chrdev_region() - register a range of device numbers
* @from: the first in the desired range of device numbers; must include
* the major number.
* @count: the number of consecutive device numbers required
* @name: the name of the device or driver.
*
* Return value is zero on success, a negative error code on failure.
*/
int register_chrdev_region(dev_t from, unsigned count, const char *name)
{
struct char_device_struct *cd;
dev_t to = from + count;
dev_t n, next;
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
if (next > to)
next = to;
cd = __register_chrdev_region(MAJOR(n), MINOR(n),
next - n, name);
if (IS_ERR(cd))
goto fail;
}
return 0;
fail:
to = n;
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
}
return PTR_ERR(cd);
}
from,要注册的起始设备号
count, 连续注册的设备号个数
name, 名称
void unregister_chrdev_region(dev_t from, unsigned count)
from, 要注销的起始设备号
count,要连续注销的设备号个数
led_drv.c
#include <linux/init.c>
#include <linux/module.c>
#include <linux/fs.h>
#include <linux/cdev.h>
MODULE_LICENSE("GPL");
struct cdev led_drv;
//设备号
dev_t dev;
unsigned int major = 200;
unsigned int minor = 10;
int __init led_drv_init(void)
{
//dev = major << 20 | minor;//自己实现
dev = MKDEV(major, minor);//内核中实现
register_chrdev_region (dev, 1, "leds");
return 0;
}
void __exit led_drv_exit(void)
{
unregister_chrdev_region (dev, 1);
}
module_init(led_drv_init);
module_exit(led_drv_exit);
实验步骤:
insmod led_drv.ko
cat /proc/devices
rmmod led_drv
cat /proc/devices
2) 动态注册
让内核选一个未被使用的主设备号
注册后归你使用
内核下也提供了一个接口:
int alloc_chrdev_region(dev_t *dev, unsigned baseminor,
unsigned count, const char *name)
dev, 传出参数 用于返回注册成功的第一个设备号
baseminor, 起始次设备号
count, 连续注册的设备号个数
name, 名称
void unregister_chrdev_region(dev_t from, unsigned count)
#include <linux/init.c>
#include <linux/module.c>
#include <linux/fs.h>
#include <linux/cdev.h>
MODULE_LICENSE("GPL");
struct cdev led_drv;
//设备号
dev_t dev;
unsigned int major = 0;
unsigned int minor = 10;
int __init led_drv_init(void)
{
//dev = major << 20 | minor;//自己实现
if (major) //静态注册
{
dev = MKDEV(major, minor);//内核中实现
register_chrdev_region (dev, 1, "leds");
}
else
{
alloc_chrdev_region (&dev, 100, 1, "leds");
major = MAJOR(dev);
minor = MINOR(dev);
printk ("major = %d minor = %d\n",major,minor);
}
return 0;
}
void __exit led_drv_exit(void)
{
unregister_chrdev_region (dev, 1);
}
module_init(led_drv_init);
module_exit(led_drv_exit);
实验步骤:
insmod led_drv.ko
cat /proc/devices
rmmod led_drv
cat /proc/devices
1.2 设备对应的操作函数集合
实例化struct cdev 对象过程中的主要编码工作
集中在 struct file_operations
具体到某个字符设备文件 只需要实现以上操作函数集合的一个子集
1.3 内核中提供操作cdev的API
//初始化cdev
void cdev_init(struct cdev *cdev, const struct file_operations *fops);
{
cdev->ops = fops;
}
//注册cdev
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
p,要注册的cdev变量的地址
dev,该cdev 对应的设备号
count,连续注册的个数
//注销cdev
void cdev_del(struct cdev *p)
led_drv.c
#include <linux/init.c>
#include <linux/module.c>
#include <linux/fs.h>
#include <linux/cdev.h>
MODULE_LICENSE("GPL");
//定义一个cdev变量
struct cdev led_cdev;
//设备号
dev_t dev;
unsigned int major = 0;
unsigned int minor = 10;
int led_open (struct inode *inode, struct file *filp)
{
printk ("enter %s\n",__func__);
return 0;
}
int led_close (struct inode *inode, struct file *filp)
{
printk ("enter %s\n",__func__);
return 0;
}
struct file_operations led_fops =
{
.owner = THIS_MODULE;
.open = led_open;
.release = led_close;
}
int __init led_drv_init(void)
{
//dev = major << 20 | minor;//自己实现
if (major) //静态注册
{
dev = MKDEV(major, minor);//内核中实现
register_chrdev_region (dev, 1, "leds");
}
else
{
alloc_chrdev_region (&dev, 100, 1, "leds");
major = MAJOR(dev);
minor = MINOR(dev);
printk ("major = %d minor = %d\n",major,minor);
}
//初始化cdev
cdev_init (&led_cdev, &led_fops);
//注册cdev
cdev_add (&led_cdev, dev, 1);
return 0;
}
void __exit led_drv_exit(void)
{
//注销cdev
cdev_del (&led_cdev);
//注销设备号
unregister_chrdev_region (dev, 1);
}
module_init(led_drv_init);
module_exit(led_drv_exit);
led_test.c
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main (void)
{
int fd = open("/dev/myleds",O_RDWR);
if(fd < 0)
{
perror("open failed");
return -1;
}
printf("open successed!\n");
sleep (10);
printf("close file...\n");
close (fd);
return 0;
}
Makefile
obj-m += led_drv.o
KERNEL_PATH=/home/liuyang/driver/kernel/
all:
make -C $(KERNEL_PATH) M=$(PWD) modules
cp *.ko ../../rootfs
clean:
make -C $(KERNEL_PATH) M=$(PWD) clean
实验步骤:
1) arm-cortex_a9-linux-gnueabi-gcc led_test.c -o led_test
cp led_test ../../rootfs
2) insmod led_drv.ko
3) mknod /dev/myleds c 244 100
//注意,这里面的主设备号和次设备号必须和安装模块时,
内核分的设备号保持一致
4) ./led_test
为什么我们没有增加系统调用号的情况下,依旧可以调用到内核态的sys_open?
原因:
2. GPIO库函数的使用
2.1 电路原理图
控制led1就是控制GPIOC12管脚
2.2 cpu datasheet
GPIOALTFN0
GPIOCOUTENB
GPIOCOUT
2.3 控制GPIO管脚
1)直接操作特殊功能寄存器,使用虚拟地址
2) 使用内核中提供的GPIO库函数
2.4 GPIO库函数的使用方式
1) 申请gpio管脚
int gpio_request(unsigned gpio, const char *label)
gpio,要申请的管脚编号
label,理解成name
2) 使用gpio管脚
int gpio_direction_input(unsigned gpio)
将编号为gpio的管脚设置为输入模式
int gpio_direction_output(unsigned gpio, int value)
将编号为gpio的管脚设置为输出模式 默认输出value值
value非0,管脚输出高电平
void gpio_set_value(unsigned gpio, int value)
让编号为gpio的管脚输出 高(value 非0) /低电平
int gpio_get_value(unsigned gpio)
获取编号为gpio的管脚电平状态
高电平返回1
释放gpio管脚
void gpio_free(unsigned gpio)
vi led_drv.c
#include <linux/init.c>
#include <linux/module.c>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/gpio.h>
#include <mach/platform.h>
MODULE_LICENSE("GPL");
//定义一个cdev变量
struct cdev led_cdev;
//设备号
dev_t dev;
unsigned int major = 0;
unsigned int minor = 10;
int led_open (struct inode *inode, struct file *filp)
{
printk ("enter %s\n",__func__);
gpio_set_value (PAD_GPIO_C + 12, 0);
return 0;
}
int led_close (struct inode *inode, struct file *filp)
{
printk ("enter %s\n",__func__);
gpio_set_value (PAD_GPIO_C + 12, 1);
return 0;
}
struct file_operations led_fops =
{
.owner = THIS_MODULE;
.open = led_open;
.release = led_close;
}
int __init led_drv_init(void)
{
//dev = major << 20 | minor;//自己实现
if (major) //静态注册
{
dev = MKDEV(major, minor);//内核中实现
register_chrdev_region (dev, 1, "leds");
}
else
{
alloc_chrdev_region (&dev, 100, 1, "leds");
major = MAJOR(dev);
minor = MINOR(dev);
printk ("major = %d minor = %d\n",major,minor);
}
//初始化cdev
cdev_init (&led_cdev, &led_fops);
//注册cdev
cdev_add (&led_cdev, dev, 1);
//申请GPIO管脚
gpio_request (PAD_GPIO_C + 12, "LED1");//PAD_GPIO_C
//设置为输出模式
gpio_direction_output(PAD_GPIO_C + 12, 1);
return 0;
}
void __exit led_drv_exit(void)
{
//释放GPIO管脚
gpio_free(PAD_GPIO_C + 12);
//注销cdev
cdev_del (&led_cdev);
//注销设备号
unregister_chrdev_region (dev, 1);
}
module_init(led_drv_init);
module_exit(led_drv_exit);
建议将linux内核中自带的led驱动程序裁剪掉
cd kernel
make menuconfig
Device Drivers --->
--*-- LED Support --->
< > LED Support for GPIO connect LEDS
make uImage
使用新内核
cp uImage /tftpboot
tftp 48000000 uImage
mmc write 48000000 800 3000
setenv bootcmd mmc read 48000000 800 3000 \; bootm 48000000
saveenv
insmod led_drv.ko
mknod /dev/my_leds c 244 100
./test
-
用户态和内核态的数据交互
用户态不能访问内核空间中的数据 内核空间代码也不直接访问用户空间数据
3.1 内核中提供数据交互的第一套API
copy_to_user(void __user *to, const void *from, unsigned long n)
copy_from_user(void __user *to, const void *from, unsigned long n)
//to, 目标地址
//from,元数据地址
//n, 连续操作的字节数
返回值, 操作失败的字节数
以上两个函数与memcpy相比,多了对要访问用户空间的权限检查
3.2 内核中提供数据交互的第二套API (了解)
int put_usr(data, ptr);
int get_usr(data, ptr);
连续操作的字节个数, 取决于data变量的数据类型
练习 :
./led_test on/off
write 0 亮
1 灭
read 获取灯的亮灭状态
arm-cortex_a9_linux-gnueabi-gccc led_test.c -o led_test
cp led_test ../../rootfs/
rmmod led_drv.ko
insmod led_drv.ko
./led_test on
./led_test off
vi led_test.c
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
//./led_test on/off
int main (int argc, char** argv)
{
int cmd = 0;
int status = 0;
if (argc != 2)
{
printf ("usage:%s <on/off>\n",argv[0]);
}
int fd = open("/dev/myleds",O_RDWR);
if(fd < 0)
{
perror("open failed");
return -1;
}
pritnf ("open successed!\n");
if (strcmp (argv[1], "on") == 0)
{
cmd = 0;
} else {
cmd = 1;
}
write(fd, &cmd, sizeof(int));
read (fd, &status, sizeof(int));
if (status == 0)
{
printf ("led is on!\n");
} else {
printf ("led is off!\n");
}
close (fd);
return 0;
}
vi led_drv.c
#include <linux/init.c>
#include <linux/module.c>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/gpio.h>
#include <mach/platform.h>
#include <linux/uaccess.h>
MODULE_LICENSE("GPL");
//定义一个cdev变量
struct cdev led_cdev;
//设备号
dev_t dev;
unsigned int major = 0;
unsigned int minor = 10;
int k_status = 1;//灭
int led_open (struct inode *inode, struct file *filp)
{
#if 0
printk ("enter %s\n",__func__);
gpio_set_value (PAD_GPIO_C + 12, 0);
#endif
return 0;
}
int led_close (struct inode *inode, struct file *filp)
{
#if 0
printk ("enter %s\n",__func__);
gpio_set_value (PAD_GPIO_C + 12, 1);
#endif
return 0;
}
ssize_t led_write(struct file *filp, const char __user *buf, size_t len, loff_t *offset)
{
int k_cmd = 0;
int ret = 0;
ret = copy_from_user(&k_cmd, buf, len);
gpio_set_value(PAD_GPIO_C + 12, k_cmd);
k_status = k_cmd;
return 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, &k_status, len);
return len;
}
struct file_operations led_fops =
{
.owner = THIS_MODULE;
.open = led_open;
.release = led_close;
.write = led_write;
.read = led_read;
}
int __init led_drv_init(void)
{
//dev = major << 20 | minor;//自己实现
if (major) //静态注册
{
dev = MKDEV(major, minor);//内核中实现
register_chrdev_region (dev, 1, "leds");
}
else
{
alloc_chrdev_region (&dev, 100, 1, "leds");
major = MAJOR(dev);
minor = MINOR(dev);
printk ("major = %d minor = %d\n",major,minor);
}
//初始化cdev
cdev_init (&led_cdev, &led_fops);
//注册cdev
cdev_add (&led_cdev, dev, 1);
//申请GPIO管脚
gpio_request (PAD_GPIO_C + 12, "LED1");//PAD_GPIO_C
//设置为输出模式
gpio_direction_output(PAD_GPIO_C + 12, 1);
return 0;
}
void __exit led_drv_exit(void)
{
//释放GPIO管脚
gpio_free(PAD_GPIO_C + 12);
//注销cdev
cdev_del (&led_cdev);
//注销设备号
unregister_chrdev_region (dev, 1);
}
module_init(led_drv_init);
module_exit(led_drv_exit);
4.ioctl
类似于 open read write close lseek 也是一系统调用
ioctl 设置/获取硬件设备工作参数
uartputs ("liuyang"); //wirte
要把115200这个数据给uart控制器
int ioctl (fd, cmd) //控制LED1的亮灭 ioctl (fd, LED_ON)
ioctl (fd, LED_OFF);
int ioctl (fd, cmd, arg) //控制4栈LED
ioctl (fd, LED_ON,1/2/3/4)
vi led_test.c
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#define CMD_LDE_ON 0x10001
#define CMD_LDE_OFF 0x10002
//./led_test on/off <1/2/3/4>
int main (int argc, char** argv)
{
int cmd = 0;
int status = 0;
int index = 0; //要控制哪栈灯
if (argc != 3)
{
printf ("usage:%s <on/off> <1/2/3/4>\n",argv[0]);
return -1;
}
int fd = open("/dev/myleds",O_RDWR);
if(fd < 0)
{
perror("open failed");
return -1;
}
pritnf ("open successed!\n");
if (strcmp (argv[1], "on") == 0)
{
cmd = CMD_LDE_ON;
} else {
cmd = CMD_LDE_OFF;
}
index = strtoul (argv[2], 0, 10);
ioctl (fd, cmd, &index);
close (fd);
return 0;
}
vi led_drv.c
#include <linux/init.c>
#include <linux/module.c>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/gpio.h>
#include <mach/platform.h>
#include <linux/uaccess.h>
MODULE_LICENSE("GPL");
//定义一个cdev变量
struct cdev led_cdev;
//设备号
dev_t dev;
unsigned int major = 0;
unsigned int minor = 10;
int k_status = 1;//灭
#define CMD_LED_ON 0x10001
#define CMD_LED_OFF 0x10002
typedef struct led_desc
{
int gpio; //管脚编号
char *name; //名称
}led_desc_t;
static led_desc_t leds[] = {
{PAD_GPIO_C + 12, "LED1"},
{PAD_GPIO_C + 7, "LED2"},
{PAD_GPIO_C + 11, "LED3"},
{PAD_GPIO_C + 21, "LED4"},
};
int led_open (struct inode *inode, struct file *filp)
{
return 0;
}
int led_close (struct inode *inode, struct file *filp)
{
return 0;
}
ssize_t led_write(struct file *filp, const char __user *buf, size_t len, loff_t *offset)
{
return len;
}
ssize_t led_read(struct file *filp, char __user *buf, size_t len, loff_t *offset)
{
return len;
}
long led_ioctl (struct file *filp, unsigned int cmd, unsigned long arg)
{
int ret = 0;
int k_index = 0;
ret = copy_from_user (&k_index, (const void *)arg, 4);
if (k_index >= 5)
{
return -EINVAL;
}
switch (cmd) {
case CMD_LED_ON:
gpio_set_value (leds[k_index-1].gpio, 0);
break;
case CMD_LED_OFF:
gpio_set_value (leds[k_index-1].gpio, 1);
break;
default:
return -EINVAL;
}
return 0;
}
struct file_operations led_fops =
{
.owner = THIS_MODULE;
.open = led_open;
.release = led_close;
.write = led_write;
.read = led_read;
.unlocked_ioctl = led_ioctl;
}
int __init led_drv_init(void)
{
//dev = major << 20 | minor;//自己实现
if (major) //静态注册
{
dev = MKDEV(major, minor);//内核中实现
register_chrdev_region (dev, 1, "leds");
}
else
{
alloc_chrdev_region (&dev, 100, 1, "leds");
major = MAJOR(dev);
minor = MINOR(dev);
printk ("major = %d minor = %d\n",major,minor);
}
//初始化cdev
cdev_init (&led_cdev, &led_fops);
//注册cdev
cdev_add (&led_cdev, dev, 1);
for (; ARRAY_SIZE(leds);i++) {
//申请GPIO管脚
gpio_request (leds[i].gpio, leds[i].name);//PAD_GPIO_C
//设置为输出模式
gpio_direction_output(leds[i].gpio, 1);
}
return 0;
}
void __exit led_drv_exit(void)
{
for (; ARRAY_SIZE(leds);i++) {
//释放GPIO管脚
gpio_free(leds[i].gpio);
}
//注销cdev
cdev_del (&led_cdev);
//注销设备号
unregister_chrdev_region (dev, 1);
}
module_init(led_drv_init);
module_exit(led_drv_exit);
5.设备文件的自动创建
自动创建设备文件的必要条件
1) 根文件系统中存在mdev命令
2) 挂载了procfs sysfs
rootfs/etc/init.d/rcS
mount -a
rootfs/etc/fstab
proc /proc proc defaults 0 0
sysfs /sys sys defaults 0 0
proc, 基于内存的文件系统
向用户空间导出了内核的执行状态
/proc/PID
/proc/cpuinfo
/proc/sys/kernel/printk
sys,描述硬件驱动的模型 反映硬件之间的层次关系
基于内存的文件系统
3) 配置热插拔事件的响应动作
echo /sbin/mdev > /proc/sys/kernel/hotpulg
4) 驱动程序中产生热插拔事件
狭义: U盘插入 拔出
广义: 不但包括U盘插入 拔出
还包括/sys 目录下的变化
//影响"/sys/class" 创建了树枝
class_create(owner, name)
//影响/sys/class/name/ 创建树枝上的果实
device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...)
const char *fmt, ... :决定了将来生成的设备的文件名
例如: "ttySAC%d", i
进一步假设 i = 0
创建出的设备文件"ttySAC0"
device_destory
class_destory
vi led_drv.c
#include <linux/init.c>
#include <linux/module.c>
#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");
//定义一个cdev变量
struct cdev led_cdev;
//设备号
dev_t dev;
unsigned int major = 0;
unsigned int minor = 10;
int k_status = 1;//灭
#define CMD_LED_ON 0x10001
#define CMD_LED_OFF 0x10002
struct class *cls = NULL;
typedef struct led_desc
{
int gpio; //管脚编号
char *name; //名称
}led_desc_t;
static led_desc_t leds[] = {
{PAD_GPIO_C + 12, "LED1"},
{PAD_GPIO_C + 7, "LED2"},
{PAD_GPIO_C + 11, "LED3"},
{PAD_GPIO_C + 21, "LED4"},
};
int led_open (struct inode *inode, struct file *filp)
{
return 0;
}
int led_close (struct inode *inode, struct file *filp)
{
return 0;
}
ssize_t led_write(struct file *filp, const char __user *buf, size_t len, loff_t *offset)
{
return len;
}
ssize_t led_read(struct file *filp, char __user *buf, size_t len, loff_t *offset)
{
return len;
}
long led_ioctl (struct file *filp, unsigned int cmd, unsigned long arg)
{
int ret = 0;
int k_index = 0;
ret = copy_from_user (&k_index, (const void *)arg, 4);
if (k_index >= 5)
{
return -EINVAL;
}
switch (cmd) {
case CMD_LED_ON:
gpio_set_value (leds[k_index-1].gpio, 0);
break;
case CMD_LED_OFF:
gpio_set_value (leds[k_index-1].gpio, 1);
break;
default:
return -EINVAL;
}
return 0;
}
struct file_operations led_fops =
{
.owner = THIS_MODULE;
.open = led_open;
.release = led_close;
.write = led_write;
.read = led_read;
.unlocked_ioctl = led_ioctl;
}
int __init led_drv_init(void)
{
//dev = major << 20 | minor;//自己实现
if (major) //静态注册
{
dev = MKDEV(major, minor);//内核中实现
register_chrdev_region (dev, 1, "leds");
}
else
{
alloc_chrdev_region (&dev, 100, 1, "leds");
major = MAJOR(dev);
minor = MINOR(dev);
printk ("major = %d minor = %d\n",major,minor);
}
//初始化cdev
cdev_init (&led_cdev, &led_fops);
//注册cdev
cdev_add (&led_cdev, dev, 1);
//自动创建设备文件
//结果是"/sys/class/leds" 树枝
cls = class_create (THIS_MODULE, "leds");
/*/sys/class/leds/myleds/文件夹出来了*/
device_create (cls, NULL, dev, NULL, "myleds");
for (; ARRAY_SIZE(leds);i++) {
//申请GPIO管脚
gpio_request (leds[i].gpio, leds[i].name);//PAD_GPIO_C
//设置为输出模式
gpio_direction_output(leds[i].gpio, 1);
}
return 0;
}
void __exit led_drv_exit(void)
{
for (; ARRAY_SIZE(leds);i++) {
//释放GPIO管脚
gpio_free(leds[i].gpio);
}
//销毁设备文件
device_destroy (cls, dev);
class_destroy (cls);
//注销cdev
cdev_del (&led_cdev);
//注销设备号
unregister_chrdev_region (dev, 1);
}
module_init(led_drv_init);
module_exit(led_drv_exit);
字符设备驱动程序:
申请注册设备号
struct cdev 变量的定义 初始化 注册
操作函数集合 (变化)
设备文件的创建
...
设备文件的销毁
cdev注销
设备号的注销