新接口注册LED字符驱动设备

#include <linux/init.h>            // __init   __exit
#include <linux/module.h>      // module_init  module_exit

#include <linux/fs.h>          //file_operations

#include <asm/uaccess.h>          //copy_from_user  copy_to_user

#include <mach/regs-gpio.h>
#include <mach/gpio-bank.h>

#include <asm/string.h> 

#include <linux/ioport.h>          //request_mem_region
#include <asm/io.h>                 //ioremap

#include <linux/cdev.h>

#define MYNAME "led_dev"
#define DEVNUM 1

#define S5PV210_PA_GPIOJ0CON 0xe0200240

volatile unsigned int *rGPJ0CON  = NULL; 
volatile unsigned int *rGPJ0DAT = NULL;  

static dev_t led_dev_no = 0;
/************ 静态使用cdev**********/
//static struct cdev led_cdev;
/************ 动态使用cdev**********/
static struct cdev *pled_cdev = NULL;

static int led_open(struct inode *inode, struct file *file);
ssize_t led_read(struct file *file, char __user *user, size_t count, loff_t *loff);
ssize_t led_write(struct file *file, const char __user *user, size_t count, loff_t *loff);
static int led_release(struct inode *inode, struct file *file);

static char kbuf[100] = {0};
static const struct file_operations led_fops = {
    .owner        = THIS_MODULE,
    .open        = led_open,
    .read       = led_read,
    .write      = led_write,
    .release    = led_release,
};

int led_open(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "led_open successful\n");
    return 0;
}

ssize_t led_read(struct file *file, char __user *user, size_t ucount, loff_t *loff)
{
    printk(KERN_INFO "led_read successful\n");
    if (copy_to_user(user,kbuf , ucount))
    {
        printk(KERN_INFO "copy_to_user fail\n");
        return -EINVAL;
    }
    printk(KERN_INFO "copy_to_user successful\n");
    return strlen(kbuf);
}

ssize_t led_write(struct file *file, const char __user *user, size_t ucount, loff_t *loff)
{
    printk(KERN_INFO "led_write successful\n");
    memset(kbuf,0,sizeof(kbuf));
    if (copy_from_user(kbuf, user, ucount))
    {
        printk(KERN_INFO "copy_from_user fail\n");
        return -EINVAL;
    }
    
    if(!strcmp(kbuf,"on"))
    {
        *rGPJ0CON &=0xff000fff;
        *rGPJ0CON |=0x00111000;
        *rGPJ0DAT &=~((0x01<<3)|(0x01<<4)|(0x01<<5));
    }
    else if(!strcmp(kbuf,"off"))
    {
        *rGPJ0CON &=0xff000fff;
        *rGPJ0CON |=0x00111000;
        *rGPJ0DAT |=((0x01<<3)|(0x01<<4)|(0x01<<5));
    }
    return ucount;
    printk(KERN_INFO "copy_from_user successful\n");
}

int led_release(struct inode *inode, struct file *file)
{
    printk(KERN_INFO "led_release successful\n");
    return 0;    
}

// 模块安装函数
static int __init chrdev_init(void)
{    
    int ret = -1;
    int retval = -1;
    printk(KERN_INFO "chrdev_init successful\n");
/******指定主设备号注册*****/
    //led_dev_no = MKDEV(250,0);
    //retval = register_chrdev_region(led_dev_no, DEVNUM,MYNAME);
/******内核自动分配主设备号注册*****/    
    retval = alloc_chrdev_region(&led_dev_no,0,DEVNUM,MYNAME);    
    if (retval)
    {
        printk(KERN_WARNING "alloc_chrdev_region fail\n.");
        ret = -EINVAL;
        goto err_reg;
    }
    printk(KERN_INFO "alloc_chrdev_region successful major = %d,minor = %d \n",MAJOR(led_dev_no),MINOR(led_dev_no));
    /****动态申请cdev实体******/
    pled_cdev = cdev_alloc();
    printk(KERN_INFO "cdev_alloc successful\n");
    cdev_init(pled_cdev, &led_fops);
    retval = cdev_add(pled_cdev, led_dev_no, DEVNUM);
    if (retval)
    {
        printk(KERN_WARNING "cdev_add fail\n.");
        ret = -EINVAL;
        goto err_add;
    }
    printk(KERN_INFO "cdev_add successful\n");
    
    
    if (request_mem_region(S5PV210_PA_GPIOJ0CON, 8, "GPIOJ0CON") == NULL) 
    {
        printk(KERN_WARNING "failed to get memory region\n");
        ret = -ENOENT;
        goto err_req;
    }
    rGPJ0CON = ioremap(S5PV210_PA_GPIOJ0CON,8);
    if (rGPJ0CON ==  NULL) 
    {
        printk(KERN_WARNING "fail to ioremap() region\n");
        ret = -ENOENT;
        goto err_map;
    }
    rGPJ0DAT = rGPJ0CON+1;
    return 0;
    
err_map:
    printk(KERN_INFO "err_map err\n");
    release_mem_region(S5PV210_PA_GPIOJ0CON,8);
    
err_req:
    printk(KERN_INFO "err_req err\n");
    /***for static cdev******/
    //cdev_del(&led_cdev);
    /***for dynamic cdev******/
    cdev_del(pled_cdev);

err_add:
    printk(KERN_INFO "err_add err\n");
    unregister_chrdev_region(led_dev_no, DEVNUM);
    
err_reg:    
    return ret;
    
    
}

// 模块卸载函数
static void __exit chrdev_exit(void)
{
    iounmap(rGPJ0CON);
    release_mem_region(S5PV210_PA_GPIOJ0CON,8);
    /***for static cdev******/
    //cdev_del(&led_cdev);
    /***for dynamic cdev******/
    cdev_del(pled_cdev);
    
    unregister_chrdev_region(led_dev_no, DEVNUM);
    printk(KERN_INFO "chrdev_exit successful\n");

}


module_init(chrdev_init);
module_exit(chrdev_exit);

// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL");                // 描述模块的许可证
MODULE_AUTHOR("musk");                // 描述模块的作者
MODULE_DESCRIPTION("module test");    // 描述模块的介绍信息
MODULE_ALIAS("alias xxx");            // 描述模块的别名信息
View Code

    一. 关于设备注册新老接口

        1.1. 老接口:register_chrdev

        1.2. 新接口:register_chrdev_region/alloc_chrdev_region + cdev

        1.3. 新老接口不同点

            a. 老接口不能注册子设备号

            b. 新接口可以注册子设备号,并且可以一次注册多个子设备号。

            c. register_chrdev = alloc_chrdev_region + cdev_init + cdev_add,所有新接口步骤更多

    二. dev_t 成员

        2.1. dev_t定义:

typedef u_long dev_t;

        2.2. dev;设备号,31位,高12位是主设备号,低20位是次设备号。以下函数可以操作设备号:

#define MINORBITS    20
#define MINORMASK    ((1U << MINORBITS) - 1)
 
#define MAJOR(dev)    ((unsigned int) ((dev) >> MINORBITS))   //获得主设备号
#define MINOR(dev)    ((unsigned int) ((dev) & MINORMASK))    //获得此设备号
#define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi))          //由主次设备号得到设备号
View Code       

 三. 注册/主线设备号

        3.1. 静态注册函数register_chrdev_region:

/**
 * 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);
}
View Code

            a. 此函数需要静态指定设备号。

        3.2. 动态申请注册函数alloc_chrdev_region

/**
 * alloc_chrdev_region() - register a range of char device numbers
 * @dev: output parameter for first assigned number
 * @baseminor: first of the requested range of minor numbers
 * @count: the number of minor numbers required
 * @name: the name of the associated device or driver
 *
 * Allocates a range of char device numbers.  The major number will be
 * chosen dynamically, and returned (along with the first minor number)
 * in @dev.  Returns zero or a negative error code.
 */
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;
}
View Code

            3.2.1. 参数dev是输出型参数,也就是自动分配的设备号   

        3.3.  注销设备号unregister_chrdev_region

/**
 * unregister_chrdev_region() - return a range of device numbers
 * @from: the first in the range of numbers to unregister
 * @count: the number of device numbers to unregister
 *
 * This function will unregister a range of @count device numbers,
 * starting with @from.  The caller should normally be the one who
 * allocated those numbers in the first place...
 */
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));
    }
}
View Code

    4. cdev结构体

        4.1. cdev结构体定义

struct cdev {
    struct kobject kobj;                    //内嵌的kobject对象
    struct module *owner;                   //使属模块
    const struct file_operations *ops;     //文件操作结构体
    struct list_head list;                  //linux 内核所维护的链表指针
    dev_t dev;                               //设备号
    unsigned int count;                     //设备数目
};
View Code

        4.2. cdev定义有两种方式,struct cdev cdev;另外一种struct cdev *cdev; cdev=cdev_alloc();一种静态声明定义,另一种动态分配。

        4.3. cdev相关操作函数

            4.3.1. cdev通过函数cdev_init()初始化,主要工作就是将file_operations和cdev关联起来。file_operations是字符驱动需要实现的主要内容。

            4.3.2. cdev通过cdev_add()实现cdev的注册,所谓注册就是将cdev根据设备号(dev_t)添加到cdev数组(cdev_map)中供系统管理。

            4.3.3. cdev通过cdev_del()将cdev从cdev_map中移除。

        4.4. cdev初始化

            4.4.1. 静态初始化

/**
 * cdev_init() - initialize a cdev structure
 * @cdev: the structure to initialize
 * @fops: the file_operations for this device
 *
 * Initializes @cdev, remembering @fops, making it ready to add to the
 * system with cdev_add().
 */
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;
}
View Code

            4.4.2. 动态初始化

/**
 * cdev_alloc() - allocate a cdev structure
 *
 * Allocates and returns a cdev structure, or NULL on failure.
 */
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;
}
View Code

          4.5. cdev_add添加字符设备

/**
 * cdev_add() - add a char device to the system
 * @p: the cdev structure for the device
 * @dev: the first device number for which this device is responsible
 * @count: the number of consecutive minor numbers corresponding to this
 *         device
 *
 * cdev_add() adds the device represented by @p to the system, making it
 * live immediately.  A negative error code is returned on failure.
 */
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);
}
View Code

        4.6. cdev_del()删除字符设备

/**
 * cdev_del() - remove a cdev from the system
 * @p: the cdev structure to be removed
 *
 * cdev_del() removes @p from the system, possibly freeing the structure
 * itself.
 */
void cdev_del(struct cdev *p)
{
    cdev_unmap(p->dev, p->count);
    kobject_put(&p->kobj);
}
View Code

    5.应用层调用内核

        5.1. 应用层代码如下

#include <stdio.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#include <string.h>

#define DEVFILE "/dev/led_dev"

int main(void)
{
    char buf[100] = {0};
    int fd = -1;
    if((fd =open(DEVFILE, O_RDWR))<0)
    {
        perror("open");
        return -1;   
    }
    printf("open successful fd = %d\n",fd);
    if(write(fd, "on", strlen("on"))<0)
    {
        perror("write");
        return -1;
    }   
    sleep(3);
    memset(buf,0,sizeof(buf));
    if(read(fd, buf, 3)<0)
    {
        perror("read");
        return -1;
    }
    printf("read data = %s\n",buf);
    
    
    if(write(fd, "off", strlen("off"))<0)
    {
        perror("write");
        return -1;
    }   
    sleep(3);
    memset(buf,0,sizeof(buf));
    if(read(fd, buf, 4)<0)
    {
        perror("read");
        return -1;
    }
    printf("read data = %s\n",buf);
    
    close(fd);
    return 0;
}
View Code

 

posted @ 2019-01-03 16:57  三七鸽  阅读(254)  评论(0编辑  收藏  举报