driver最近做的小笔记,有点乱,后面来整理

时间:2021-10-14 00:30:38
kernel makefile:
对Documentation/kbuild/makefiles.txt翻译:
http://blog.chinaunix.net/uid-21712186-id-1818187.html

uboot到start_kernel过程:
http://blog.chinaunix.net/uid-26215986-id-3458494.html

对结构体device,device_driver解释
http://www.wowotech.net/linux_kenrel/device_and_driver.html
http://www.cnblogs.com/armlinux/archive/2010/09/20/2396909.html

 2: struct device_driver {  
 3:     const char *name;  //驱动名字
 4:     struct bus_type     *bus;//驱动总线
 5:  
 6:     struct module       *owner;
 7:     const char *mod_name; /* used for built-in modules */
 8:  
 9:     bool suppress_bind_attrs; /* disables bind/unbind via sysfs */
 10:  
 11:    const struct of_device_id   *of_match_table;
 12:    const struct acpi_device_id *acpi_match_table;
 13:  
 14:    int (*probe) (struct device *dev);//检测到设备时调用,开始驱动。
 15:    int (*remove) (struct device *dev);//移除设备时调用,结束驱动。这两个回调函数是热插拔概念。
 16:    void (*shutdown) (struct device *dev);
 17:    int (*suspend) (struct device *dev, pm_message_t state);
 18:    int (*resume) (struct device *dev);
 19:    const struct attribute_group **groups;
 20:  
 21:    const struct dev_pm_ops *pm;
 22:  
 23:    struct driver_private *p;
 24: };

device_register,device_add,device_create_vargs,device_create 注册设备到内核。如果设备名相同的driver会调用probe函数。
设备注册函数在内核包的drivers/base/platform.c driver.c函数中,头文件在include/linux/里面。
http://blog.chinaunix.net/uid-7828352-id-3833188.html

******************************************************************************************
驱动进入的第一个函数:postcore_initcall
http://blog.csdn.net/wh_19910525/article/details/16370863
http://my.oschina.net/u/180497/blog/177206
在init.h里面:
init.h有定义module_init和postcore_initcall等。其实他们都是在do_initcalls中以优先级来调度他们指定的函数的。
注意一个语法:
postcore_initcall
#define __define_initcall(fn, id) \
    static initcall_t __initcall_##fn##id __used \
    __attribute__((__section__(".initcall" #id ".init"))) = fn
__attribute__设置属性,__section__将定义的函数__initcall_##fn##id放到.initcall" #id ".init集合(段)里面。在kernel/init/main.c中会依次调用initcall开始的段。
start_kernel -->rest_init() -->kernel_init() --> do_basic_setup() -->do_initcalls()
http://baike.baidu.com/link?url=qIpb3-D3ymURJ-SlI2AoYJ3sjMOlIOiyg_dUviyZ0I1CUj4TWKRt0PNiiksCJqelCVztXZaNUybPeiW1k2GoQ_
id:是优先级
fn是名字吧?
http://blog.csdn.net/zhijianjingling00/article/details/9767271

******************************************************************************************

//下面这个对platform做了个解释,其实是一种虚拟总线,用于管理io,内存,中断...的。
用platform_driver_register时,驱动就指定了总线drv->driver.bus = &platform_bus_type;
struct bus_type platform_bus_type = {
    .name       = "platform",
    .dev_attrs  = platform_dev_attrs,  //设备属性
    .match      = platform_match,       //match函数这个函数在当属于platform的设备或者驱动注册到内核时就会调用完成设备与驱动的匹配工作
    .uevent     = platform_uevent,      //热插拔操作函数
    .pm     = &platform_dev_pm_ops,
};
http://my.oschina.net/yuyang/blog/85888

******************************************************************************************
//解释platform_divice 和platform_driver不同的地方。有两个地方分别定义了他们。
设备就是设备树建立是注册,而驱动就是我们的实现。当注册驱动的时候就会与已经注册了的设备名字比较,相同,就调用platform_driver->probe,说明检测到设备。
http://blog.csdn.net/zhandoushi1982/article/details/5130207
如platform总线设备树注册过程:
arch_initcall(customize_machine);入口函数。
.init_machine    = xxxx_dt_init,----->of_platform_populate(函数根据device tree生成platform_device)---->of_platform_bus_create(首先是需要确定节点是否有"compatible"属性="arm,primecell")---->of_amba_device_create(这个函数才是真正创建platform_device的。of_device_is_available(检测status属性的),of_device_alloc(分配设备内存,初始化,of_address_to_resource和of_irq_count去计算io和中断资源的个数.....))
总结,涉及到的属性有:
"compatible"    必须
"status"    可选属性
"reg"        io资源
"interrupts"    中断资源
http://blog.csdn.net/mcgrady_tracy/article/details/42777969
这里对start_kernel-->setup_arch-->取得machine_desc = DT_MACHINE_START对应设备
DT_MACHINE_START(X_DT, "phoenix")
    .init_machine    = rtk119x_dt_init,
    .reserve        = rtk_reserve,
    .map_io            = rtk119x_map_io,
    .init_time        = rtk119x_timer_init,
    .init_irq        = irqchip_init,
    .dt_compat        = rtk119x_board_dt_compat,
    .smp            = smp_ops(rtk119x_smp_ops),
MACHINE_END
http://blog.csdn.net/wh_19910525/article/details/17762625

******************************************************************************************
驱动注册后和之前定义的设备进行匹配:以GPIO为例:
platform_bus_type(类型bus_type)->match() --> platform_match() --> of_driver_match_device() --> of_match_device(drv->of_match_table, dev)。所以drv必须指定of_match_table如:
static struct platform_driver xxxxx_gpio_driver = {
    .driver = {
               .name = "xxxxx-gpio",
               .owner = THIS_MODULE,
               .of_match_table = xxxxx_gpio_of_match,//这就是匹配想。
               },
    .probe = xxxxx_gpio_probe,
    .suspend = xxxxx_gpio_suspend,
    .resume = xxxxx_gpio_resume,
};
static const struct of_device_id xxxxx_gpio_of_match[] = {
    {.compatible = "xxxx,xxxx-misc-gpio-irq-mux",},//match is this 对应设备树的compatible值。这样才能匹配成功。
    {.compatible = "xxxx,xxxx-iso-gpio-irq-mux",},
    { /* Sentinel */ },
};
*******************************************************************************************
匹配成功,说明检测到设备,调用platform_driver->probe,进行设备初始化。
static int xxxxx_gpio_probe(struct platform_device *pdev)//参数是设备参数。
{
    //首先将chip注册到内核。类型:gpio_chip结构如下。
    p_xxx_gpio_ctl->chip->request=请求方法;
    p_xxx_gpio_ctl->gpio_reg_dir = 0x1801b100;//拿gpio方向寄存器地址。
    ....
    gpiochip_add(&p_xxx_gpio_ctl->chip);//p_xxx_gpio_ctl是自定义的一个gpio控制器,包含了chip(包含了gpio相关的方法),和一些寄存器地址,以便操作寄存器。
    xxx_gpio_irq_setup();//配置gpio中断,一会再说。
    platform_set_drvdata(pdev,p_xxx_gpio_ctl);//将自定义gpio控制结构放到pdev->dev->p->driver_data(设备私有数据),便于后面使用设备得到。
    
}

struct gpio_chip {
    const char        *label;//标签,比如设备名字。
    struct device        *dev;
    struct module        *owner;
    struct list_head        list;

    int            (*request)(struct gpio_chip *chip,//请求request gpio的方法,自己实现。
                        unsigned offset);
    void            (*free)(struct gpio_chip *chip,//释放gpio
                        unsigned offset);
    int            (*get_direction)(struct gpio_chip *chip,//获取方向
                        unsigned offset);
    int            (*direction_input)(struct gpio_chip *chip,//设置为输入
                        unsigned offset);
    int            (*get)(struct gpio_chip *chip,//获取引脚电平
                        unsigned offset);
    int            (*direction_output)(struct gpio_chip *chip,//设置为输出
                        unsigned offset, int value);
    int            (*set_debounce)(struct gpio_chip *chip,//消除抖动
                        unsigned offset, unsigned debounce);

    void            (*set)(struct gpio_chip *chip,//输出时,设置输出电平
                        unsigned offset, int value);

    int            (*to_irq)(struct gpio_chip *chip,//设置io口为中断。
                        unsigned offset);

    void            (*dbg_show)(struct seq_file *s,
                        struct gpio_chip *chip);
    int            base;//我们设备树定义的一组引脚其实编号
    u16            ngpio;//这组引脚个数。
    struct gpio_desc    *desc;//对应的gpio_desc位置地址,gpiochip_add会初始化他,以gpio_desc[base]为首地址。
    const char        *const *names;
    unsigned        can_sleep:1;
    unsigned        exported:1;

#if defined(CONFIG_OF_GPIO)
    /*
     * If CONFIG_OF is enabled, then all GPIO controllers described in the
     * device tree automatically may have an OF translation
     */
    struct device_node *of_node;//设备节点:node = struct platform_device *pdev->dev.of_node
    int of_gpio_n_cells;//必须与设备树#gpio-cells一致。#gpio-cells = <3>对应gpio-ranges = <&pinctrl 0 0 60>;
    int (*of_xlate)(struct gpio_chip *gc,//应该是解析设备树的gpio的方法的。
                const struct of_phandle_args *gpiospec, u32 *flags);
#endif
#ifdef CONFIG_PINCTRL
    /*
     * If CONFIG_PINCTRL is enabled, then gpio controllers can optionally
     * describe the actual pin range which they serve in an SoC. This
     * information would be used by pinctrl subsystem to configure
     * corresponding pins for gpio usage.
     */
    struct list_head pin_ranges;
#endif
}







关于一些__iomem定义的东西 :
http://www.cnblogs.com/wang_yb/p/3575039.html




对于设备树的解释:
http://blog.chinaunix.net/uid-27717694-id-4274992.html
 分析Linux内核的源码,可以看到其对扁平设备树的解析流程如下:
(1)首先在内核入口处将从u-boot传递过来的镜像基地址。
(2)通过调用early_init_dt_scan()函数来获取内核前期初始化所需的bootargs,cmd_line等系统引导参数。
(3)根据bootargs,cmd_line等系统引导参数进入start_kernel()函数,进行内核的第二阶段初始化。
(4)调用unflatten_device_tree()函数来解析dtb文件,构建一个由device_node结构连接而成的单项链表,并使用全局变量of_allnodes指针来保存这个链表的头指针。
(5)内核调用OF提供的API函数获取of_allnodes链表信息来初始化内核其他子系统、设备等。
start_kernel --> setup_arch --> unflatten_device_tree解析设备树。
设备树文件dts(i)文件参数意思:
http://blog.csdn.net/airk000/article/details/21345159
/ {            //设备树根
        interrupt-parent = <&gic>;
        #address-cells = <1>;//表示地址有1个单元
        #size-cells = <1>;
    xxxx-lsadc@0x1801bc00 {//xxxx-lsadc设备名字
                compatible = "xxxxx,xxxxx-lsadc";//设备兼容,就是用他来匹配驱动的。
                interrupt-parent = <&mux_intc>;    //指定中断控制器应该是。
                interrupts = <0 21>;    //中断属性,0表示中断类型,21表示中断号(应该是HW interrupt number中断控制器用他来映射IRQ number)。
//##############注::unsigned int irq_of_parse_and_map(struct device_node *dev, int index),index=0指定第一组数据<0 21>映射21到中断控制器的IRQ number
        reg = <0x18007000 0x100>, <0x18007200 0x100>;//0x18007000 对应 address-cells,0x100 对应 #size-cells。第一个寄存器地址0x18007000,空间0x100;第二个寄存器地址0x18007200,空间0x100;
//##############注::void __iomem *of_iomap(struct device_node *np, int index)为设备映射寄存器地址,index=0取第一组数据<0x18007000 0x100>映射0x18007000地址并分配空间0x100到内核地址。1去第二组数据<0x18007200 0x100>。
                status = "disabled";
                xxxx-lsadc-pad0@0 {
                        //compatible = "xxxx,xxxx-lsadc-pad0";
                        activate =      <1>;                    /* 0: in-activate; 1:activate */
                        ctrl_mode = <0>;                        /* 0: Voltage mode; 1:Current mode*/
                        sw_idx =        <0>;                    /* 0: External input pin 0; 1:External input pin 1 */
                        voltage_threshold = <32>;       /* 8 bit : 0 ~ 255 */
                };
                xxxx-lsadc-pad1@0 {
                        //compatible = "xxxx,xxxx-lsadc-pad1";
                        activate =      <1>;                    /* 0: in-activate; 1:activate */
                        ctrl_mode = <0>;                        /* 0: Voltage mode; 1:Current mode*/
                        sw_idx =        <0>;                    /* 0: External input pin 0; 1:External input pin 1 */
                        voltage_threshold = <16>;       /* 8 bit : 0 ~ 255 */
                };
        };
};

    reg
    #address-cells
    #size-cells
父类会包含的#address-cells表示地址有几个单元,1个,2个等,一般一个
父类会包含的#size-cells表示空间大小,1个,2个等,一般只有一个
而子类包含了寄存器内容:reg = <0x1801b000 0x100>, <0x1801b100 0x100>;说明#address-cells为1,#size-cells为1.<寄存器地址(一个) 寄存器大小(一个)>这样用的。
void __iomem *of_iomap(struct device_node *np, int index)
就是获得io设备节点np的寄存器(reg属性)映射地址的,index是reg参数的第几个--从0开始,他就是去读取设备节点reg属性,提取地址和空间参数来映射的。index = 0就是reg的第一个,=1就是第二个
参考:
#address-cells = <2>
#size-cells = <1>;
    ethernet@0,0 {
        compatible = "smc,smc91c111";
        reg = <0 0 0x1000>;
    };
compatible属性是设备说明的,注册driver的时候需要配置兼容driver.of_match_table.compatible和他一致,这样才能驱动和设备对号入座。


对文件drivers/of/address.c函数的解释
http://blog.chinaunix.net/uid-26675482-id-3563606.html

gpio设计:
http://blog.chinaunix.net/uid-27717694-id-3624294.html

gpio库设计了一个双向链表来保存gpio数据,链表只是作为一个成员在gpio_chip结构体中,可以通过list的地址来找到gpio_chip的对象地址。

gpio_desc:chip每个gpio都有一个chip,flags放输入输出等信息的,label是标签,说明的。

中断
http://blog.csdn.net/droidphone/article/details/7497787
http://www.wowotech.net/linux_kenrel/irq-domain.html
request_irq() 注册
http://blog.csdn.net/wealoong/article/details/7566546

device tree:
http://www.right.com.cn/forum/thread-146260-1-1.html

*************************************************************************************************
interrupt-parent = <&gic>;表示中断子系统为gic中断控制器。
interrupts的DTS结构
http://blog.sina.com.cn/s/blog_ad64b8200101e7q0.html
对于ARM GIC中断控制器而言,#interrupt-cells为3,它3个cell的具体含义
Documentation/devicetree/bindings/arm/gic.txt就有如下文字说明:
01  The 1st cell is the interrupt type; 0 for SPI interrupts, 1 for PPI
02  interrupts.
03    及中断类型
04  The 2nd cell contains the interrupt number for the interrupt type.
05  SPI interrupts are in the range [0-987]. PPI interrupts are in the
06  range [0-15].
07    中断编号
08  The 3rd cell is the flags, encoded as follows:
09  bits[3:0] trigger type and level flags.触发类型
10  1 = low-to-high edge triggered
11  2 = high-to-low edge triggered
12  4 = active high level-sensitive
13    8 = active low level-sensitive
14  bits[15:8] PPI interrupt cpu mask. Each bit corresponds to each of
15  the 8 possible cpus attached to the GIC. A bit set to '1' indicated
16  the interrupt is wired to that CPU. Only valid for PPI interrupts.
另外,值得注意的是,一个设备还可能用到多个中断号。对于ARM GIC而言,若某设
备使用了SPI的168、169号2个中断,而言都是高电平触发,则该设备结点的interrupts
属性可定义为:interrupts = <0 168 4>, <0 169 4>;
*************************************************************************************************

(1)我按照大神思路去看了3.17内核的gpio(基于zynq)部分初始代码,确实在probe()函数里新建了gpio irq的domain。但是这个过程很不优雅:因为在dts的gpio部分,没有看到如下所示、包含interrupt-controller的内容:
gpf: gpf {
    gpio-controller;
    #gpio-cells = <2>;
    interrupt-controller;
    #interrupt-cells = <2>;
};
上面这个node是明确表明这是一个interrupt-controller,所以在内核init_IRQ时,应该就会建立domain吧?
而我的这个zynq的dts文件关于gpio的部分如下:
gpio0: gpio@e000a000 {
    compatible = "xlnx,zynq-gpio-1.0";
    #gpio-cells = <2>;
    clocks = <&clkc 42>;
    gpio-controller;
    interrupt-parent = <&intc>;
    interrupts = <0 20 4>;
    reg = <0xe000a000 0x1000>;
};
------------------------------------------------------
这里并没有看到interrupt-controller属性,但是probe函数里居然新建了irq domain,总感觉别扭?
**************************************************************************************************


modprobe与insmod一样,但能够安装依赖模块
lsmod,rmmod。
书籍2.6内核设计模块:
MODULE_AUTHOR(author);  
MODULE_DESCRIPTION(description);  
MODULE_VERSION(version_string);  
MODULE_DEVICE_TABLE(table_info);  
MODULE_ALIAS(alternate_name);  
放置文档在目标文件的模块中.
module_init(init_function);  
module_exit(exit_function);  



/tmp/aeicmdpipe AEI_CMD_WIFI_MODULE_ON
echo AEI_CMD_WIFI_MODULE_ON >> /tmp/aeicmdpipe
echo AEI_CMD_WIFI_MODULE_OFF >> /tmp/aeicmdpipe


***********************************************************************************************************
***********************************************************************************************************
***********************************************************************************************************
驱动阻塞:等待队列
wait_queue_head_t mywait_queue;
int flags;
在模块初始化时:
init_waitqueue_head(&mywait_queue)//初始化阻塞
***********************************************************************************************************
在read时:
wait_event_interruptible(mywait_queu,flags);//阻塞,等待wakeup
/*flags=0阻塞,非0通过*/
返回数据长度。
***********************************************************************************************************
在poll时:(这个方法包括select)
poll_wait(pfile, &mywait_queu ,wait);//阻塞,等待wakeup,或者wait超时
return (POLLIN | POLLRDNORM);//为应用程序返回可用
http://blog.chinaunix.net/uid-26851094-id-3175940.html
常量    说明
POLLIN    普通或优先级带数据可读
POLLRDNORM    普通数据可读
POLLRDBAND    优先级带数据可读
POLLPRI    高优先级数据可读
POLLOUT    普通数据可写
POLLWRNORM    普通数据可写
POLLWRBAND    优先级带数据可写
POLLERR    发生错误
POLLHUP    发生挂起
POLLNVAL    描述字不是一个打开的文件
***********************************************************************************************************
数据准备好时:
wake_up_interruptible(&mywait_queu);//wakeup,唤醒阻塞。
***********************************************************************************************************
如何释放?
***********************************************************************************************************
***********************************************************************************************************
***********************************************************************************************************
起定时器:
http://blog.csdn.net/jidonghui/article/details/7449546
***********************************************************************************************************
初始化:(timer并没有运行)
    struct timer_list mytimer;
    init_timer(&mytimer);    
    mytimer ->timer.expires = jiffies + 5*HZ;//
    mytimer ->timer.data = (unsigned long) dev;//function的参数
    mytimer ->timer.function = &corkscrew_timer; /* timer handler */
***********************************************************************************************************
注册:
add_timer(&mytimer);//添加到内核链表等待调度(定时器计时),它只会运行一次,在运行定时器的时候相当与注销,如果要重新启动,可以再使用add_timer,或mod_timer
修改注册:
mod_timer(struct timer_list *timer, unsigned long expires)//expires表示时间。
不管定时器是否运行,都可以修改。
***********************************************************************************************************
注销:与注册相反。
del_timer(&mytimer);//不管定时器是否运行,立刻注销
del_timer_sync(&mytimer);//等待定时器完成。
***********************************************************************************************************
判断挂起(就是判断是否注册,注册就是做添加到内核链表):
timer_pending(&mytimer);//1表示挂起
这个函数用来判断一个定时器是否被添加到了内核链表中以等待被调度运行。
比如我们要add_timer的时候判断它已经被挂起了,说明我们就不用再add了。
注意:当一个定时器函数即将要被运行前,内核会把相应的定时器从内核链表中删除(相当于注销)


***********************************************************************************************************
***********************************************************************************************************
***********************************************************************************************************
_IO是什么意思:驱动和应用程序中很多时候会用到。

#define _IOC_NRBITS      8
#define _IOC_TYPEBITS    8
#define _IOC_SIZEBITS   13    /* Actually 14, see below. */
#define _IOC_DIRBITS     3

#define _IOC_NRMASK      ((1 << _IOC_NRBITS)-1)
#define _IOC_TYPEMASK    ((1 << _IOC_TYPEBITS)-1)
#define _IOC_SIZEMASK    ((1 << _IOC_SIZEBITS)-1)
#define _IOC_XSIZEMASK   ((1 << (_IOC_SIZEBITS+1))-1)
#define _IOC_DIRMASK     ((1 << _IOC_DIRBITS)-1)

#define _IOC_NRSHIFT     0 //0 for nr数字
#define _IOC_TYPESHIFT   (_IOC_NRSHIFT + _IOC_NRBITS)//8 for type类型
#define _IOC_SIZESHIFT   (_IOC_TYPESHIFT + _IOC_TYPEBITS)//16 for size大小
#define _IOC_DIRSHIFT    (_IOC_SIZESHIFT + _IOC_SIZEBITS)//29 for dir方向

#define _IOC_NONE        1U
#define _IOC_READ        2U
#define _IOC_WRITE       4U

#define _IOC(dir,type,nr,size) \
        (((dir)  << _IOC_DIRSHIFT) | \
         ((type) << _IOC_TYPESHIFT) | \
         ((nr)   << _IOC_NRSHIFT) | \
         ((size) << _IOC_SIZESHIFT))
/*驱动的io控制经常看到*/
#define _IO(type,nr)        _IOC(_IOC_NONE,(type),(nr),0)
#define _IOR(type,nr,size)  _IOC(_IOC_READ,(type),(nr),sizeof(size))
#define _IOW(type,nr,size)  _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))

//应用如下:驱动和用户层代码都有:
#define GPIO_GET_VALUE                     _IOR('G',5,int)
#define GPIO_REQUEST                        _IO('G',6)

***********************************************************************************************************
***********************************************************************************************************
***********************************************************************************************************
create_proc_entry用来创建一个/proc/xx123文件或者目录。
参考:http://blog.csdn.net/njuitjf/article/details/16940865
//创建目录也可以用下面函数 
struct proc_dir_entry *proc_mkdir(const char *name,
        struct proc_dir_entry *parent)
{
    return proc_mkdir_mode(name, S_IRUGO | S_IXUGO, parent);
}


void create_new_proc_entry(void)
{
    proc_write_entry = create_proc_entry("xx123",0666,NULL);//kernel3.0我没看到这个函数。
    if(!proc_write_entry) {
        printk(KERN_INFO "Error creating xx123 proc entry\n");
        //return -ENOMEM;
        return;
    }
    proc_write_entry->read_proc = read_proc ;
    proc_write_entry->write_proc = write_proc;
}

void aei_kernel_rtp_init(void)
{
    aei_rtp_packet_filter_init();
    create_new_proc_entry();
}

 *------------------------------------------------------------------*/
static int __init mcp_module_init(void)
{
    aei_kernel_rtp_init();
    if (mcp_init()<0)
        return -ENODEV;

    cdev_init(&mcp_dev, &mcp_ops);

    if (alloc_chrdev_region(&devno, 0, 1, MCP_DEV_FILE_NAME)!=0) {
        cdev_del(&mcp_dev);
        return -EFAULT;
    }

    if (cdev_add(&mcp_dev, devno, 1)<0)
        return -EFAULT;

    devfs_mk_cdev(devno, S_IFCHR|S_IRUSR|S_IWUSR, MCP_DEV_FILE_NAME);

    mcp_device = platform_device_register_simple(MCP_DEVICE_NAME, 0, NULL, 0);
    //MCP_AES_128_ECB_DataEncryptTest();
    //MCP_AES_128_CTR_DataEncryptTest();
    //MCP_AES_H_DataHashTest();
    //MCP_SHA1_DataHashTest();

#ifdef CONFIG_PM
    driver_register(&mcp_drv);
#endif

    return 0;
}
/*------------------------------------------------------------------
 * Func : mcp_module_exit
 *
 * Desc : mcp module exit function
 *
 * Parm : N/A
 *
 * Retn : N/A
 *------------------------------------------------------------------*/
static void __exit mcp_module_exit(void)
{
    aei_kernel_rtp_exit();
    platform_device_unregister(mcp_device);

#ifdef CONFIG_PM
    driver_unregister(&mcp_drv);
#endif

    cdev_del(&mcp_dev);
    devfs_remove(MCP_DEV_FILE_NAME);
    unregister_chrdev_region(devno, 1);
    mcp_uninit();
}
module_init(mcp_module_init);
module_exit(mcp_module_exit);

***********************************************************************************************************
***********************************************************************************************************
***********************************************************************************************************
网络驱动:
NIC:network interface card.  
NAPI:混合使用中断与轮询,而不是使用单一的中断驱动机制,这样就提高了系统的性能。
scatter/gather IO:对于DMA传输不连续地址,只在最后传输时触发中断,是一种机制。
Netif_rx:单一的中断驱动处理机制,每产生一个设备接收中断,就会在中断结束时唤醒该软中断,调用该软中断的处理函数。
http://blog.chinaunix.net/uid-21768364-id-209652.html
http://bbs.chinaunix.net/thread-2141004-1-1.html
***********************************************************************************************************
***********************************************************************************************************
***********************************************************************************************************
在看request_irq-->会用到内核线程:
kthread_create
struct task_struct *kthread_create(int (*threadfn)(void *data),void *data,const char *namefmt, ...);

***********************************************************************************************************
***********************************************************************************************************
***********************************************************************************************************

很多驱动都会在/proc建立与用户的交互:
struct proc_dir_entry *proc_create_data(const char *name, umode_t mode,
                    struct proc_dir_entry *parent,
                    const struct file_operations *proc_fops,
                    void *data)
name:文件名
mode:访问权限,如0666
parent:/proc中的父目录,比如设置为null,文件创建在/proc根目录中。
proc_fops:文件操作,和驱动设备通讯一样一样的。
data:参数。
对应的删除文件:
void remove_proc_entry(const char *name, struct proc_dir_entry *parent)
void proc_remove(struct proc_dir_entry *de)
代码:
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/uaccess.h>
#include <linux/poll.h>
#include <linux/proc_fs.h>

char undiscoverFlag = '0';//0 can discover, 1 undiscover
struct proc_dir_entry *aeiCheckUndiscover = NULL;//create_proc_entry(name, mode, proc_net);

static int aeiUndiscover_open(struct inode *pinode, struct file *pfile);
static int aeiUndiscover_release(struct inode *pinode, struct file *pfile);
static ssize_t aeiUndiscover_read(struct file *pfile, char __user * user, size_t len, loff_t *offset);
static unsigned int aeiUndiscover_poll(struct file *pfile, struct poll_table_struct * wait);            


const struct file_operations aeiUndiscover_fops = {
    .owner = THIS_MODULE,
    .open = aeiUndiscover_open,
    .release = aeiUndiscover_release,
    //.unlocked_ioctl = rtk119x_gpio_ioctl,
    .poll = aeiUndiscover_poll,
    .read = aeiUndiscover_read,
};
static int aeiUndiscover_open(struct inode *pinode, struct file *pfile)
{
    printk(KERN_DEBUG "[surpas_undiscover]%s,%d\n", __FILE__, __LINE__);
    return 0;
}
static int aeiUndiscover_release(struct inode *pinode, struct file *pfile)
{
    printk(KERN_DEBUG "[surpas_undiscover]%s,%d\n", __FILE__, __LINE__);
    return 0;
}
static ssize_t aeiUndiscover_read(struct file *pfile, char __user * user, size_t len, loff_t *offset)
{
    undiscoverFlag = (undiscoverFlag + 1 - '0') % 9 + '0';
    if(len < sizeof(undiscoverFlag))
        return 0;
    copy_to_user(user, &undiscoverFlag,sizeof(undiscoverFlag));
    printk(KERN_ALERT "[surpas_undiscover]%s,%d,%c\n", __FILE__, __LINE__, undiscoverFlag);
    if (*offset == 0) {
        *offset += sizeof(undiscoverFlag);
        return sizeof(undiscoverFlag);
    } else
        return 0;
    //return 1;
}
static unsigned int aeiUndiscover_poll(struct file *pfile, struct poll_table_struct * wait)                
{
    printk(KERN_DEBUG "[surpas_undiscover]%s,%d\n", __FILE__, __LINE__);
    return (POLLIN | POLLRDNORM);
}
static int __init hello_init(void)//“__init”使hello_init()函数放到初始化代码段里

{

    printk("Hello, driver!\n");
    if(NULL == aeiCheckUndiscover)
    {
        aeiCheckUndiscover = proc_create_data("aeireadUndiscover", 0444, NULL, &aeiUndiscover_fops, NULL);
        printk(KERN_DEBUG "[surpas_undiscover]%s,%d,%p,create=%s\n", __FILE__, __LINE__, aeiCheckUndiscover,(NULL == aeiCheckUndiscover ? "faile" : "ok"));
    }
    return 0;

}
static int __exit hello_exit(void)

{

    printk("Goodbye, driver!\n");
    if(NULL != aeiCheckUndiscover)
    {
        proc_remove(aeiCheckUndiscover);
        //remove_proc_entry("aeireadUndiscover", NULL);都一样
        printk(KERN_DEBUG "[surpas_undiscover]%s,%d,df=%p,delete=%s\n", __FILE__, __LINE__, aeiCheckUndiscover, (NULL != aeiCheckUndiscover ? "faile" : "ok"));
        aeiCheckUndiscover = NULL;
    }
    return 0;
}
module_init(hello_init);
module_exit(hello_exit);

***********************************************************************************************************
***********************************************************************************************************
***********************************************************************************************************