知识理论:
1. 创建设备节点有两种方法:(1)手动:mknod (2)class_create() device_create()
2. 手动创建:mknod 设备文件 设备类型 主设备号 次设备号
获取设备号两种方法:(1)驱动printk打印 (2)/sys/class/xxx/uevent
3. 自动创建:
insmod led_drv.ko 时候,udev 自动帮我们在/dev 目录下创建设备节点
rmmod led_drv 时候,udev 自动帮我们在/dev 目录下删除设备节点
思路:若要编写一个能用 udev 管理的设备驱动,需要在驱动代码中调用 class_create()为设备创建一个 class 类,
再调用 device_create()为每个设备创建对应的设备。
------------------------------------
struct class *__class_create(struct module *owner, const char *name,
struct lock_class_key *key)
参数:
owner:class 的所有者,默认写 THIS_MODULE
name:自定义 class 的名字,会显示在/sys/class 目录当中
返回值:
成功:就返回创建好的 class 指针
失败:就返回错误码指针
如何判断当前的指针为错误码,使用 IS_ERR 函数进行判断。
-------------------------------------
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...)
参数:
class:创建 device 是属于哪个类
parent:默认为 NULL
devt:设备号,设备号必须正确,因为这个函数会在/dev 目录下帮我们自动创建设备文件
drvdata:默认为 NULL
fmt:设备的名字,如果创建成功,就可以在/dev 目录看到该设备的名字
返回值:
成功:就返回创建好的设备指针
失败:就返回错误码指针
----------------------------------------
3. 参考三星公司创建类与设备的方法
#include <linux/device.h>
static struct class *adb_dev_class;
static struct device *adb_dev_device;
static int __init adbdev_init(void)
{
...............
if (register_chrdev(ADB_MAJOR, "adb", &adb_fops))
{
printk(KERN_ERR "adb: unable to get major %d\n", ADB_MAJOR);
return;
}
adb_dev_class = class_create(THIS_MODULE, "adb");
if (IS_ERR(adb_dev_class))
return PTR_ERR(adb_dev_class);
adb_dev_device=device_create(adb_dev_class, NULL, MKDEV(ADB_MAJOR, 0), NULL, "adb");
if (IS_ERR(adb_dev_device))
{
class_destroy(adb_dev_class);
return PTR_ERR(adb_dev_device);
}
return 0;
}
驱动程序:led_drv.c
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/device.h>
static struct cdev gec6818_led_cdev; //字符设备结构体
static dev_t led_num=0; //设备号
static struct class *led_class;
static struct device *led_device;
static int gec6818_led_open (struct inode * inode, struct file *file)
{
printk("gec6818_led_open \n");
return 0;
}
static int gec6818_led_release (struct inode * inode, struct file *file)
{
printk("gec6818_led_release \n");
return 0;
}
static ssize_t gec6818_led_write (struct file * file, const char __user * buf, size_t len, loff_t * off)
{
int rt;
char kbuf[64]={0};
//判断当前len是否合法
if(len > sizeof kbuf)
return -EINVAL; //返回参数无效错误码
//从用户空间拷贝数据
rt = copy_from_user(kbuf,buf,len);
//获取成功复制的字节数
len = len - rt;
printk("gec6818_led_write,kbuf[%s],len[%d]\n",kbuf,len);
return len;
}
static ssize_t gec6818_led_read (struct file *file, char __user *buf, size_t len, loff_t * offs)
{
int rt;
char kbuf[5]={'1','2','3','4','\0'};
//判断当前len是否合法
if(len > sizeof kbuf)
return -EINVAL; //返回参数无效错误码
//从内核空间拷贝到用户空间
rt = copy_to_user(buf,kbuf,len);
//获取成功复制的字节数
len = len - rt;
printk("gec6818_led_read,__user buf[%s],len[%d]\n",buf,len);
return len;
}
static const struct file_operations gec6818_led_fops = {
.owner = THIS_MODULE,
.write = gec6818_led_write,
.open = gec6818_led_open,
.release = gec6818_led_release,
.read = gec6818_led_read,
};
//入口函数
static int __init gec6818_led_init(void)
{
int rt=0;
//动态申请设备号
rt=alloc_chrdev_region(&led_num,0,1,"gec6818_leds");
if(rt < 0)
{
printk("alloc_chrdev_region fail\n");
return rt;
}
printk("led_major = %d\n",MAJOR(led_num));
printk("led_minor = %d\n",MINOR(led_num));
//字符设备初始化
cdev_init(&gec6818_led_cdev,&gec6818_led_fops);
//字符设备添加到内核
rt = cdev_add(&gec6818_led_cdev,led_num,1);
if(rt < 0)
{
printk("cdev_add fail\n");
goto fail_cdev_add;
}
//在sys/class目录创建文件夹gec6818_leds
//THIS_MODULE:class 的所有者,默认写 THIS_MODULE
//class_led:自定义 class 的名字,会显示在/sys/class 目录当中
led_class = class_create(THIS_MODULE,"class_led1");
if(IS_ERR(led_class))
{
rt = PTR_ERR(led_class);
printk("class_create fail\n");
goto class_create_fail;
}
//创建设备
/*
struct device *device_create(struct class *class, struct device *parent,
dev_t devt, void *drvdata, const char *fmt, ...)
class:创建 device 是属于哪个类
parent:默认为 NULL
devt:设备号,设备号必须正确,因为这个函数会在/dev 目录下帮我们自动创建设备文件
drvdata:默认为 NULL
fmt:设备的名字,如果创建成功,就可以在/dev 目录看到该设备的名字 */
led_device = device_create(led_class,NULL,led_num,NULL,"wgh_led1");
if(IS_ERR(led_device))
{
rt = PTR_ERR(led_device);
printk("device_create fail\n");
goto device_create_fail;
}
printk("gec6818 led init\n");
return 0;
// 请注意goto执行的顺序,即释放资源规则:后创建先释放。
device_create_fail:
class_destroy(led_class);
class_create_fail:
cdev_del(&gec6818_led_cdev);
fail_cdev_add:
unregister_chrdev_region(led_num,1);
return rt;
}
//出口函数
static void __exit gec6818_led_exit(void)
{
device_destroy(led_class,led_num);
class_destroy(led_class);
cdev_del(&gec6818_led_cdev);
unregister_chrdev_region(led_num,1);
printk("gec6818 led exit\n");
}
//驱动程序的入口:insmod led_drv.ko调用module_init,module_init又会去调用gec6818_led_init。
module_init(gec6818_led_init);
//驱动程序的出口:rmsmod led_drv调用module_exit,module_exit又会去调用gec6818_led_exit。
module_exit(gec6818_led_exit)
//模块描述
MODULE_AUTHOR("stephenwen88@163.com"); //作者信息
MODULE_DESCRIPTION("gec6818 led driver"); //模块功能说明
MODULE_LICENSE("GPL"); //许可证:驱动遵循GPL协议
测试程序:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char **argv)
{
int fd=-1;
int len;
char buf[64]="hello teacher.wen";
//打开gec6818_leds设备
fd = open("/dev/gec6818_leds",O_RDWR);
if(fd < 0)
{
perror("open /dev/gec6818_leds:");
return fd;
}
sleep(2);
write(fd,buf,strlen(buf));
sleep(2);
len = read(fd,buf,5);
if(len > 0)
printf("read buf:%s\n len=%d\n",buf,len);
sleep(2);
close(fd);
return 0;
}