今天,在友善之笔的LED驱动程序基础上学习了LED驱动程序的简单设计,

/*led_driver.c*/

丘猴子 發表在 痞客邦 留言(0) 人氣()

参考:

            http://longer.spaces.eepw.com.cn/articles/article/item/60415

丘猴子 發表在 痞客邦 留言(0) 人氣()

最近在看驱动模型,是越看越糊涂,以前接触比较多的都是一些字符驱动,对字符驱动的框架有一定的了解。后来因为想在驱动中实现设备文件的创建,又了解了一下,sysfs文件系统和udev设备文件系统,必然就涉及到了驱动模型。可是发现驱动模型和以前接触的字符驱动没什么联系。
比如,以前写字符驱动,主要的内容就是实现file_operations结构体里的函数,然后就是申请设备号,注册字符设备,根本就没有涉及到设备驱动模型。而驱动模型里,device_driver根本没有涉及到设备操作的函数、file_operations等,只有一些电源管理,热插拔相关的函数。platform_device里也主要是resource的管理,所以感觉两者根本就没关系,也很奇怪为什么要弄两套东西来实现,而且两者也对应不起来。
后来看了一些内核中的驱动源码,发现很多都是用miscdevice、platform_device、platform_driver实现的,而且流程很相似:

1、在系统初始化阶段注册platform_device,主要是添加设备对应的resource进链表,以便系统对设备占用的资源统一管理;
2、实现platform_driver并注册,在这部分,需要实现的主要有platform_driver结构体中的probe,还有remove、shutdown等一些关于热插拔、电源管理方面的函数。
3、然后在模块初始化函数(xx_init)里注册platform_driver(platform_driver_register)

其中设备资源的获取(platform_get_resource),如IO内存、IO端口、中断号,申请(request),物理地址到虚拟地址的映射(ioremap),misc_device的注册(misc_register),时钟的获取(clk_get)及使能(clk_enable)都是在probe函数里实现的,probe函数是在platform_driver注册,或者新设备添加时,platform_device和platform_driver匹配(通过名字)成功后执行的,有别于以往接触的字符驱动里的注册流程。
对于misc_device对于设备操作函数的实现和字符设备一样,都是填充file_operations结构体,然后在模块初始化函数里注册(misc_register)。
对于platform驱动模型,似乎就是platform_device负责设备资源,platform_driver负责电源管理以及资源的申请,中断的注册等设备初始化及启动有关的操作,然后就是设备操作方法(file_operations)的注册(misc_register或者cdev_add),cdev或者misc_device就负责file_operations。

但是目前还有不少疑问:
1、设备号的申请在哪里,它是怎么放到驱动模型里的device结构体中的?
2、platform_driver结构体和其中的device_driver结构体中都有probe、remove、shutdown等,为什么要在外层放重复的东西,二者有什么关系和区别嘛?(这部分实际的实现是放在platform_driver中,内核只会引用device_driver中的指针,platform实现为在device_driver的引用中再调用platform_driver中的实现)
3、misc_register实现里最终和platform_device_register一样都会调用device_add,这样在设备驱动模型里不是有两个device和device_driver对应,而实际的物理设备只有一个嘛?
4、看起来好像驱动模型是对实际的设备及驱动的抽象,提取它们的信息包装成内核对象kobject,然后按照它们之间的关系对其进行分类、分层次管理(建立一棵树),借由这些对象,由系统管理设备资源的注册申请、释放以及实际驱动(file_operations)的注册时机(由此可以实现热插拔,即插即用)和电源管理(系统可以根据设备树来决定设备关闭的顺序,device->device_driver->shutdown)。
所以设备驱动模型中,device只是用来建立设备树,最终会根据结构体中的device_driver中的电源管理函数实现合理的电源开关顺序?
而对于热插拔有关的功能,和device与device_driver的匹配过程有关,而与设备树层次关系无关?
以上是目前想到的不明白的地方,遗漏的地方想起会再添加。改天找老师好好问问,太复杂了!


===============================================
最近研究Linux设备驱动程序遇到混乱,请大侠过来理理头绪。
Linux设备模型中:bus_type、device、device_driver
《Linux设备驱动程序》的linux设备模型章中说到设备模型中,所有设备都通过总线相连。
添加设备devA,必须指定其device结构体的bus_type域,初始化其他域,然后调用device_register(&devA),将设备devA
注册到指定总线。
添加该设备驱动driverA,也必须指定其device_driver结构体的bus_type域,初始化其他域,然后调用driver_register(&driverA),
将该驱动注册到总线上。
如果驱动driverA和设备devA匹配成功,即调用probe函数成功,则建立他们之间的符号链接,即将设备与驱动捆绑起来。
而实际我看Linux源代码中却大量使用platform_device,
struct platform_device {    
const char * name;       
u32 id;             
struct device dev;    
u32 num_resources;    
struct resource * resource;
};

struct platform_driver {                                                                               
int (*probe)(struct platform_device *);                         
int (*remove)(struct platform_device *);                         
void (*shutdown)(struct platform_device *);                      
int (*suspend)(struct platform_device *, pm_message_t state);    
int (*suspend_late)(struct platform_device *, pm_message_t state);
int (*resume_early)(struct platform_device *);                   
int (*resume)(struct platform_device *);                         
struct device_driver driver;                                     
};
从结构体可以看出,platform_device是device派生出,platform_driver是device_driver派生出
同样添加设备PlatformDevA,初始化platform_device结构体的dev域时,没有初始化其bus_type域,而实际将该设备添加在sys\bus\platform\devices目录下,
在源代码中哪里可以看到这部分代码。
同样添加驱动PlatformDrvA,初始化platform_driver结构体的driver域时,没有初始化其bus_type域,而实际将该驱动添加在sys\bus\platform\drivers目录下,
在源代码中哪里可以看到这部分代码。

还有
struct miscdevice   {                               
int minor;                      
const char *name;                
const struct file_operations *fops;
struct list_head list;          
struct device *parent;          
struct device *this_device;       
}; 
与字符型设备
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
从结构体可以看出,miscdevice是device派生出,它与platform_device区别:
1、platform_device中有设备使用的资源的信息resource。
2、miscdevice中有该设备的使用方法file_operations。
从设备驱动源代码中:
第一步
static struct platform_device at91sam9260_adc_device = {
.name    = "at91_adc",
.id     = -1,
.dev        = {
.platform_data   = &adc_data,
},  
.resource = adc_resources,
.num_resources   = ARRAY_SIZE(adc_resources),
};
static struct resource spi0_resources[] = {
[0] = {
.start   = AT91SAM9260_BASE_SPI0,
.end = AT91SAM9260_BASE_SPI0 + SZ_16K - 1,
.flags   = IORESOURCE_MEM,
},
[1] = {
.start   = AT91SAM9260_ID_SPI0,
.end = AT91SAM9260_ID_SPI0,
.flags   = IORESOURCE_IRQ,
},
};
//向系统添加此设备,注册设备的资源
platform_device_register(&at91sam9260_adc_device);


第二步:
static struct file_operations at91_adc_fops = {
.owner = THIS_MODULE,
.ioctl = at91_adc_ioctl,
.read = at91_adc_readdata,    
.open = at91_adc_open,       
.release = at91_adc_release,       
};
static struct miscdevice at91_adc_dev = {       
.minor = MISC_DYNAMIC_MINOR,
.name = "adc",
.fops = &at91_adc_fops,
};
//向系统添加此设备,注册设备的使用方法
misc_register(&at91_adc_dev);


第三步:
static struct platform_driver at91_i2c_driver = {
.probe = at91_adc_probe,
.remove = __devexit_p(at91_adc_remove),
.suspend = at91_adc_suspend,
.resume = at91_adc_resume,
.driver = {
.name = "at91_adc",
.owner = THIS_MODULE,
},
};
//注册此设备驱动
platform_driver_register(&at91_i2c_driver);

这三个结构体关系:
(基类)
kobject --------------------
/     \                   \
/    \                   \
device     cdev                driver
/     \ (设备驱动操作方法)           \
/    \                            \
miscdevice       platform_device             platform_driver
(设备驱动操作方法) (设备的资源)                (设备驱动) 
我的疑问:
1、当写字符型设备驱动时,我一般只使用cdev结构体,使用此种方式,好像无法在sysfs中显示出该设备。
2、miscdevice是否支持字符设备和块设备,如果使用它怎么辨别块设备或字符设备。
3、miscdevice、platform_device、platform_driver是否可以作为通用的设备驱动方法,由platform_device注册设备资源
platform_driver注册设备驱动、miscdevice注册设备使用方法。

===============================================
以上是网上搜到的一个帖子,和我的疑问有些相似,而且有些地方我也没搞清楚。
这个帖子转载不少,可是少有人回复,回复的也没什么实质性内容。
不过这个同学整理的部分还是比较清晰的,值得看看!

丘猴子 發表在 痞客邦 留言(0) 人氣()

继续上一篇。整理虚拟总线的代码部分:
   1.将挂在总线上的设备及驱动的数据各定义成一个结构体;
   2.将设备及驱动的注册,注销部分各自模块化并导出;
   3.初始化match函数指针。

下面是虚拟总线的代码:

#include <linux/module.h>
#include <linux/init.h>

#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/mm.h>
#include <linux/ioport.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <asm/io.h>

#include "bus.h"

MODULE_LICENSE("GPL");
MODULE_AUTHOR("zl");

int zl_match(struct device * dev, struct device_driver * drv);

struct bus_type zl_bus = {
    .name = "zlbus",
    .match = zl_match,
};

int zl_device_register(struct zl_device *dev)
{
    dev->dev.bus = &zl_bus;    
    //strcpy(dev->dev.bus_id, dev->name); //2.6.13

    dev->dev.init_name = dev->name; //2.6.36

    device_register(&dev->dev);
    return 0;
}
EXPORT_SYMBOL(zl_device_register);

int zl_device_unregister(struct zl_device *dev)
{
    device_unregister(&dev->dev);
    return 0;
}
EXPORT_SYMBOL(zl_device_unregister);

int zl_driver_register(struct zl_driver *drv)
{
    drv->drv.bus = &zl_bus;

    driver_register(&drv->drv);
    return 0;
}
EXPORT_SYMBOL(zl_driver_register);

int zl_driver_unregister(struct zl_driver *drv)
{
    driver_unregister(&drv->drv);
    return 0;
}
EXPORT_SYMBOL(zl_driver_unregister);

//------------------------------------------
int zl_match(struct device * dev, struct device_driver * drv)
{
    struct zl_device *zldev;
    struct zl_driver *zldrv;

    printk("zl_match.\n");

    zldev = container_of(dev, struct zl_device, dev);
    zldrv = container_of(drv, struct zl_driver, drv);
    
    if ((zldev->idVendor == zldrv->idVendor) && (zldev->idProduct ==zldrv->idProduct)) {
        return 1;
    }
    return 0;
}

int test_init(void)
{
    int ret = 0;

    bus_register(&zl_bus);

    return ret;
}

void test_exit(void)
{
    bus_unregister(&zl_bus);
}

module_init(test_init);
module_exit(test_exit);

丘猴子 發表在 痞客邦 留言(0) 人氣()

下面是简单的反映bus, device, driver之间关系的代码。将一个名为zldevice的设备,一个名为zldriver的驱动挂在一个名为zlbus的总线上。


下面是一个虚拟总线的代码:

#include <linux/module.h>
#include <linux/init.h>

#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/mm.h>
#include <linux/ioport.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/device.h>

#include <asm/io.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("zl");

struct bus_type zl_bus = { 
    .name = "zlbus", //this string will be show as /sys/bus/zlbus

};

EXPORT_SYMBOL(zl_bus);


int test_init(void)
{
    int ret = 0;

    bus_register(&zl_bus);

    return ret;
}

void test_exit(void)
{
    bus_unregister(&zl_bus);
}

module_init(test_init);
module_exit(test_exit);

丘猴子 發表在 痞客邦 留言(0) 人氣()

                               使用Kprobe工具 
1.kprobe的用途       
    使用kprobe可以向正在运行的内核中插入一个探测器,监视内核函数的执行,取得内核数据及其它需要的诊断信息。       
   跟踪内核函数的执行。可在进入(handler_pre)和退出(handler_post)要跟踪的的函数时,打印调试信息。如果加上执行时的时间,则所跟踪的函数的执行时间也一目了然。

2.kprobe使用例子
系统环境:Ubuntu 10.04.2 LTS/2.6.32-30-generic
下面是Makefile

ifneq ($(KERNELRELEASE),)
    obj-:= kprobe_clock_settime.
else
    KERNELDIR := /lib/modules/$(shell uname -r)/build
    PWD := $(shell pwd)
default:
        $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
clean:
        rm -*.ko *..*.cmd .tmp_versions *.mod.*.symvers modules.order

丘猴子 發表在 痞客邦 留言(0) 人氣()

s3c2440外接RAM所在的地址空间0x30000000-0x34000000. MMU启用后,只能通过虚拟地址访问内存。线性地址就是虚拟地址。

下面是在u-boot进行试验的过程及代码:

将虚拟地址空间0x30000000-0x34000000映射到物理地址空间0x30000000-0x34000000
将虚拟地址空间0x48000000-0x60000000映射到物理地址空间0x48000000-0x60000000
以上映射的建立,使相应的虚拟地址与物理地址相同。

将虚拟地址空间0xc0000000-0xc4000000映射到物理地址空间0x30000000-0x34000000
以上映射建立后,虚拟地址空间0xc0000000-0xc4000000与0x30000000-0x34000000对应同一块物理地址空间。

当个修改0x30000000-0x34000000中某个地址的内容时,0xc0000000-0xc4000000中相应地址的内容也被修改了.

下面修改0x30130000的内容,然后查看0xc0130000的内容。其内容会相应的改变。
test.c文件的内容

#define COARSE_FLAGS    (| (<< 4) ) //bit(1,0) = 01 coarse page base, 
#define SMALL_MEM_FLAGS        (| (<< 2) | ( 1 << 3)) //bit(1,0) = 10 4KB page, bit(3,2) = 11 write back cacheable
#define SMALL_IO_FLAGS        () //bit(1,0) = 10 4KB page, bit(3,2) = 00 nocached nobuffered


(*printf)(char *, ...) = 0x33f95964;

/*
 *前面0地址开始的4KB + 0x30000000开始的64MB
 *
 *1个一级页项表对应256个二级页表项
 *
 *1个二级页表项对应1个页帧
 *
 *1个页帧4KB
 *
 */
 

#define TTB 0x32000000; //ttb基址,也是一级页目录表基址
#define TTB_L2 (0x32000000 + 0x4000) //二级页表基址, 给一级页表留足16KB

unsigned long *ttb = (void *)TTB; 
unsigned long *ttb_l2 = (void *)TTB_L2; 

void memset(char *dst, char ch, int size);
void _start(void)
{
    unsigned long c1_flags;
    unsigned long virt, phys, temp, addr;
    int *p;

    memset(ttb, 0x00, 4096 * 4);

    c1_flags = 1 | (<< 1) | (0xf << 3) | (0x3 << 30); //bit0 = 1 MMU enable,bit1 = 1 fault check, bit6..3 = 0xf write, bit31,30 = 0x3 async clock mode

/*第一个4KB, 第一页*/
    ttb[0] = ((unsigned long)ttb_l2) | COARSE_FLAGS;
    ttb_l2[0] = 0 | SMALL_MEM_FLAGS;
    ttb_l2 = TTB_L2 + 256 * 4;
/*线性地址空间[0x30000000, 0x34000000] 映射到物理地址空间[0x30000000, 0x34000000]*/
    for (addr = 0x30000000; addr < 0x34000000; ttb_l2 += 256) {
        ttb[addr >> 20]    = ((unsigned long )ttb_l2 & 0xfffffc00) | COARSE_FLAGS;

        temp = addr + 0x100000; //限定处理1MB空间对应的页表

        for (; addr < temp; addr += 0x1000) {
            ttb_l2[(addr & 0xff000) >> 12] = (addr & 0xfffff000) | SMALL_MEM_FLAGS;
        }    
    }
    
/*线性地址空间[0x48000000, 0x60000000] 映射到物理地址空间[0x48000000, 0x60000000]*/
    for (addr = 0x48000000; addr < 0x60000000; ttb_l2 += 256) {
        ttb[addr >> 20]    = ((unsigned long)ttb_l2 & 0xfffffc00) | COARSE_FLAGS;

        temp = addr + 0x100000; //限定处理1MB空间对应的页表

        for (; addr < temp; addr += 0x1000) {
            ttb_l2[(addr & 0xff000) >> 12] = (addr & 0xfffff000) | SMALL_IO_FLAGS;
        }
    }

/*线性地址空间[0xc0000000, 0xc4000000] 映射到物理地址空间[0x30000000, 0x34000000]*/
    for (virt = 0xc0000000; virt < 0xc4000000; ttb_l2 += 256) {
        ttb[virt >> 20]    = ((unsigned long )ttb_l2 & 0xfffffc00) | COARSE_FLAGS;

        temp = virt + 0x100000; //每一个一级页表项限定处理1MB空间对应的页表

        for (; virt < temp; virt += 0x1000) {
            phys = virt - 0xc0000000 + 0x30000000; //虚拟地址与物理地址之间的关系

            ttb_l2[(virt & 0xff000) >> 12] = (phys & 0xfffff000) | SMALL_MEM_FLAGS;
        }    
    }
//------------------------------------------


    __asm__ __volatile__ (    
        "mov r0, #0\n"            
        "mcr p15, 0, r0, c7, c7, 0\n" //清除I-cache和D-cache的内容:
        "mcr p15, 0, r0, c8, c7, 0\n" //清除I & D TLB 的内容

        "mvn r0, #0\n"
        "mcr p15, 0, r0, c3, c0, 0\n" //clear Domain access control
        //--------------------------------
        "mcr p15, 0, %1, c2, c0, 0\n" //ttb写入c2
        "mcr p15, 0, %0, c1, c0, 0\n" //c1_flags写入c1
        :
        : "r" (c1_flags), "r" (ttb)
        : "r0"
    );
/*先看初始值*/
    virt = 0x30130000;
    printf("0x30130000 = %x\n", *(int *)virt);
    virt = 0xc0130000;
    printf("0xc0130000 = %x\n", *(int *)virt);

/*修改0x30130000地址处的值, 再看0xc0130000处的值*/
    virt = 0x30130000;
    *(int *)virt = 0x2222;
    virt = 0xc0130000;
    printf("0xc0130000 = %x\n", *(int *)virt);

/*修改0x30130000地址处的值, 再看0xc0130000处的值*/
    virt = 0x30130000;
    *(int *)virt = 0x3333;
    virt = 0xc0130000;
    printf("0xc0130000 = %x\n", *(int *)virt);
}

void memset(char *dst, char ch, int size)
{
    int i;
    for (= 0; i < size; i ++) {
        dst[i] = ch;
    }
}

丘猴子 發表在 痞客邦 留言(0) 人氣()

在linux中使用工作队列work_queue
软硬件环境:linux-2.6.36/s3c2440

工作队列在进程上下文中运行,允许重新调度甚至睡眠
test.c

#include <linux/module.h>
#include <linux/init.h>

#include <linux/mm.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/workqueue.h>

#include <asm/current.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("zhanglong");

struct work_struct my_work;

void work_handle(struct work_struct* arg)
{
    long data = atomic_long_read(&arg->data);

    printk("current: %s, pid: %d\n", current->comm, current->pid);
    ssleep(3);
    printk("after sleep, data = %ld\n", data);    
    //printk("&my_work = 0x%x\n", (unsigned int)&my_work);    

}

irqreturn_t irq_handle(int irq, void *dev_id)
{
    schedule_work(&my_work);
    printk("----irq handle. dev_id = %d\n", (int)dev_id);
    return 0;
}

int test_init(void)
{
    int ret = 0;

    INIT_WORK(&my_work, work_handle);

    set_irq_type(IRQ_EINT0, IRQ_TYPE_EDGE_RISING);
    ret = request_irq(IRQ_EINT0, irq_handle, 0, "my irq", (void *)123);
    if (ret) {    
        printk("request irq failed.\n");
        return -EBUSY;
    }

    return 0;
}

void test_exit(void)
{
    free_irq(IRQ_EINT0, (void *)123);
}

module_init(test_init);
module_exit(test_exit);

丘猴子 發表在 痞客邦 留言(0) 人氣()

在linux中使用tasklet
软硬件环境:linux-2.6.36/s3c2440

tasklet是通过软中断实现的,所以它本身也是软中断。

test.c

#include <linux/module.h>
#include <linux/init.h>

#include <linux/mm.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/delay.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("zhanglong");

struct tasklet_struct my_tasklet;

void tasklet_action(unsigned long arg)
{
    printk("tasklet handle. arg = %ld\n", arg);    
}

static irqreturn_t irq_handle(int irq, void *dev_id)
{
    tasklet_schedule(&my_tasklet);    //再中断处理函数中调度tasklet
    printk("------irq handle. dev_id = %d\n", (int)dev_id);
    return IRQ_HANDLED;
}

int test_init(void)
{
    int ret = 0;

    tasklet_init(&my_tasklet, tasklet_action, 789);

    set_irq_type(IRQ_EINT0, IRQ_TYPE_EDGE_RISING);
    ret = request_irq(IRQ_EINT0, irq_handle, 0, "my irq", (void *)123);
    if (ret) {    
        printk("request irq failed.\n");
        return -EBUSY;
    }

    return 0;
}

void test_exit(void)
{
    free_irq(IRQ_EINT0, (void *)123);
}

module_init(test_init);
module_exit(test_exit);

丘猴子 發表在 痞客邦 留言(0) 人氣()

在linux中断加入自已的软中断
软硬件环境:linux-2.6.36/s3c2440
步骤如下:
1>在软中断定义的枚举类型列表中加入自已的软中断号MY_SOFTIRQ
   具体到linux-2.6.36/include/linux/interrupt.h的第376行的枚举列表。其中已经有10项,在NR_SOFTIRQS之前加入MY_SOFTIRQ就可以了。make编译后使用新内核
2>注册软中断
   如:open_softirq(MY_SOFTIRQ, my_softirq_action)
     对2.6.36内核,在使用前需用EXPORT_SYMBOL_GPL(open_softirq)导出此函数。
3>在中断处理程序中触发软中断
   如:raise_softirq(MY_SOFTIRQ)
   同样,使用前要用EXPORT_SYMBOL_GPL(raise_softirq)导出此函数。

下面是在s3c2440上实现的一个例子。其中GPF0设为中断EINT0, 在EINT0的中断处理函数中触发软中断。
test.c

#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/irq.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("zhanglong");

void my_softirq_action(struct softirq_action *data)
{
    printk("my softirq atcion ok!\n");
    return;
}

static irqreturn_t irq_handle(int irq, void *dev_id, struct pt_regs *regs)
{
    int flag;

    raise_softirq(MY_SOFTIRQ); //触发软中断

    printk("irq %s ok.\n", dev_id);

丘猴子 發表在 痞客邦 留言(0) 人氣()