B.Linux

灵魂构造师

导航

字符设备驱动(1)驱动代码完整源码:charButtons.c

内核版本:Linux3.0.8

开发板:基于三星S5PV210处理器的Tiny210开发板

驱动名称:charButtons.c

驱动描述:按键触发中断,中断处理程序执行相应的简单LED点亮操作

方案1注册字符设备使用新的接口实现(需要好几个函数来实现。貌似更复杂)

方案2注册字符设备使用老的接口实现(貌似老接口更简单)

/*****************************************************************************
简    述:简单字符型驱动程序,手动静态分配设备号,手动创建设备节点
******************************************************************************/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/irq.h>
#include <asm/irq.h>
#include <linux/interrupt.h>


#include <mach/map.h>
#include <mach/gpio.h>
#include <mach/regs-gpio.h>
#include <plat/gpio-cfg.h>

#include <linux/slab.h>

#define DEVICE_NAME        "buttons"

struct button_desc {
    int gpio;
    int number;
    char *name;    
};

struct led_desc {
    int gpio;
    int number;
    char *name;    
};

static struct button_desc buttons[] = {
    { S5PV210_GPH2(0), 0, "KEY0" },
    { S5PV210_GPH2(1), 1, "KEY1" },
    { S5PV210_GPH2(2), 2, "KEY2" },
    { S5PV210_GPH2(3), 3, "KEY3" },
    { S5PV210_GPH3(0), 4, "KEY4" },
    { S5PV210_GPH3(1), 5, "KEY5" },
    { S5PV210_GPH3(2), 6, "KEY6" },
    { S5PV210_GPH3(3), 7, "KEY7" },
};

static struct led_desc leds[] = {
    {S5PV210_GPJ2(0),1,"LED1"},
    {S5PV210_GPJ2(1),2,"LED2"},    
    {S5PV210_GPJ2(2),3,"LED3"},
    {S5PV210_GPJ2(3),4,"LED4"},
};

#define OK            (0)
#define ERROR         (-1)
struct gpio_chip *chip;
struct cdev *gDev;
struct file_operations *gFile;
dev_t  devNum;
unsigned int subDevNum = 1;//要申请的次设备号个数
int reg_major  =  234;    
int reg_minor =   0;


static irqreturn_t button_interrupt(int irq, void *dev_id)
{
    struct button_desc *bdata = (struct button_desc *)dev_id;

    int down;
    unsigned tmp;
    tmp = gpio_get_value(bdata->gpio);

    /* active low */
    down = !tmp;
    printk("KEY %d: %08x\n", bdata->number, down);
        
    if(bdata->number < 4)
    {
        gpio_set_value(leds[bdata->number].gpio,0);
        printk("LED %d: On \n",leds[bdata->number].number);
    }
    else
    {
        gpio_set_value(leds[(bdata->number) - 4].gpio,1);
        printk("LED %d: OFF \n",leds[(bdata->number)-4].number);
    }
    
    return IRQ_HANDLED;
}

int butsOpen(struct inode *p, struct file *f)
{
    
        int irq;
    int i;
    int err = 0;
    printk(KERN_EMERG"butsOpen\r\n");
    for (i = 0; i < ARRAY_SIZE(buttons); i++) {
        if (!buttons[i].gpio)
            continue;

        irq = gpio_to_irq(buttons[i].gpio);
        // irq = IRQ_EINT(16)+i =160+i "S5PV210_GPH2(i)"
        //irq = IRQ_EINT(24)+i =168+i  "S5PV210_GPH3(i)"
        err = request_irq(irq, button_interrupt, IRQ_TYPE_EDGE_BOTH, 
                buttons[i].name, (void *)&buttons[i]);
        if (err)
            break;
    }
    
    for(i = 0; i<ARRAY_SIZE(leds);i++)
    {
        if(!leds[i].gpio)
            continue;
        gpio_direction_output(leds[i].gpio,1);
    }
    
    if (err) {
        i--;
        for (; i >= 0; i--) {
            if (!buttons[i].gpio)
                continue;

            irq = gpio_to_irq(buttons[i].gpio);
            disable_irq(irq);
            free_irq(irq, (void *)&buttons[i]);
        }

        return -EBUSY;
    }
    return 0;
}




int charDrvInit(void)
{
    
    devNum = MKDEV(reg_major, reg_minor);

    printk(KERN_EMERG"devNum is %d\r\n", devNum);
    if(OK == register_chrdev_region(devNum, subDevNum, DEVICE_NAME))
    {
        printk(KERN_EMERG"register_chrdev_region ok\r\n");
    }
    else
    {
        printk(KERN_EMERG"register_chrdev_region error\r\n");
        return ERROR;
    }
    /*if(OK == alloc_chrdev_region(&devNum, subDevNum, subDevNum,"test"))
    {
        printk(KERN_EMERG"register_chrdev_region ok\r\n");
    }
    else
    {
        printk(KERN_EMERG"register_chrdev_region error\r\n");
        return ERROR;
    }*/

    gDev = kzalloc(sizeof(struct cdev), GFP_KERNEL);
    gFile = kzalloc(sizeof(struct file_operations), GFP_KERNEL);
    
    gFile->open = butsOpen;
//注册设备函数到file_operations结构体gFile

   //gDev->owner = THIS_MODULE;
    gFile->owner = THIS_MODULE;
    cdev_init(gDev, gFile);
//在cdev结构体中添加指针指向file_operations结构体gFile
    cdev_add(gDev, devNum, 3);
//建立设备号与cdev结构体联系
    printk(KERN_EMERG"button driver initial done...\r\n");
    return 0;
}

void __exit charDrvExit(void)
{
    int i,irq;
    cdev_del(gDev);
    unregister_chrdev_region(devNum, subDevNum);
    for (i = 0; i < ARRAY_SIZE(buttons); i++) {
            if (!buttons[i].gpio)
                continue;

            irq = gpio_to_irq(buttons[i].gpio);
            disable_irq(irq);
            free_irq(irq, (void *)&buttons[i]);
        }
    
    return;
}
module_init(charDrvInit);//执行insmod时会执行此行代码并调用charDrvInit,驱动开始
module_exit(charDrvExit);//执行rmmod时,结束
MODULE_LICENSE("GPL");
代码实现方案1
/*****************************************************************************
简    述:简单字符型驱动程序,手动静态分配设备号,手动创建设备节点
******************************************************************************/
#include <linux/module.h>
#include <linux/fs.h>
#include <mach/gpio.h>
#include <linux/irq.h>
#include <linux/kdev_t.h>
#include <linux/interrupt.h>
#include <linux/init.h>

#define DEVICE_NAME        "leds"

struct button_desc {
    int gpio;
    int number;
    char *name;    
};

struct led_desc {
    int gpio;
    int number;
    char *name;    
};

static struct button_desc buttons[] = {
    { S5PV210_GPH2(0), 0, "KEY0" },
    { S5PV210_GPH2(1), 1, "KEY1" },
    { S5PV210_GPH2(2), 2, "KEY2" },
    { S5PV210_GPH2(3), 3, "KEY3" },
    { S5PV210_GPH3(0), 4, "KEY4" },
    { S5PV210_GPH3(1), 5, "KEY5" },
    { S5PV210_GPH3(2), 6, "KEY6" },
    { S5PV210_GPH3(3), 7, "KEY7" },
};

static struct led_desc leds[] = {
    {S5PV210_GPJ2(0),1,"LED1"},
    {S5PV210_GPJ2(1),2,"LED2"},    
    {S5PV210_GPJ2(2),3,"LED3"},
    {S5PV210_GPJ2(3),4,"LED4"},
};

#define OK            (0)
#define ERROR         (-1)
dev_t  devNum;
unsigned int subDevNum = 1;//要申请的次设备号个数
int reg_major  =  234;    
int reg_minor =   0;


static irqreturn_t button_interrupt(int irq, void *dev_id)
{
    struct button_desc *bdata = (struct button_desc *)dev_id;

    int down;
    unsigned tmp;
    tmp = gpio_get_value(bdata->gpio);

    /* active low */
    down = !tmp;
    printk("KEY %d: %08x\n", bdata->number, down);
        
    if(bdata->number < 4)
    {
        gpio_set_value(leds[bdata->number].gpio,0);
        printk("LED %d: On \n",leds[bdata->number].number);
    }
    else
    {
        gpio_set_value(leds[(bdata->number) - 4].gpio,1);
        printk("LED %d: OFF \n",leds[(bdata->number)-4].number);
    }
    
    return IRQ_HANDLED;
}

int butsOpen(struct inode *p, struct file *f)
{
    
        int irq;
    int i;
    int err = 0;
    printk(KERN_EMERG"butsOpen\r\n");
    for (i = 0; i < ARRAY_SIZE(buttons); i++) {
        if (!buttons[i].gpio)
            continue;

        irq = gpio_to_irq(buttons[i].gpio);
        // irq = IRQ_EINT(16)+i =160+i "S5PV210_GPH2(i)"
        //irq = IRQ_EINT(24)+i =168+i  "S5PV210_GPH3(i)"
        err = request_irq(irq, button_interrupt, IRQ_TYPE_EDGE_FALLING, 
                buttons[i].name, (void *)&buttons[i]);
        if (err)
            break;
    }
    
    for(i = 0; i<ARRAY_SIZE(leds);i++)
    {
        if(!leds[i].gpio)
            continue;
        gpio_direction_output(leds[i].gpio,1);
    }
    
    if (err) {
        i--;
        for (; i >= 0; i--) {
            if (!buttons[i].gpio)
                continue;

            irq = gpio_to_irq(buttons[i].gpio);
            disable_irq(irq);
            free_irq(irq, (void *)&buttons[i]);
        }

        return -EBUSY;
    }
    return 0;
}

static const struct file_operations gFile = 
{
    .owner = THIS_MODULE,
    .open  =  butsOpen,    
};


int charDrvInit(void)
{
    
    devNum = MKDEV(reg_major, reg_minor);
    printk(KERN_EMERG"devNum is %d\r\n", devNum);
   
    if(OK == register_chrdev(reg_major, DEVICE_NAME, &gFile))
    {
        printk(KERN_EMERG "register_chrdev_region ok\r\n");
    }
    else
    {
        printk(KERN_EMERG"register_chrdev_region error\r\n");
        return ERROR;
    }
    
    printk(KERN_EMERG "button driver initial done...\r\n");
    return 0;
}

void __exit charDrvExit(void)
{
    int i,irq;
    unregister_chrdev(reg_major, DEVICE_NAME);
    for (i = 0; i < ARRAY_SIZE(buttons); i++) {
            if (!buttons[i].gpio)
                continue;

            irq = gpio_to_irq(buttons[i].gpio);
            disable_irq(irq);
            free_irq(irq, (void *)&buttons[i]);
        }
    
    return;
}
module_init(charDrvInit);//执行insmod时会执行此行代码并调用charDrvInit,驱动开始
module_exit(charDrvExit);//执行rmmod时,结束
MODULE_LICENSE("GPL");
MODULE_AUTHOR("LiuB");
代码实现方案2

函数修饰符

__init,本质上是一个宏定义,在内核源代码中定义:#define __init __section(.init.text) __cold notrace
作用就是,将被它修饰的函数放入.init.text段中去。所以所有的内核模块的__init修饰的函数被放在一起。内核启动时统一加载,加载完后统一释放以节省内存。

__exit,同上:#define __exit __section(.exit.text) __exitused __cold notrace

printk()函数:内核封装出来的打印函数。格式:
printk( 打印级别 “打印信息”) //printk的打印级别是用来控制printk打印的信息是否在终端显示

#define KERN_EMERG    "<0>"    /* system is unusable            */
#define KERN_ALERT    "<1>"    /* action must be taken immediately    */
#define KERN_CRIT    "<2>"    /* critical conditions            */
#define KERN_ERR    "<3>"    /* error conditions            */
#define KERN_WARNING    "<4>"    /* warning conditions            */
#define KERN_NOTICE    "<5>"    /* normal but significant condition    */
#define KERN_INFO    "<6>"    /* informational            */
#define KERN_DEBUG    "<7>"    /* debug-level messages            */

/* Use the default kernel loglevel */
#define KERN_DEFAULT    "<d>"

 如果定义了模块,即这一驱动被动态编译为模块时

驱动代码中的module_init、module_exit的内核定义(/include/linux/init.h)

/* Each module must use one module_init(). */
#define module_init(initfn)    \
static inline initcall_t __inittest(void)    { return initfn; }    \
int init_module(void) __attribute__((alias(#initfn)));

typedef int (*initcall_t)(void);
//static inline initcall_t __inittest(void)    { return initfn; }
//inittest这个函数返回值为一个initcall_t类型的initfn
//而initcall_t是一个函数指针,返回值为整型,输入参数为空
module_init()
/* This is only required if you want to be unloadable. */
#define module_exit(exitfn)    \
static inline exitcall_t __exittest(void)    { return exitfn; }    \
void cleanup_module(void) __attribute__((alias(#exitfn)));

typedef void (*exitcall_t)(void);
module_exit()

如果未定义模块,这一驱动被静态编译进内核

驱动代码中的module_init、module_exit的内核定义(/include/linux/init.h)

/**
 * module_init() - driver initialization entry point
 * @x: function to be run at kernel boot time or module insertion
 * 
 * module_init() will either be called during do_initcalls() (if
 * builtin) or at module insertion time (if a module).  There can only
 * be one per module.
 */
#define module_init(x)    __initcall(x);
#define __initcall(fn) device_initcall(fn)
#define device_initcall(fn)    __define_initcall("6",fn,6)
/* initcalls are now grouped by functionality into separate 
 * subsections. Ordering inside the subsections is determined
 * by link order. 
 * For backwards compatibility, initcall() puts the call in 
 * the device init subsection.
 *
 * The `id' arg to __define_initcall() is needed so that multiple initcalls
 * can point at the same handler without causing duplicate-symbol build errors.
 */

#define __define_initcall(level,fn,id) \
    static initcall_t __initcall_##fn##id __used \
    __attribute__((__section__(".initcall" level ".init"))) = fn
module_init(x)

通过这些段代码,我们能够看出最终的结果是将我们使用module_init修饰的函数指针链接到一个叫.initcall的段里,也就是说最终所有使用module_init修饰的函数指针都被链接在这个段里,内核在启动的时候顺序调用所有链接在这个段里的函数,实现设备的初始化

posted on 2019-06-26 22:12  B.Linux  阅读(565)  评论(0编辑  收藏  举报