1:上一章我们使用了register_chrdev这个函数来向内核注册字符设备
static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
这个函数需要三个参数:主设备号、name、file_operations结构体
使用register_chrdev这个函数无法设置次设备号;
下面我们介绍一个新的函数:
int register_chrdev_region(dev_t from, unsigned count, const char *name)
这个函数也需要三个参数:设备号、同类设备的个数、name比如说我们led设备s5pv210中有4颗led设备,
我们led设备的主设备号可以设置为250,个数4个,name:led_dev
注意这里的参数from ,因为主设备号与次设备号在linux内核中共同组成一个4字节的int类型的数,第16位或者其它为次设备号,高16位或者其它为主设备号;
linux内核为我们提供了三个宏来确定from、主设备号、次设备号
MKDEV、MAJOR、NIMOR,这么是这三个宏在linux内核中定义的用法:
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
比如说我们的主设备号为250 第一个次设备号为0,那么from应该赋值的参数为MKDEV(250, 0)
知道dev的话 主设备号为 MAJOR(dev)、次设备号为:MINOR(dev)
这个函数中只设置了主设备号、次设备号、与name还没有绑定file_operations结构体,所以我们还需要一个函数来把主设备号,与file_operations绑定起来;
这里就要用到:cdev、cdev_alloc、cdv_init、cdev_add、cdev_del
cdev类型是下面这个结构体中,两个变量很重要一个是dev_t dev设备号,file_operations
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
void cdev_init(struct cdev *, const struct file_operations *);
cdev_init函数,这个函数是吧cdev与我们写的file_operations绑定起来完成注册;
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, 0, sizeof *cdev);
INIT_LIST_HEAD(&cdev->list);
kobject_init(&cdev->kobj, &ktype_cdev_default);
cdev->ops = fops;
}
cdev_add函数,这个函数才是真正的注册函数
这个函数有三个参数:cdev的结构体指针,dev设备号,count几个设备;
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
p->dev = dev;
p->count = count;
return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
}
以上几个是驱动设备注册的几个函数;
下面要到设备的注销,也是分两步的
涉及到两个函数:
cdev_del这个函数只需要一个参数就是cdev指针
void cdev_del(struct cdev *p)
{
cdev_unmap(p->dev, p->count);
kobject_put(&p->kobj);
}
unregister_chrdev_region函数
这个函数需要两个参数一个是设备号,一个是注册的同类设备的个数
void unregister_chrdev_region(dev_t from, unsigned count)
{
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;
kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
}
上面为进行注册的必须要提前知道主设备号,
下面我们来进行一下代码实战:
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <plat/map-base.h>
#include <plat/map-s5p.h>
#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>
#include <linux/ioport.h>
#include <linux/string.h>
#include <asm/io.h>
#include <linux/cdev.h>
//#define MYMAJOR 200
//#define MYNAME "LED_DEVICE"
#define MYDEV 250
#define LED_COUNT 1
#define GPJ0_PA_base 0xE0200240
#define GPJ0CON_PA_OFFSET 0x0
struct cdev my_led_cdev;
unsigned int *pGPJ0CON;
unsigned int *pGPJ0DAT;
static char kbuf[100];
static int mymojor;
static int led_dev_open(struct inode *inode, struct file *file)
{
printk(KERN_INFO "led_dev open
");
return 0;
}
static int led_dev_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "led_dev close
");
return 0;
}
ssize_t led_dev_read(struct file *file, char __user *buf, size_t size, loff_t *ppos)
{
int ret = -1;
ret = copy_to_user(buf, kbuf, sizeof(kbuf));
if(ret) {
printk(KERN_ERR "kernel led read error
");
}
printk(KERN_INFO "led device read success
");
}
static ssize_t led_dev_write(struct file *file, const char __user *user_buf, size_t count, loff_t *ppos)
{
int ret = -1;
//首先把kbuf清零
memset(kbuf, 0, sizeof(kbuf));
ret = copy_from_user(kbuf, user_buf, count);
if(ret) {
printk(KERN_ERR "kernel led write error
");
return -EINVAL;
}
printk(KERN_INFO "led device write success
");
if (kbuf[0] == '1') {
*pGPJ0CON = 0x11111111;
*(pGPJ0CON + 1) = ((0<<3) | (0<<4) | (0<<5));
}
if (kbuf[0] == '0') {
*(pGPJ0CON + 1) = ((1<<3) | (1<<4) | (1<<5));
}
return 0;
}
static const struct file_operations led_dev_fops = {
.open = led_dev_open,
.write = led_dev_write,
.read = led_dev_read,
.release = led_dev_release,
.owner = THIS_MODULE,
};
// 模块安装函数
static int __init leddev_init(void)
{
int err = 0;
printk(KERN_INFO "led_device init
");
//在这里进行注册驱动,因为安装驱动实际上执行的就是这个函数;
err = register_chrdev_region(MKDEV(MYDEV,0), LED_COUNT, "MY_LED_DEV");
if(err)
{
printk(KERN_ERR " register_chrdev_region failed
");
return -EINVAL;
}
printk(KERN_INFO "leddev_dev regist success
");
cdev_init(&my_led_cdev, &led_dev_fops);
cdev_add(&my_led_cdev, MKDEV(MYDEV,0), LED_COUNT);
if(!request_mem_region(GPJ0_PA_base + GPJ0CON_PA_OFFSET, 8, "GPJ0PABAST")) {
return -EINVAL;
}
pGPJ0CON = ioremap(GPJ0_PA_base + GPJ0CON_PA_OFFSET, 4);
return 0;
}
// 模块下载函数
static void __exit leddev_exit(void)
{
printk(KERN_INFO "leddev_dev exit
");
//注销led设备驱动
cdev_del(&my_led_cdev);
unregister_chrdev_region(MKDEV(MYDEV,0), LED_COUNT);
iounmap(GPJ0_PA_base + GPJ0CON_PA_OFFSET);
release_mem_region(GPJ0_PA_base + GPJ0CON_PA_OFFSET, 8);
printk(KERN_INFO "leddev_dev unregist success
");
}
module_init(leddev_init);
module_exit(leddev_exit);
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("bhc"); // 描述模块的作者
MODULE_DESCRIPTION("led test"); // 描述模块的介绍信息
MODULE_ALIAS("alias xxx"); // 描述模块的别名信息
上面使用到的函数:
这里关于cdev_init在内核中的具体实现可以参考下面这篇博客
linux内核cdev_init系列函数(字符设备的注册)
module_init:
register_chrdev_region
cdev_init
cdev_add
module_exit
cdev_del
unregister_chrdev_region
中间层:
file_operations
mknod /dev/led1 c 250 0
mknod /dev/led1 c 250 1
mknod /dev/led1 c 250 2
mknod /dev/led1 c 250 3
这里注意安装驱动以后,设备文件没有自动创建需要我们手动来创建;
下面我们介绍一下自动分配设备号的注册函数:
alloc_chrdev_regio
这个函数需要4个参数:把dev_t dev的地址传进去,次设备号的最小值一般为0,几个设备,名字 四个参数
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
{
struct char_device_struct *cd;
cd = __register_chrdev_region(0, baseminor, count, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
*dev = MKDEV(cd->major, cd->baseminor);
return 0;
}
测试代码如下:
// 模块安装函数
static int __init leddev_init(void)
{
int err = 0;
printk(KERN_INFO "led_device init
");
//在这里进行注册驱动,因为安装驱动实际上执行的就是这个函数;
err = alloc_chrdev_region(&mydev, 0, LED_COUNT, "MY_LED_DEV");
if(err)
{
printk(KERN_ERR " register_chrdev_region failed
");
return -EINVAL;
}
printk(KERN_INFO "leddev_dev regist success
");
printk(KERN_INFO "major device NO. is %u,
", MAJOR(mydev));
printk(KERN_INFO "minor device NO. is %u,
", MINOR(mydev));
cdev_init(&my_led_cdev, &led_dev_fops);
cdev_add(&my_led_cdev, mydev, LED_COUNT);
if(!request_mem_region(GPJ0_PA_base + GPJ0CON_PA_OFFSET, 8, "GPJ0PABAST")) {
return -EINVAL;
}
pGPJ0CON = ioremap(GPJ0_PA_base + GPJ0CON_PA_OFFSET, 4);
return 0;
}
// 模块下载函数
static void __exit leddev_exit(void)
{
printk(KERN_INFO "leddev_dev exit
");
//注销led设备驱动
cdev_del(&my_led_cdev);
unregister_chrdev_region(mydev, LED_COUNT);
iounmap(GPJ0_PA_base + GPJ0CON_PA_OFFSET);
release_mem_region(GPJ0_PA_base + GPJ0CON_PA_OFFSET, 8);
printk(KERN_INFO "leddev_dev unregist success
");
}
module_init(leddev_init);
module_exit(leddev_exit);
// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("bhc"); // 描述模块的作者
MODULE_DESCRIPTION("led test"); // 描述模块的介绍信息
MODULE_ALIAS("alias xxx"); // 描述模块的别名信息
cdev_alloc函数:
struct cdev *cdev_alloc(void)
{
struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
if (p) {
INIT_LIST_HEAD(&p->list);
kobject_init(&p->kobj, &ktype_cdev_dynamic);
}
return p;
}
cdev_alloc函数用来自动分配内存空间,并初始化链表;
module_exit的时候用 cdev_del来释放就可以;
这里要注意到cdev_alloc分配一段内存,
void cdev_del(struct cdev *p)
{
cdev_unmap(p->dev, p->count);
kobject_put(&p->kobj);
}
static void cdev_unmap(dev_t dev, unsigned count)
{
kobj_unmap(cdev_map, dev, count);
}
void kobj_unmap(struct kobj_map *domain, dev_t dev, unsigned long range)
{
unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1;
unsigned index = MAJOR(dev);
unsigned i;
struct probe *found = NULL;
if (n > 255)
n = 255;
mutex_lock(domain->lock);
for (i = 0; i < n; i++, index++) {
struct probe **s;
for (s = &domain->probes[index % 255]; *s; s = &(*s)->next) {
struct probe *p = *s;
if (p->dev == dev && p->range == range) {
*s = p->next;
if (!found)
found = p;
break;
}
}
}
mutex_unlock(domain->lock);
kfree(found);
中调用的
cdev_del
cdev_unmap
kobj_unmap
kfree
cdev_del中调用cdev_unmap在调用kobj_unmap在调用kfree来释放我们用cdev_alloc申请的这段内存,所以卸载模块的时候不用自己来释放这段内存了;
还有一般用 cdev_alloc的时候直接
pcdev->owner = THIS_MODULE;
pcdev->opt = &led_dev_fops; 有时候用这两句来代替cdev_init函数;