linux驱动开发(二)

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函数;

原文地址:https://www.cnblogs.com/biaohc/p/6597122.html