linux常用驱动模型,LINUX 驱动学习之路 -设备模型之我理解(1)

前言

作为开头篇,我不想写HELLLOWORLD驱动,甚至字符设备驱动的开发,这样文章充斥在各大网站上的博客上,随便搜搜,就可以找到几百篇。这是最基本的东西,通过这些内容的学习,我们要掌握LINUX驱动的基本要素,比如初始化函数,退初函数,以及去理解简单的驱动的MAKE

FILE的编写,推荐去看LDD,这方面有比较详细的叙述。

但是我的理解,即使我们会写这些东西,对我们的工作也没有太大的用处,如果你深入去读LINUX的驱动代码,就会发现,你还是云里雾里。比如触摸屏是字符设备,可是怎么我看到的触摸屏驱动里根本看不到一点字符设备的影子。OH

OH...,

因此我觉得学习LINUX驱动,要跳出一个圈子,不能把自己局限在某个驱动的编写上,而应该把重心放在模型或者架构的层次上去掌握它,只有这样才能更快地深入地理解LINUX驱动的精髓。

因此在看过HELLWORLD或者写过一个简单的字符设备驱动之后,应该迅速地去学习LINUX的设备模型,这是我们必须第一个要学习的模型,也是最重要一个模型。如果你不能理解它,你将掉进驱动的泥潭里。

学习之前,不要忘记,源码,源码!!!在看任何关于LINUX的文章的时候,都要把SOURCEINSIGHT打开,随时要做好准备去查源码,源码是理解任何LINUX驱动的捷径。

不要忘记这个目录Documentation,你会有意想不到的发现,这是LINUX开发者留给我们的精华。

LINUX设备模型四要素

linux设备模型的抽象是总线、设备、驱动,类。按照这个顺序来分析就可以勾勒出linux设备模型。

很多人看设备模型,会选择直接去学习Kobject、Kset 和

Subsystem(在2.6其实也是KSET),进入了个大坑,让人直接对LINUX驱动模型的理解望而生畏。这么复杂的东西,实在太难以理解了。我们是不是可以试着先忽略掉它们,你们是谁呀,我不想知道你们,滚一边去。

我们先简单的讲一下总线、设备、驱动的关系(类到下一节再来说)。在LINUX驱动的世界里,所有的设备和驱动都是挂在总线上的,也就是总线来管理设备和驱动的,总线知道挂在它上边的所有驱动和设备的情况,由总线完成驱动和设备的匹配和探测。至于怎么实现,以后再讲,现在只要姐的这一点就可以了。让我们想象以前我们工作碰到的各种总线,I2C,SPI,PCI等等,看来总线也不神秘呀。如果你够聪明的话,估计你要问,有些设备不是直接连在总线上的呀,LINUX如何管理的呢?比如RTC,那我先告诉你,针对SOC(什么是SOC,自己去BAIDU去)上一些外设,系统已经虚拟了一个PLATFORM总线,更准确的说,是一套PLATFORM

总线,设备,驱动, 甚至I2C这些总线也是挂在这个PLARFORM上的。是不是迷糊了? 那就暂时忘记最后半句话。

在我们的脑海中,应该有这个概念,总线上挂着驱动和设备,一个驱动可以管理多个设备,一个设备保存一个对应驱动的信息,一般在初始化的时候,总线先初始化,然后设备先注册,最后驱动去找设备,完成他们之间的衔接。

我们要不要去写一个总线驱动呢?可以说99.999999%的人都不需要,系统已经给我们准备好了我们所学要的总线。对于我们来说,就是去学好怎么在系统中添加设备以及相关的驱动就行了。我是没写过任何总线的驱动,所以我们看看设备和驱动,回头再看总线。

LINUX设备

在底层,LINUX设备都可以用DEVICE结构的一个实例来表示:

struct device {

struct device_type *type;

struct

bus_type *bus; struct device_driver

*driver; void *platform_data; };

在这个节都中还包含着许多其他的结构成员,只是我们现在暂时不考虑,否则又要出现很多个为什么了。我们现在只关心这几个成员:

1)设备类型

2)设备所挂的总线

3)设备的驱动

4)设备的私有数据

可以看出,描述设备的结构已经把自身和总线以及设备关联起来,一般情况下,我们也不会这么定义一个设备。为了描述一个设备,常常把设备其他信息和这个结构定义在一起来描述特定的设备。以我们之前提到的虚拟的PLARFORM

DEVICE为例:

struct platform_device {

const char * name;

int id;

struct

device dev; u32 num_resources;

struct resource * resource;

const struct

platform_device_id *id_entry;

struct

pdev_archdata archdata;

};

在我们写的驱动里,我们常常这么定义一个设备:platform_device xxx_device,而不是直接device dev。

在S3C系列中,它所支持的大部分设备都是在common-sdk.c和MACH-SMDK***.C中定义好,在板级初始化的时候通过smdkXXXX_init(void)把设备添加到系统中->调用platform_add_devices。而这个函数platform_add_devices的参数smdk_devs则包含系统了S3C上支持的设备。

//共用的

static struct platform_device __initdata *smdk_devs[] = {

&s3c_device_nand,

&smdk_led4,

&smdk_led5,

&smdk_led6,

&smdk_led7,

};

//具体芯片的

static struct platform_device *smdk2410_devices[] __initdata =

{

&s3c_device_ohci,

&s3c_device_lcd,

&s3c_device_wdt,

&s3c_device_i2c0,

&s3c_device_iis,

};

在MACH-SMDK***.C文件(比如MACH-SMDK2410.C)的最后几行看看MACHINE_START->smdk2410_init->smdk_machine_init()->platform_add_devices,就这样完成了板子上主要设备的注册,挂在PLATFORM总线上了。

关于换个PLATFORM

DEVICE,还有一个很关键的地方就是该结构一个重要的元素是resource,该元素存入了最为重要的设备资源信息,定义在kernel\include\linux\ioport.h中,

struct resource {

const char *name;

unsigned long start, end;

unsigned long flags;

struct resource *parent, *sibling, *child;

};

具体可以这么定义:

static struct resource s3c_lcd_resource[] = {

[0] = {

.start = S3C24XX_PA_LCD,

.end = S3C24XX_PA_LCD + S3C24XX_SZ_LCD - 1,

.flags = IORESOURCE_MEM,

},

[1] = {

.start = IRQ_LCD,

.end = IRQ_LCD,

.flags = IORESOURCE_IRQ,

}

};

这里定义了两组resource,它描述了一个LCD设备的资源,第1组描述了这个LCD设备所占用的

总线地址范围,也就是DATASHEET上LCD控制器的寄存器地址的范围IORESOURCE_MEM表示第1组描述的是内存类型的资源信息,第2组描述了这个LCD设备的中断号,IORESOURCE_IRQ表示第2组描述的是中断资源信息。设备驱动会根据flags来获取相应的资源信息。

设备驱动

在LINUX中,一个设备驱动是以device_driver这个结构描述的(还包含着许多其他的结构成员,只是我们现在暂时不考虑)。

struct device_driver {

const

char *name;

struct

bus_type *bus;

int (*probe) (struct device *dev);

struct driver_private *p;

};

driver_register用来把去总挂接到总线上。

和DEVICE类似,在写驱动的时候,我们也会把device_driver 包装一下,比如platform_driver。

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 (*resume)(struct platform_device *);

struct device_driver

driver; const struct

platform_device_id *id_table;

};

我们定义一个LCD驱动:

static struct platform_driver s3c_fb_driver = {

.probe =

s3c_fb_probe,

.remove =

__devexit_p(s3c_fb_remove),

.id_table =

s3c_fb_driver_ids,

.driver =

{

.name =

"s3c-fb",

.owner =

THIS_MODULE,

.pm =

&s3cfb_pm_ops,

},

};

在模块初始化的时候,调用platform_driver_register调用driver_register把注册platform_driver到PLATFORM总线上,去看看platform_driver_register的实现就什么都清楚了。

int platform_driver_register(struct platform_driver *drv)

{

drv->driver.bus =

&platform_bus_type; if

(drv->probe)

drv->driver.probe

= platform_drv_probe;

if (drv->remove)

drv->driver.remove

= platform_drv_remove;

if (drv->shutdown)

drv->driver.shutdown

= platform_drv_shutdown;

return

driver_register(&drv->driver);

}

在一个驱动中,最最重要的一个接口就是probe函数,它负责去获取对应DEVICE的数据信息,然后初始化这个设备。如果完成这个函数,我们就完成了驱动的大部分工作了。暂时先停住,在具体的驱动分析中,我们再来考虑这个问题。

到目前为止,我们还没有说清楚这个DEVICE和DRIVER是怎么联系起来的。正如你想到的,我们要去总线那里去看看,它是设备模型中的管理者嘛,因此把驱动和设备联系起来,就是它主要的工作了。

总线

我们直接看代码,看看总线在系统中是如何表述的:

struct bus_type {

const

char *name;

int (*match)(struct device *dev, struct

device_driver *drv);

int (*probe)(struct device *dev);

};

name 就是总线的名字,比如PLATFORM,PCI,I2C等等。

对于总线,PLATFORM总线倒是没有封装,直接采用下边方法定义:

struct bus_type platform_bus_type = {

.name =

"platform",

.match =

platform_match,

};

这条总线在系统初始化的时候通过platform_bus_init来初始化,显然这个动作应该驱动注册之前完成。

总线是用MATCH方法来完成驱动和设备的联系的。当总线上的新设备或者新的驱动程序被添加的时候,会来调用这个函数。如果指定的驱动程序能够处理指定的设备,干函数就返回非0,去执行驱动的PROBE函数.

我们结合驱动的注册来看看这个过程是什么样子的?

在驱动初始化函数中调用函数platform_driver_register()注册platform_driver,需要注意的是

platform_device结构中name元素和platform_driver结构中driver.name必须是相同的,这样

在platform_driver_register()注册时会对所有已注册的所有platform_device中的name和当前注

册的platform_driver的driver.name进行比较,只有找到相同的名称的platfomr_device才能注册

成功,当注册成功时会调用platform_driver结构元素probe函数指针。

platform_driver_register

driver_register

bus_add_driver

driver_attach

__driver_attach

driver_match_device

如果MATCH成功,测开始PROBE.

driver_probe_device

really_probe

dev->bus->probe(dev)

dev->probe

platform_driver->probe

这样就直接走到我们驱动PROBE函数中,这个过程很负责,但是对于我们来说只要记住亮点:

1)MATCH的标准: NAME 要相同,或者有的驱动和设备支持ID

2)MATCH成功,我们就转向驱动的PROBE函数。

我们来看一个驱动的PROBE函数:

static int s3c_fb_probe(struct platform_device

*pdev)

{

struct s3c_fb_driverdata *fbdrv;

struct device *dev =

&pdev->dev;

struct s3c_fb_platdata *pd;

struct s3c_fb *sfb;

struct resource *res;

int win;

int ret = 0;

//这个数据是在设备定义的时候定义的,就是我们前面看到的内存,IRQ等资源

fbdrv = (struct s3c_fb_driverdata

*)platform_get_device_id(pdev)->driver_data;

pd =

pdev->dev.platform_data;

sfb = kzalloc(sizeof(struct s3c_fb),

GFP_KERNEL);

dev_dbg(dev, "allocate new framebuffer %p\n",

sfb);

sfb->dev = dev;

sfb->pdata = pd;

sfb->variant =

fbdrv->variant;

//获取时钟,并且ENABLE它

sfb->bus_clk = clk_get(dev,

"lcd");

clk_enable(sfb->bus_clk);

//处理来自驱动的资源,记着去ioremap,为什么呢?见下边。

res = platform_get_resource(pdev,

IORESOURCE_MEM, 0);

sfb->regs_res =

request_mem_region(res->start,

resource_size(res),

dev_name(dev));

sfb->regs =

ioremap(res->start, resource_size(res));

res = platform_get_resource(pdev, IORESOURCE_IRQ,

0);

sfb->irq_no =

res->start;

ret = request_irq(sfb->irq_no,

s3c_fb_irq,

0, "s3c_fb", sfb);

platform_set_drvdata(pdev, sfb);

//初始化硬件

pd->setup_gpio();

writel(pd->vidcon1,

sfb->regs + VIDCON1);

for (win = 0; win <

fbdrv->variant.nr_windows; win++)

s3c_fb_clear_win(sfb, win);

for (win = 0; win <

(fbdrv->variant.nr_windows - 1); win++) {

void __iomem *regs =

sfb->regs + sfb->variant.keycon;

regs += (win * 8);

writel(0xffffff, regs +

WKEYCON0);

writel(0xffffff, regs +

WKEYCON1);

}

for (win = 0; win <

fbdrv->variant.nr_windows; win++) {

if

(!pd->win[win])

continue;

if

(!pd->win[win]->win_mode.pixclock)

s3c_fb_missing_pixclock(&pd->win[win]->win_mode);

ret = s3c_fb_probe_win(sfb,

win, fbdrv->win[win],

&sfb->windows[win]);

if (ret < 0)

{

dev_err(dev,

"failed to create window %d\n", win);

for (; win

>= 0; win--)

s3c_fb_release_win(sfb,

sfb->windows[win]);

goto

err_irq;

}

}

platform_set_drvdata(pdev, sfb);

pm_runtime_put_sync(sfb->dev);

return 0;

//出错处理

return ret;

}

这里说明一下如何获取资源的:

当进入probe函数后,需要获取设备的资源信息,获取资源的函数有:

struct resource * platform_get_resource(struct platform_device

*dev, unsigned int type, unsigned int num);

根据参数type所指定类型,例如IORESOURCE_MEM,来获取指定的资源。

struct int platform_get_irq(struct platform_device *dev, unsigned

int num);

获取资源中的中断号。

struct resource * platform_get_resource_byname(struct

platform_device *dev, unsigned int type, char *name);

根据参数name所指定的名称,来获取指定的资源。

int platform_get_irq_byname(struct platform_device *dev, char

*name);

根据参数name所指定的名称,来获取资源中的中断号。

ioremap

是用来把资源中定义的物理地址转换成内核虚拟地址的,我们的代码中用到的地址是虚拟地址,必须做这样的转换哟。还有一种静态的转换方法,我们以后再来看。

在ioremap之后,我们就可以读写外设的寄存器了,比如控制寄存器,状态寄存器,数据寄存器等等,这样就可以操作外围设备了。

当然,还有一些其他东西没有提,对应各种注册,添加函数,同样存在注销,移除等函数,基本就是做相反的操作,只要稍微看看就行了。

上边的例子是以PLATFORM 的总线,设备和驱动来讲的,其实I2C等等总线以及设备也采取的是大致的流程。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值