2016-12-05 15:12:11 Creator_Ly 阅读数 1201
  • C语言入门到阶-- 跨平台的 Hello, world 与 ...

    尹成老师、带你步入 C 语言的殿堂,讲课生动风趣、深入浅出,全套视频内容充实,整个教程以 C 语言为核心,完整精彩的演练了数据结构、算法、设计模式、数据库、大数据高并发检索、文件重定向、多线程同步、进程通讯、黑客劫持技术、网络安全、加密解密,以及各种精彩的小项目等,非常适合大家学习!帮助大家顺利入门C语言

    22627 人正在学习 去看看 尹成

Linux mtd system

MTD(Memory Technology Device),内存技术设备是Linux的存储设备中的一个子系统。其设计此系统的目的是,对于内存类的设备,提供一个抽象层,一个接口,使得对于硬件驱动设计者来说,只需要去提供最简单的底层硬件设备的读/写/擦除函数就可以了,数据对于上层使用者来说是如何表示的,可以不关心,因为MTD存储设备子系统都帮你做好了。

MTD框架

Linux的MTD设备位于drivers/mtd/下面,这边只对其目录结构进行大致的分析,如果想了解具体的细节可以查看crifan的博客,里面有很多连接和文章可供参考,分析的很透彻,MTD文件下的内容如下:

这里写图片描述
MTD设备通常可分为四层

上到下依次是:设备节点、MTD设备层、MTD原始设备层和硬件驱动层。
这里写图片描述

1.cmdlinepart.c

当mtd分区表由u-boot通过cmd参数传输给linux时,linux内核可以不用对mtdparts进行注册添加,只需要将MTD中的command line partition选项开启即可。使用这种的方法u-boot下需要对MTD进行支持,且所传输的mtd分区参数要符合格式要求。

2.devices文件夹

当我们有一个spi flash设备时且要使用mtd进行管理,我们一般会将其放在devices文件夹下,如devices文件夹下面的m25p80.c就是一个典型的spi flash设备。

3.chips/maps/nand/onenand文件夹

nand flash 驱动在nand文件夹下;

onenand flash 驱动在onenand文件夹下;

nor flash比较杂,下面几个文件下都会有:

  • chips:cfi/jedec接口通用驱动
  • devices:nor flash底层驱动(spi flash)
  • maps:nor flash映射关系相关函数

4.核心文件

mtdchar.c : MTD字符设备接口相关实现,设备号31;

mtdblock.c : MTD块设备接口相关实现,设备号90,;

mtdcore.c: MTD原始设备接口相关实现;

mtdpart.c : MTD分区接口相关实现。

5.ubi

ubifs文件的支持层,当使用ubifs文件系统时,需要将Device Drivers -> Memory Technology Device (MTD) support -> UBI -Unsorted block image 中的Enable UBI选中。

将File systems -> Miscellaneous filesystems中的UBIFS file system support选中。

MTD分区表的实现

在开机过程从console经常可以看到类似以下信息,

0x000000000000-0x000000100000 : "Bootloade"
0x000000100000-0x000002000000 : "Kernel"
0x000002000000-0x000003000000 : "User"
0x000003000000-0x000008000000 : "File System"

这就是MTD给我们一种最直观的表示形式,给我们展示了内存中各模块的分区结构,但这些分区是怎样实现的呢?分区表的实现方式有几种,下面进行分别说明:

注:分区表实现的前提是MTD设备驱动已经成功了,否则连驱动都没成功就无分区可说了。

1.内核中添加

在内核中添加这是一个比较经常使用的方法,随便一本驱动移植的书上应该都有,主要就是在平台设备里面添加mtd_partition,添加类似下面的信息,这边就不过多描述

struct mtd_partition s3c_nand_part[] = {
    {
        .name       = "Bootloader",
        .offset     = 0,
        .size       = (1 * SZ_1M),
        .mask_flags = MTD_CAP_NANDFLASH,
    },
    {
        .name       = "Kernel",
        .offset     = (1 * SZ_1M),
        .size       = (31 * SZ_1M) ,
        .mask_flags = MTD_CAP_NANDFLASH,
    },
    {
        .name       = "User",
        .offset     = (32 * SZ_1M),
        .size       = (16 * SZ_1M) ,
    },
    {
        .name       = "File System",
        .offset     = (48 * SZ_1M),
        .size       = (96 * SZ_1M),
    }
};

static struct s3c_nand_set s3c_nand_sets[] = {
    [0] = {
        .name       = "nand",
        .nr_chips   = 1,
        .nr_partitions  = ARRAY_SIZE(s3c_nand_part),
        .partitions = ok6410_nand_part,
    },
};

static struct s3c_platform_nand s3c_nand_info = {
    .tacls      = 25,
    .twrph0     = 55,
    .twrph1     = 40,
    .nr_sets    = ARRAY_SIZE(s3c_nand_sets),
    .sets       = ok6410_nand_sets,
};

static void __init s3c_machine_init(void)
{
    s3c_nand_set_platdata(&s3c_nand_info); 
}

因为我们的MTD驱动已经完成了,当device和driver匹配后会调用驱动中的probe接口函数,我们需要在probe函数里面调用add_mtd_partitions(s3c_mtd, sets->partitions, sets->nr_partitions);实现分区表的添加。

2.u-boot传参

在u-boot下可以通过添加mtdparts信息到bootargs中,u-boot启动后会将bootargs中的信息传送给kernel,,kernel在启动的时候会解析bootargs中mtdparts的部分,这边举个例子:

mtdparts=nand.0:1M(Bootloader)ro,31M(Kernel)ro,16M(User),96M(File System),更具体的mtdparts格式可以查阅下相关资料。

为了使kernel能够解析mtdparts信息,我们需要将内核中的Device Drivers -> Memory Technology Device (MTD) support ->Command line partition table parsing选项开启,这在上面已经说过。

在内核中添加分区表的时候,我们是在平台设备里面加入mtd_partition信息。这边通过u-boot传参则取消平台设备里面的partition信息,那我们需要怎样解析u-boot的传过来的mtdparts呢。

u-boot传参过来后,cmdlinepart.c中会将这些参数解析好,存在里面LIST_HEAD(part_parsers)链表里面,然后我们在驱动的probe函数中,通过调用mtd_device_parse_register(mtd, probe_types,&ppdata, NULL, 0);函数。

mtd_device_parse_register()函数位于drivers/mtd/mtdcore.c 中,内容如下:

int mtd_device_parse_register(struct mtd_info *mtd, const char * const *types,
                  struct mtd_part_parser_data *parser_data,
                  const struct mtd_partition *parts,
                  int nr_parts)
{
    int err;
    struct mtd_partition *real_parts;

    err = parse_mtd_partitions(mtd, types, &real_parts, parser_data);
    if (err <= 0 && nr_parts && parts) {
        real_parts = kmemdup(parts, sizeof(*parts) * nr_parts,
                     GFP_KERNEL);
        if (!real_parts)
            err = -ENOMEM;
        else
            err = nr_parts;
    }

    if (err > 0) {
        err = add_mtd_partitions(mtd, real_parts, err);
        kfree(real_parts);
    } else if (err == 0) {
        err = add_mtd_device(mtd);
        if (err == 1)
            err = -ENODEV;
    }

    return err;
}

可以看到该函数会先执行parse_mtd_partitions(mtd, types, &real_parts, parser_data);函数,后面还是通过add_mtd_partitions()函数来实现分区表的添加。

parse_mtd_partitions()函数位于drivers/mtd/mtdpart.c中,内容如下:

int parse_mtd_partitions(struct mtd_info *master, const char *const *types,
             struct mtd_partition **pparts,
             struct mtd_part_parser_data *data)
{
    struct mtd_part_parser *parser;
    int ret = 0;

    if (!types)
        types = default_mtd_part_types;

    for ( ; ret <= 0 && *types; types++) {
        parser = get_partition_parser(*types);
        if (!parser && !request_module("%s", *types))
            parser = get_partition_parser(*types);
        if (!parser)
            continue;
        ret = (*parser->parse_fn)(master, pparts, data);
        put_partition_parser(parser);
        if (ret > 0) {
            printk(KERN_NOTICE "%d %s partitions found on MTD device %s\n",
                   ret, parser->name, master->name);
            break;
        }
    }
    return ret;
}

进入parse_mtd_partitions()函数会先判断types的类型,如果为空则给默认值,types的类型一般就两种,如下:

static const char * const default_mtd_part_types[] = {
    "cmdlinepart",
    "ofpart",
    NULL
};

第一个”cmdlinepart”即u-boot传参的方式,第二个”ofpart”即下面要讲到的使用dts传参的方式,判断完类型后,就通过get_partition_parser去解析part_parsers链表里面的数据,这样就完成u-boot参数的解析。

3.dts传参

在Linux3.14以后的linux版本中,加入一个新的知识DTS(Device tree),dts其实就是为了解决ARM Linux中的冗余代码,在Linux2.6版本的arch/arm/plat.xxx和arch/arm/mach.xxx中充斥着大量的垃圾代码,采用Device Tree后,许多硬件的细节可以直接透过它传递给Linux,而不再需要在kernel中进行大量的冗余编码,关于dts可以自行查阅资料。

dts传参的原理其实和u-boot一样,区别在于:u-boot的时候是通过cmdlinepart.c文件实现分区信息写入LIST_HEAD(part_parsers)链表,dts则是用过ofpart.c文件实现分区信息写入LIST_HEAD(part_parsers)链表,所以同样要把ofpart.c文件的宏打开,在调用mtd_device_parse_register(mtd, probe_types,&ppdata, NULL, 0);函数的时候types要设置成ofpart。

如果去对比Linux2.6版本和Linux3.14版本,会发现drivers/mtd/ofpart.c和drivers/mtd/mtdpart.c文件有所不同,Linux3.8版本里面多了Device tree这一部分的内容,感兴趣的可以自己深究下。

这边举个dts的例子:

 pinctrl-0 = <&s3c_nand_flash>;
    ranges = <0 0 0x000000000000 0x000008000000>;   /* CS0: NAND */
    nand@0,0 {
        partition@1 {
            label = "Bootloader";
            reg = <0x000000000000 0x000000100000>;
        };
        partition@2 {
            label = "Kernel";
            reg = <0x000000100000 0x000002000000>;
        };
        partition@3 {
            label = "User";
            reg = <0x000002000000 0x000003000000>;
        };
        partition@4 {
            label = "File System";
            reg = <0x000003000000 0x000008000000>;
        };
    };

MTD分区表的三种实现方式这边做了简单介绍,具体还需自行深入研究实践,有误的地方还请指正修改。

注:以上内容都是本人在学习过程积累的一些心得,难免会有参考到其他文章的一些知识,如有侵权,请及时通知我,我将及时删除或标注内容出处。文章只是起一个引导作用,详细的数据解析内容还请查看Linux相关教程,感谢您的查阅。

2016-12-14 16:03:22 Creator_Ly 阅读数 946
  • C语言入门到阶-- 跨平台的 Hello, world 与 ...

    尹成老师、带你步入 C 语言的殿堂,讲课生动风趣、深入浅出,全套视频内容充实,整个教程以 C 语言为核心,完整精彩的演练了数据结构、算法、设计模式、数据库、大数据高并发检索、文件重定向、多线程同步、进程通讯、黑客劫持技术、网络安全、加密解密,以及各种精彩的小项目等,非常适合大家学习!帮助大家顺利入门C语言

    22627 人正在学习 去看看 尹成

Linux time system

Linux中跟时间有关的函数变量有很多,但是对开发者来说无非就是用了计时/延时/挂起/获取时间等,下面列出自己所使用过的一些时间接口,供大家理解使用。

jiffies

在内核代码中,到处充斥着jiffies变量,jiffies在include/linux/jiffies.h中定义,在linux启动的时候会将jiffies清0,当系统完成对时钟中断的初始化后,在每个时钟滴答时jiffies都会被加1,所以在驱动程序开发过程中,使用jiffies变量就已足够所有基于jieffies的时间度量任务。

jiffies的使用通常要配合着宏HZ,宏HZ的定义位于.confg,一般都将其设置为1000,1秒1000次即1ms。
例:

time1 = jiffies + 2 * HZ;则time1表示未来的2秒;
time2 = jiffies + 3 * HZ/1000;则time2表示未来的3毫秒;

在jiffies.h还为我们提供了很多接口函数,主要有两类,一类为时间的比较,一类为时间的转化。 下面进行简单说明举例:
1、时间的比较

time_after(a,b);        //如果时间点a在时间点b之后,则返回1
time_before(a,b);       //如果时间点a在时间点b之前,则返回1
time_after_eq(a,b);     //如果时间点a在时间点b之后或等于b,则返回1
time_before_eq(a,b);    //如果时间点a在时间点b之前或等于b,则返回1
time_in_range(a,b,c);   //如果时间点a在时间点b和c之间或等于b或c,则返回1

例:

int time_test()
{
    unsigned long timeout = jiffies + 2 * HZ/1000;  //2ms
    do_time_task();
    if(time_after(jiffies,timeout))    //如果do_time_task()函数执行超过2ms,则成立
        return 1;
    return 0;
}

2.时间转化
为了方便理解,我们需要将jiffies转化成比较直观的毫秒ms或者是微妙us等形式,jiffies.h里面一个了一些比较直观的转化函数,其实现函数在kernel/time.c里面 如:

unsigned int jiffies_to_msecs(const unsigned long j);
unsigned int jiffies_to_usecs(const unsigned long j);
unsigned long msecs_to_jiffie(const unsigned int m);
unsigned long usecs_to_jiffie(const unsigned int u);

do_gettimeofday

当我们需要得到某一段代码所执行的时间是多少,可以使用do_gettimeofday函数,该函数定义在kernel/time/timekeeping.c中,申明在include/linux/time.h中,do_gettimeofday函数是在内核的函数,如果在应用层实现该这个函数则使用gettimeofday,包含sys/time.h即可,使用方法与do_gettimeofday一样。

举个简单的例子:

int gettimeofday_test()
{
    struct timeval tv_start;
    struct timeval tv_end;
    float timeuse;

    gettimeofday(&tv_start, NULL);
    do_gettimeofday_task();
    gettimeofday(&tv_end, NULL);

    timeuse = 1000000*(tv_end.tv_sec - tv_start.tv_sec) + tv_end.tv_usec - tv_start.tv_usec;
    timeuse /= 1000;
    printf("Time-consuming: %f ms\n",  timeuse);

    return 0;
}

delay

delay函数为死等待,这种函数只能用在短延时上,如微妙甚至纳秒等级的 Linux内核提供了如下短延时接口函数,位于include/linux/delay.h中

void mdelay(usigned long msecs); 
void udelay(usigned long usecs); 
void ndelay(usigned long nsecs);

sleep

上面的短延时为忙等待,当为长延时不能为忙等待,需要将任务挂起,这是我们可以使用sleep函数 在kernel/timer.c里面有实现了msleep()函数,也在include/linux/delay.h中申明,其原型如下,

sleep也是我们在写shell脚本时比较经常使用的延时、循环执行命令,直接调用即可使用。

内核定时器timer

如果希望在内核中以一定的时间间隔来执行执行某项任务,如:每隔一秒查询一次button的状态,可以使用内核定时器来实现。内核中已经为我们提供了定时器数据结构timer_list,和定时器的接口函数,位于include/linux/timer.h中。

定时器timer的实现步骤就两步:

  • 初始化定时器,设置定时器的触发时间,指定该定时器的回调函数。
  • 实现定时器回调函数,如果需要循环执行,则需要在该定时器的回调函数中重新启动该定时器。

举个简单的例子:

#define TIMER_FUNC(_fn)  void _fn(unsigned long timer_arg)
#define TIMER_INIT(_osdev, _timer, _fn, _arg)        \
do {                                                 \
        init_timer(_timer);                          \
        (_timer)->function = (_fn);                  \
        (_timer)->data = (unsigned long)(_arg);      \
} while (0)
#define TIMER_SET(_timer, _ms)  mod_timer(_timer, jiffies + ((_ms)*HZ)/1000)

struct timer_list os_timer_test; 

static TIMER_FUNC(timer_test_func)
{
    TIMER_SET(&os_timer_test, 1000);   //重新设置该定时器
}

int timer_test()
{
    TIMER_INIT(NULL, &os_timer_test, timer_test_func, &os_timer_test);    //初始化定时器,绑定回调函数
    TIMER_SET(&os_timer_test, 1000);   //1s
    return 0;
}

系统时间date

date主要用来显示和设定系统的日期与时间,通常会在shell中使用到data命令

1.data的显示

最直接的方式即输入date,如下:

# date 
Thu Dec 15 14:28:19 GMT 2016

这种方式会将信息以一定的格式显示出来,星期、月份、日期、时:分:秒、时区、年份
如果我们需要得到更具体的信息可以通过”data +%$”来获取,如:

# date +%H    //小时
14
# date +%m    //月份
12
# date +%y    //年份
16

具体指令如下:

命令 含义
%H 小时(00..23)
%I 小时(01..12)
%k 小时(0..23)
%l 小时(1..12)
%M 分钟(00..59)
%p 显示本地 AM 或 PM
%r 直接显示时间 (12 小时制,格式为 h : m : s [AP]M)
%s 从 1970 年 1 月 1 日 00:00:00 UTC 到目前为止的秒数
%S 秒(00..61)
%T 直接显示时间 (24 小时制)
%X 相当于 %H:%M:%S
%Z 显示时区
%a 星期几 (Sun..Sat)
%A 星期几 (Sunday..Saturday)
%b 月份 (Jan..Dec)
%B 月份 (January..December)
%c 直接显示日期与时间
%d 日 (01..31)
%D 直接显示日期 (mm/dd/yy)
%h 同 %b
%j 一年中的第几天 (001..366)
%m 月份 (01..12)
%U 一年中的第几周 (00..53) (以 Sunday 为一周的第一天的情形)
%w 一周中的第几天 (0..6)
%W 一年中的第几周 (00..53) (以 Monday 为一周的第一天的情形)
%x 直接显示日期 (mm/dd/yy)
%y 年份的最后两位数字 (00.99)
%Y 完整年份 (0000..9999)

2.data的设置
既然读取到了时钟发现错误时就需要修改,修改的方式有很多,这边就不想弄的那么复杂,用一个最直观简单的格式,如下:

# date -s "2016-12-15 14:43:33"
Thu Dec 15 14:43:33 GMT 2016

RTC时钟hwclock

linux系统时钟有两个,一个是硬件时钟,一般就是RTC时钟,另一个是系统时钟,即上面使用date时得到的信息,它是linux系统Kernel时间。当Linux启动时,系统Kernel会去读取硬件时钟的设置,然后系统时钟就会独立于硬件运作。

硬件时钟可以通过hwclock来获取,如下:

# hwclock 
Thu Dec 15 11:12:07 2016  0.000000 seconds

当我们修改了系统时钟后,想将系统时间同步到硬件时钟可以通过hwclock -w命令实现,如下:

# date -s "2016-12-15 15:40:00"
Thu Dec 15 15:40:00 GMT 2016
# hwclock 
Thu Dec 15 11:24:25 2016  0.000000 seconds
# hwclock -w
# hwclock 
Thu Dec 15 15:40:23 2016  0.000000 seconds

busybox里面也实现从硬件时钟同步到系统时钟的命令hwclock -s,但这个指令一般很少用,因为kernel一启动系统时钟就是读取硬件的时钟,所以两则已经相等了,我们发现系统时钟不对时,则会去修改系统时钟再同步到硬件时钟。

注:以上内容都是本人在学习过程积累的一些心得,难免会有参考到其他文章的一些知识,如有侵权,请及时通知我,我将及时删除或标注内容出处。文章只是起一个引导作用,详细的数据解析内容还请查看Linux相关教程,感谢您的查阅。

2017-01-03 17:07:27 Creator_Ly 阅读数 588
  • C语言入门到阶-- 跨平台的 Hello, world 与 ...

    尹成老师、带你步入 C 语言的殿堂,讲课生动风趣、深入浅出,全套视频内容充实,整个教程以 C 语言为核心,完整精彩的演练了数据结构、算法、设计模式、数据库、大数据高并发检索、文件重定向、多线程同步、进程通讯、黑客劫持技术、网络安全、加密解密,以及各种精彩的小项目等,非常适合大家学习!帮助大家顺利入门C语言

    22627 人正在学习 去看看 尹成

Linux platform system

platform是Linux内的一种虚拟总线,称为platform总线,相应的设备称为platform_device,而驱动称为platform_driver。platform总线、设备和驱动这3个实体,总线将设备和驱动绑定,在系统每注册一个设备的时候,会寻找与之匹配的驱动;相反的,在系统每注册一个驱动的时候,会寻找与之匹配的设备,而匹配由总线完成。

先来看下platform的框架:
这里写图片描述
platform框架其实比较直观,就总线/设备/驱动三部分,所以platform的实现一般就为三个步骤,先对platform设备进行注册,再编写platform驱动,最后设备和驱动进行匹配,匹配成功则执行probe探测函数,probe函数里面对具体的功能实现,下面对这三步的内容进行简单分析。

1.platform device

platform设备对应的结构体paltform_device,位于linux/platform_device.h中,如下:

struct platform_device {
    const char  * name;    //设备的名字,这将代替device->dev_id,用作sys/device下显示的目录名
    int     id;            //设备id,用于给插入给该总线并且具有相同name的设备编号,如果只有一个设备的话填-1。
    struct device   dev;   //结构体中内嵌的device结构体。
    u32     num_resources; //资源数。
    struct resource * resource;  //资源数。

    const struct platform_device_id *id_entry;

    /* MFD cell pointer */
    struct mfd_cell *mfd_cell;

    /* arch specific additions */
    struct pdev_archdata    archdata;
};

可以看到,platform_device封装了指定的名字name、id、内嵌device。

platform_device的注册一般在平台设备里面实现,这边举例说明下:

1.先要为设备提供platform_device结构体,并赋值,这边以i2c为例。

static struct resource s3c_i2c_resource[] = {
    [0] = {
        .start = S3C_PA_IIC,
        .end   = S3C_PA_IIC + SZ_4K - 1,
        .flags = IORESOURCE_MEM,
    },
    [1] = {
        .start = IRQ_IIC,
        .end   = IRQ_IIC,
        .flags = IORESOURCE_IRQ,
    },
};

struct platform_device s3c_device_i2c0 = {
    .name         = "s3c-i2c",
    .id       = -1,
    .num_resources    = ARRAY_SIZE(s3c_i2c_resource),
    .resource     = s3c_i2c_resource,
};

2.有了各设备的platform_device后,一般会统一放到用来注册的platform_device,里面会包含各自platform设备,如下:

static struct platform_device *s3c_devices[] __initdata = 
{
    &s3c_device_i2c0,
    &s3c_device_spi,
    &s3c_device_hsmmc0,
    &s3c_device_hsmmc1,
}

3.最后使用platform_add_devices进行将device添加到platform总线上。

platform_add_devices(s3c_devices, ARRAY_SIZE(s3c_devices));

该函数位于drivers/base/platform.c中,如下:

int platform_add_devices(struct platform_device **devs, int num)
{
    int i, ret = 0;

    for (i = 0; i < num; i++) {
        ret = platform_device_register(devs[i]);
        if (ret) {
            while (--i >= 0)
                platform_device_unregister(devs[i]);
            break;
        }
    }

    return ret;
}

可以看到platform_device会扫描s3c_devices里面的每一个设备,并为它们进行注册,这边的注册和注销使用platform_device_register()platform_device_unregister()函数。

注册后,同样会在/sys/device/目录下创建一个以name命名的目录,并且创建软连接到/sys/bus/platform/device下。

2.platform driver

platform驱动对应的结构体paltform_driver,位于linux/platform_device.h中,如下:

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;
};

与device类似,driver里面嵌入的是device_driver,不过driver里面有实现函数probe、remove等。
platform_driver的实现也比较简单,这边也以i2c为例,如下:

static struct platform_driver s3c_i2c_driver = {
    .probe      = s3c_i2c_probe,
    .remove     = s3c_i2c_remove,
    .id_table   = s3c_driver_ids,
    .driver     = {
        .owner  = THIS_MODULE,
        .name   = "s3c-i2c",
        .pm = S3C_DEV_PM_OPS,
    },
};

static int __init i2c_adap_s3c_init(void)
{
    return platform_driver_register(&s3c24xx_i2c_driver);
}

static void __exit i2c_adap_s3c_exit(void)
{
    platform_driver_unregister(&s3c_i2c_driver);
}

module_init(i2c_adap_s3c_init);
module_exit(i2c_adap_s3c_exit);

platform驱动的注册和注销分别用platform_driver_register()platform_driver_unregister()函数。

3.platform match

platform driver编写完成后,就是要执行driver的probe函数。probe函数能否执行的关键在于,device中的name与driver中的name是否相等,相等时说明匹配成功,就可以执行probe函数实现具体的功能了。

这边也追踪下如何执行到match函数,驱动会调用platform_driver_register进行注册,该函数会将总线指向platform总线,而platform总线的结构体如下:

struct bus_type platform_bus_type = {
    .name       = "platform",
    .dev_attrs  = platform_dev_attrs,
    .match      = platform_match,
    .uevent     = platform_uevent,
    .pm     = &platform_dev_pm_ops,
};

可以看到platform_matchplatform_bus的一个成员,platform_match的实现如下:

static int platform_match(struct device *dev, struct device_driver *drv)
{
    struct platform_device *pdev = to_platform_device(dev);
    struct platform_driver *pdrv = to_platform_driver(drv);

    /* Attempt an OF style match first */
    if (of_driver_match_device(dev, drv))
        return 1;

    /* Then try to match against the id table */
    if (pdrv->id_table)
        return platform_match_id(pdrv->id_table, pdev) != NULL;

    /* fall-back to driver name match */
    return (strcmp(pdev->name, drv->name) == 0);
}

platform_match会调用platform_match_id()函数,匹配函数platform_match_id()会去搜索所有已经注册到platform总线上的设备,如果匹配到则返回id,没找到则返回NULL如下:

static const struct platform_device_id *platform_match_id(
            const struct platform_device_id *id,
            struct platform_device *pdev)
{
    while (id->name[0]) {
        printk("pdev->name:%s\n",pdev->name);
        printk("id->name:%s\n",id->name);
        if (strcmp(pdev->name, id->name) == 0) {
            pdev->id_entry = id;
            return id;
        }
        id++;
    }
    return NULL;
}

这边两句printk调试信息是我自己加上去的,在调试的时候可以很直观的观察到两个name是否相等,在Linux驱动中很多设备都是通过这一原理来进行device和driver的匹配,如下面几个模块:
1.drivers/i2c/i2c-core.c中的i2c_match_id

static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
                        const struct i2c_client *client)
{
    while (id->name[0]) {
        if (strcmp(client->name, id->name) == 0)
            return id;
        id++;
    }
    return NULL;
}

2.drivers/spi/spi.c中的spi_match_id

static const struct spi_device_id *spi_match_id(const struct spi_device_id *id,
                        const struct spi_device *sdev)
{
    while (id->name[0]) {
        if (!strcmp(sdev->modalias, id->name))
            return id;
        id++;
    }
    return NULL;
}

3.drivers/pci/pci-driver.c中的pci_match_id

const struct pci_device_id *pci_match_id(const struct pci_device_id *ids,
                     struct pci_dev *dev)
{
    if (ids) {
        while (ids->vendor || ids->subvendor || ids->class_mask) {
            if (pci_match_one_device(ids, dev))
                return ids;
            ids++;
        }
    }
    return NULL;
}

4.drivers/usb/core/driver.c中的usb_match_id

const struct usb_device_id *usb_match_id(struct usb_interface *interface,
                     const struct usb_device_id *id)
{
    if (id == NULL)
        return NULL;

    for (; id->idVendor || id->idProduct || id->bDeviceClass ||
           id->bInterfaceClass || id->driver_info; id++) {
        if (usb_match_one_id(interface, id))
            return id;
    }

    return NULL;
}

5.drivers/hid/hid-core.c中的hid_match_id

static const struct hid_device_id *hid_match_id(struct hid_device *hdev,
        const struct hid_device_id *id)
{
    for (; id->bus; id++)
        if (hid_match_one_id(hdev, id))
            return id;

    return NULL;
}

如果发现打印的name不一样或有一个没有打印出来,这是候就要去检测对应的device和driver,可能就是总线没注册上或驱动id、name不一致。

Linux platform system的分析就到这边,有感悟时会持续会更新。

注:以上内容都是本人在学习过程积累的一些心得,难免会有参考到其他文章的一些知识,如有侵权,请及时通知我,我将及时删除或标注内容出处,如有错误之处也请指出,进行探讨学习。文章只是起一个引导作用,详细的数据解析内容还请查看Linux相关教程,感谢您的查阅。

2017-02-10 20:43:49 Creator_Ly 阅读数 444
  • C语言入门到阶-- 跨平台的 Hello, world 与 ...

    尹成老师、带你步入 C 语言的殿堂,讲课生动风趣、深入浅出,全套视频内容充实,整个教程以 C 语言为核心,完整精彩的演练了数据结构、算法、设计模式、数据库、大数据高并发检索、文件重定向、多线程同步、进程通讯、黑客劫持技术、网络安全、加密解密,以及各种精彩的小项目等,非常适合大家学习!帮助大家顺利入门C语言

    22627 人正在学习 去看看 尹成

Linux proc system

proc 文件系统是由内核创建的虚拟文件系统,被内核用来向外界报告信息的一个文件系统,存储着当前内核运行状态的一系列特殊文件,是在系统运行时才创建的,所以它仅存在于内存之中而不在外存(硬盘、flash)上。通过proc虚拟文件可以实现Linux内核空间和用户间之间进行通信,可以说是内核向用户空间打开的一扇窗户。

1.proc目录分析


Linux系统开机完成进入文件系统后,进入proc目录

这边对proc目录下常见文件进行简要分析:

1.cat /proc/number

proc目录下包含许多以数字命名的子目录,这些数字表示系统当前正在运行进程的进程号,里面包含对应该进程相关的多个信息文件。

例如打开进程270的目录,如下:

/proc/270# ls
auxv             exe              net              stack
cgroup           fd               ns               stat
clear_refs       fdinfo           oom_adj          statm
cmdline          limits           oom_score        status
comm             maps             oom_score_adj    task
coredump_filter  mem              pagemap          wchan
cpuset           mountinfo        personality
cwd              mounts           root
environ          mountstats       smaps

其中每一个文件都有具体的含义,

如:cmdline-启动当前进程的完整命令,mem-当前进程所占用的内存空间等

2.cat /proc/cmdline

在启动时传递至内核的相关参数信息,这些信息通常由u-boot传递的;

/proc# cat cmdline 
console=ttyS0,115200n8, init=/etc/preinit mac_addr=00:0A:0B:0C:0D:0E,, ip=none root=ubi0:rootfs ubi.mtd=4 rootfstype=ubifs rw noinitrd 

3.cat /proc/cpuinfo

处理器的相关信息的文件;

proc# cat cpuinfo 
Processor       : ARMv7 Processor rev 1 (v7l)
processor       : 0
BogoMIPS        : 1292.69

processor       : 1
BogoMIPS        : 1292.69

Features        : swp half thumb fastmult vfp edsp neon vfpv3 tls 
CPU implementer : 0x41
CPU architecture: 7
CPU variant     : 0x2
CPU part        : 0xc09
CPU revision    : 1

4.cat /proc/crypto

系统上已安装的内核使用的密码算法及每个算法的详细信息列表;

/proc# cat crypto 
name         : hmac(sha256)
driver       : hmac(sha256-generic)
module       : kernel
priority     : 0
refcnt       : 1
selftest     : passed
type         : shash
blocksize    : 64
digestsize   : 32

name         : md5
driver       : md5-generic
module       : kernel
priority     : 0
refcnt       : 1
selftest     : passed
type         : shash
blocksize    : 64
digestsize   : 16

5.cat /proc/devices

系统已经加载的所有块设备和字符设备的信息,包含主设备号和设备组名;

/proc# cat devices 
Character devices:
  1 mem
  4 ttyS
  5 /dev/tty
  5 /dev/console
  5 /dev/ptmx
 10 misc
 13 input
 89 i2c
 90 mtd
180 usb
188 ttyUSB

Block devices:
259 blkext
  8 sd
  9 md
 31 mtdblock
 65 sd
 66 sd

6.cat /proc/filesystems

当前被内核支持的文件系统类型列表文件,被标示为nodev的文件系统表示不需要块设备的支持;通常mount一个设备时,如果没有指定文件系统类型将通过此文件来决定其所需文件系统的类型;

/proc# cat filesystems 
nodev   sysfs
nodev   rootfs
nodev   bdev
nodev   proc
nodev   cgroup
nodev   cpuset
nodev   tmpfs
nodev   debugfs
nodev   sockfs
nodev   usbfs
nodev   pipefs
nodev   anon_inodefs
nodev   rpc_pipefs
nodev   devpts
nodev   ramfs
nodev   nfs
nodev   jffs2
        yaffs
        yaffs2
nodev   mtd_inodefs
nodev   ubifs
        vfat
        fuseblk
nodev   fuse
nodev   fusectl

7.cat /proc/interrupts

架构系统上每个IRQ相关的中断号列表,多路处理器平台上每个CPU对于每个I/O设备均有自己的中断号;

/proc# cat interrupts 
           CPU0       CPU1       
 29:     351743    3480462       GIC  twd
 36:       2005          0       GIC  pfe_hif
 37:          0          0       GIC  pfe_hif_nocpy
 45:          0          0       GIC  spacc
 53:          1          0       GIC  dwc_otg, dwc_otg:usb3
 54:          0          0       GIC  xhci-hcd:usb1
 59:       1621          0       GIC  serial
 60:        192          0       GIC  comcerto_spi
 61:          0          0       GIC  comcerto_spi
 62:         12          0       GIC  I2C
 90:          0          0       GIC  pmutimer
 91:         15          0       GIC  timer4
 92:          0          0       GIC  timer5
 94:          0          0       GIC  rtc-alarm
IPI0:          0          0  Timer broadcast interrupts
IPI1:      17381       7533  Rescheduling interrupts
IPI2:          0          0  Function call interrupts
IPI3:         92         63  Single function call interrupts
IPI4:          0          0  CPU stop interrupts
Err:          0

8.cat /proc/iomem

每个物理设备在系统内存中的映射信息;

/proc# cat iomem 
00000000-02bfffff : System RAM
03400000-03ffffff : ddr
04000000-3fffffff : System RAM
04008000-0455dfff : Kernel text
0458a000-0461ea07 : Kernel data
83000000-83001fff : iram
904500d0-904500d8 : comcerto_wdt
90498000-90498fff : comcerto_spi.0
9049c000-9049cfff : I2C
904e0000-904e001f : c2k-rtc
90500000-9050ffff : apb
905e0000-905e0403 : c2k mdma base address
92000000-92ffffff : dwc_otg.0
96000000-960002c0 : dw_dmac.0
96400000-9640001f : serial
96500000-96500fff : comcerto_spi.1
9a000000-9affffff : ipsec
9b000000-9bffffff : elp
9c000000-9cffffff : axi
9d000000-9d00ffff : ahci
9f000000-9f7fffff : xhci-hcd
c0000000-c3ffffff : comcertoflash.0
c8300000-c8301fff : comcertonand

9.cat /proc/meminfo

系统中关于当前内存的利用状况等的信息,其内容显示为两列,前者为统计属性,后者为对应的值;

/proc# cat meminfo 
MemTotal:        1012928 kB
MemFree:          901932 kB
Buffers:               0 kB
Cached:            32764 kB
SwapCached:            0 kB
Active:            56176 kB
Inactive:          21676 kB
Active(anon):      46048 kB
Inactive(anon):     4644 kB
Active(file):      10128 kB
Inactive(file):    17032 kB
Unevictable:           0 kB
Mlocked:               0 kB
HighTotal:             0 kB
AnonPages:         45120 kB
Mapped:             9912 kB
Shmem:              5604 kB
Slab:              17872 kB
SReclaimable:       2860 kB
SUnreclaim:        15012 kB
KernelStack:        1072 kB
PageTables:         1276 kB

10.cat /proc/mounts

系统当前挂载的所有文件系统,此文件指向/proc/self/mounts。

如下所示,其中第一列表示挂载的设备,第二列表示在当前目录树中的挂载点,第三点表示当前文件系统的类型,第四列表示挂载属性(ro或者rw),第五列和第六列用来匹配/etc/mtab文件中的转储(dump)属性;

/proc# cat mounts
rootfs / rootfs rw 0 0
ubi0:rootfs / ubifs rw,noatime 0 0
proc /proc proc rw,noatime 0 0
sysfs /sys sysfs rw,noatime 0 0
tmpfs /tmp tmpfs rw,nosuid,nodev,noatime 0 0
tmpfs /dev tmpfs rw,noatime,size=512k,mode=755 0 0
devpts /dev/pts devpts rw,noatime,mode=600 0 0
debugfs /sys/kernel/debug debugfs rw,relatime 0 0
none /proc/bus/usb usbfs rw,relatime 0 0

11.cat /proc/modules

当前装入内核的所有模块名称列表,其实与lsmod命令得到的结果一样,只不过lsmod排版的更好看,/proc/modules的信息更全面。

如下所示,其中第一列表示模块名,第二列表示此模块占用内存空间大小,第三列表示此模块有多少实例被装入,第四列表示此模块依赖于其它哪些模块,第五列表示此模块的装载状态(Live:已经装入;Loading:正在装入;Unloading:正在卸载),第六列表示此模块在内核内存(kernel memory)中的偏移量;

/proc# cat modules 
cls_fw 3409 0 - Live 0x8352d000
sbr_cdev 1970 0 - Live 0x83529000 (O)
nbvpn 4111 1 sbr_cdev, Live 0x83524000 (O)
nf_nat_ftp 1346 0 - Live 0x834ee000
nf_conntrack_ftp 4626 1 nf_nat_ftp, Live 0x834e4000
fci 3474 5 - Live 0x834c2000 (O)

/proc# lsmod 
Module                  Size  Used by    Tainted: P  
cls_fw                  3409  0 
sbr_cdev                1970  0 
nbvpn                   4111  1 sbr_cdev
nf_nat_ftp              1346  0 
nf_conntrack_ftp        4626  1 nf_nat_ftp
fci                     3474  5 

12.cat /proc/partitions

块设备每个分区的主设备号(major)和次设备号(minor)等信息,同时包括每个分区所包含的块
(block)数目,可以与/proc/mtd的内容一起查看;

/proc# cat partitions 
major minor  #blocks  name

  31        0       1024 mtdblock0
  31        1       1024 mtdblock1
  31        2       1024 mtdblock2
  31        3       8192 mtdblock3
  31        4      94208 mtdblock4
  31        5       1024 mtdblock5
  31        6       8192 mtdblock6
  31        7      94208 mtdblock7
  31        8       1024 mtdblock8
  31        9       2048 mtdblock9
  31       10      12288 mtdblock10
  31       11      32768 mtdblock11
  31       12       2048 mtdblock12
  31       13        128 mtdblock13
  31       14        512 mtdblock14
  31       15        128 mtdblock15
  31       16        128 mtdblock16
  31       17         64 mtdblock17
  31       18         64 mtdblock18
  31       19      84320 mtdblock19
/proc# cat mtd 
dev:    size   erasesize  name
mtd0: 00100000 00020000 "barebox"
mtd1: 00100000 00020000 "bareboxfact"
mtd2: 00100000 00020000 "env"
mtd3: 00800000 00020000 "kernel1"
mtd4: 05c00000 00020000 "rootfs1"
mtd5: 00100000 00020000 "reserved_dtb1"
mtd6: 00800000 00020000 "kernel2"
mtd7: 05c00000 00020000 "rootfs2"
mtd8: 00100000 00020000 "reserved_dtb2"
mtd9: 00200000 00020000 "configcert"
mtd10: 00c00000 00020000 "reserved_avcsign"
mtd11: 02000000 00020000 "webrootdb"
mtd12: 00200000 00020000 "license"
mtd13: 00020000 00010000 "uloader"
mtd14: 00080000 00010000 "barebox"
mtd15: 00020000 00010000 "env"
mtd16: 00020000 00010000 "boardinfo"
mtd17: 00010000 00010000 "md5sum1"
mtd18: 00010000 00010000 "md5sum2"
mtd19: 05258000 0001f000 "rootfs"

以上内容只是对proc目录进行简单的分析,更具体的可以查看proc文件系统详解

2.proc接口的实现


在proc文件系统中,我们可以将对虚拟文件的读写作为与内核中实体进行通信的一种手段,进行传输操作内核数据,但是与普通文件不同的是,这些虚拟文件的内容都是动态创建的。

proc的定义在include/linux/proc_fs.h下,接口函数的实现在/fs/proc/generic.c/fs/proc/文件夹下寻找,第一节的proc目录分析,很多都是在/fs/proc/文件夹在实现的,从文件名称就可以看出,如下:。

/fs/proc$ ls
array.c    generic.c     kcore.c    mmu.c           proc_sysctl.c  task_mmu.c
base.c     inode.c       kmsg.c     nommu.c         proc_tty.c     task_nommu.c
cmdline.c  internal.h    loadavg.c  page.c          root.c         uptime.c
cpuinfo.c  interrupts.c  Makefile   proc_devtree.c  softirqs.c     version.c
devices.c  Kconfig       meminfo.c  proc_net.c      stat.c         vmcore.c

proc接口的创建有两种方式,create_proc_entryproc_create,下面分别举例说明:

1.create_proc_entry方式

直接查看源码,应该比较容易理解

//包含proc头文件
#include <linux/proc_fs.h>

//定义proc接口
static struct proc_dir_entry *proc_dir = NULL;
static struct proc_dir_entry *proc_test1 = NULL;
static struct proc_dir_entry *proc_test2 = NULL;

//proc read的实现函数
static int proc_test1_read(char *page, char **start, off_t off, int count, int *eof, void *data)
{
    int len = 0;

    len = sprintf(page, "proc_test1 read ok!\n");
    return len;
}

//proc write的实现函数
static int proc_test1_write(struct file *file, const char __user * buf, unsigned long count, void *data)
{
    int val;

    if(sscanf(buf, "%d", &val) != 1)
        return -EINVAL;

    if(val == 1)
        printk("proc_test1 write true\n");
    else 
        printk("proc_test1 write false\n");

    return val;
}

//proc接口创建
static int proc_test_fs_create(void)
{
    proc_dir = proc_mkdir("proc_test", NULL);
    if(!proc_dir)
        return -ENOMEM;

    proc_test1 = create_proc_entry("proc_test1", 0644, proc_dir);
    if(!proc_test1)
        return -ENOMEM;
    proc_test1->read_proc = proc_test1_read;
    proc_test1->write_proc = proc_test1_write;
}
  • 要使用proc虚拟文件需要包含头文件

2.proc_create方式

我们首先查看这两个创建函数的结构:

  • create_proc_entry(const char *name, mode_t mode, struct proc_dir_entry *parent)
  • proc_create(const char *name,mode_t mode, struct proc_dir_entry *parent, const struct file_operations *proc_fops)

可以观察到proc_create函数增加一个struct file_operations结构体,那在实现的时候有什么区别呢?

proc_create方式的实现其实在/fs/proc/文件夹下有很多例子,如cmdline.c/version.c等,对应申明等动作上面已经给出,这边就不再重复了,直接写一个proc_create方式的例子吧。

static int proc_test2_show(struct seq_file *seq, void *v)
{
    seq_printf(seq, "proc_test2 read ok!\n");
    return 0;
}

static int proc_test2_open(struct inode *inode, struct file *file)
{
    return single_open(file, proc_test2_oshow, NULL);
}

static int proc_test2_write (struct file *file, const char *buf,
                                unsigned long count, void *data)
{
    int val;

    if(sscanf(buf, "%d", &val) != 1)
        return -EINVAL;

    if(val == 1)
        printk("proc_test2 write true\n");
    else 
        printk("proc_test2 write false\n");

    return val;
}

static  struct file_operations proc_test2_fops = {
    .open       = proc_test2_open,
    .write      = proc_test2_write,
    .read       = seq_read,
    .llseek     = seq_lseek,
    .release    = single_release,
};

static int proc_test_fs_create(void)
{
    proc_test2 = proc_create ("proc_test2", 0644, proc_dir, &proc_test2_fops);

    if (!proc_test2)
        return -ENOENT;
}

注意到proc_create函数增加一个struct file_operations结构
,而不是像create_proc_entry那样直接返回。其实原理也一样,一个是将成员函数write、read等指向proc接口proc_dir_entry,而这边是将成员函数wriet、read等指向结构体file_operations

使用proc_create方式则不使用read成员,因为当cat该proc成员时,seq_read()函数被反复调用,导致一直打印read函数的内容,不过也可以通过判断off_t *off变量进行处理,这边不过多描述。

既然不使用read的方式,那就有新的方式代替,这边使用成员open,通过上面的例子可以很直观的看到,open函数会调用show函数,所以show函数的内容就是cat时得到的内容,write成员则与create_proc_entry的实现方式一致。

Linux proc system的分析就到这边,有感悟时会持续会更新。

注:以上内容都是本人在学习过程积累的一些心得,难免会有参考到其他文章的一些知识,如有侵权,请及时通知我,我将及时删除或标注内容出处,如有错误之处也请指出,进行探讨学习。文章只是起一个引导作用,详细的数据解析内容还请查看Linux相关教程,感谢您的查阅。

2017-03-31 13:31:32 Creator_Ly 阅读数 741
  • C语言入门到阶-- 跨平台的 Hello, world 与 ...

    尹成老师、带你步入 C 语言的殿堂,讲课生动风趣、深入浅出,全套视频内容充实,整个教程以 C 语言为核心,完整精彩的演练了数据结构、算法、设计模式、数据库、大数据高并发检索、文件重定向、多线程同步、进程通讯、黑客劫持技术、网络安全、加密解密,以及各种精彩的小项目等,非常适合大家学习!帮助大家顺利入门C语言

    22627 人正在学习 去看看 尹成

Linux spi system

SPI是由Motorola提出的一种全双工同步串行通信接口,通信波特率可以高达5Mbps,但具体速度大小取决于SPI硬件,SPI接口具有全双工操作,操作简单,数据传输速率较高的优点,但也存在没有指定的流控制,没有应答机制确认是否接收到数据的缺点。

1.Linux下SPI的驱动架构


如下:

SPI框架

从图中可以观察到SPI系统的整个框架,发现跟I2C的框架很十分相似;

硬件和用户空间就不多说了,直接看内核空间部分。

内核空间主要分为三个模块,控制器驱动,设备器驱动,和连接两个驱动的SPI核心;

spi与i2c内核空间的对比

  • master就相当于i2c中的apapter
  • spi_device即i2c中的i2c_client
  • spi core 即i2c core

不同于i2c的目录框架比较忙明确,i2c的控制器驱动在/drivers/i2c/busses/下面,设备器驱动在/drivers/i2c/chips/下面。而spi的控制器就直接放在spi/目录下,设备器驱动根据功能分开在各个地方,如spi flash的一般就在/drivers/mtd/device/下,siwtch的spi接口就放在/drivers/net/ethernet/下。

2.SPI master驱动


SPI控制器的驱动分为两部分,device和driver,使用platform总线进行连接。

platform_device的注册一般在arch下面实现,使用platform_add_devices(arch_devices, ARRAY_SIZE(arch_devices));进行添加,由于内核中有很多使用platform注册的总线,所以一般会统一使用一次platform_add_devices函数进行添加,如下:

static struct platform_device *arch_devices[] __initdata = {
    &arch_nor,
    &arch_i2c,
    &arch_wdt,
    &arch_spi,
    &arch_tdm_device,
    &arch_pfe_device,
}

一个arch_devices里面包含很多platform_device,每个device各自实现后,再一块儿添加。

static struct platform_device comcerto_spi = {
    .name = "comcerto_spi",
    .id = 0,
    .num_resources = ARRAY_SIZE(comcerto_spi_resource),
    .resource = comcerto_spi_resource,
    .dev = {
        .platform_data = &ls_spi_pdata,
    },
};

platform_driver的实现在/driver/spi/中实现,使用platform_driver_registerplatform_driver_unregister进行加载下载。

static struct platform_driver designware_spi_driver = {
    .probe  = designware_spi_probe,
    .remove = __devexit_p(designware_spi_remove),
#if CONFIG_PM
        .suspend        = designware_spi_suspend,
        .resume         = designware_spi_resume,
#endif
    .driver = {
        .name = DESIGNWARE_SPI_NAME,
        .owner = THIS_MODULE,
    },
};

static int __init designware_spi_init(void)
{
    return platform_driver_register(&designware_spi_driver);
}

static void __exit designware_spi_exit(void)
{
    platform_driver_unregister(&designware_spi_driver);
}

module_init(designware_spi_init);
module_exit(designware_spi_exit);

designware_spi_probe函数里面就是master的实现部分,里面会对spi进行很多处理,如中断/dma/等,最后会使用spi_register_master函数进行注册spi_master,该函数就是spi的核心层了,位于/driver/spi/spi.c中,这一部分的内容还不知道如何描述,后面有思路时在更新。

3.SPI device驱动


SPI设备的驱动也是分为两部分,device和driver,使用spi总线进行连接。

spi_device的注册一般在arch下面实现,使用spi_register_board_info(arch_spi_board_info, ARRAY_SIZE(arch_spi_board_info))函数进行添加,函数里面的arch_spi_board_info
spi_board_info结构体,需要我们对里面的参数进行配置,如下例子:

static struct spi_board_info comcerto_spi_board_info[] = {
    {
        /* FIXME: for chipselect-0 */
        .modalias = "s25fl256s0",
        .chip_select = 0,
        .max_speed_hz = 4*1000*1000,
        .bus_num = 1,
        .irq = -1,
        .mode = SPI_MODE_3,
        .platform_data = &spi_pdata,
        .controller_data = &spi_ctrl_data,
    },
}

spi_driver的实现也比较简单,一般就是通过spi_register_driver来进行添加,spi_unregister_driver进行卸载。

如下例子:

static struct spi_driver m25p80_driver = {
    .driver = {
        .name   = "m25p80",
        .bus    = &spi_bus_type,
        .owner  = THIS_MODULE,
    },
    .id_table   = m25p_ids,
    .probe  = m25p_probe,
    .remove = __devexit_p(m25p_remove),
};

static int __init m25p80_init(void)
{
    return spi_register_driver(&m25p80_driver);
}

static void __exit m25p80_exit(void)
{
    spi_unregister_driver(&m25p80_driver);
}

module_init(m25p80_init);
module_exit(m25p80_exit);

spi_driver.id_table信息与spi_board_info所指向的设备(或者设备树中的节点)匹配成功,则执行spi_driver.probe(),对应probe函数里面实现的内容就要根据spi所使用的场景进行对于的实现了。

如spi flash一般是作为存储介质位于mtd下面的,所以probe函数里面就要使用mtd_device_parse_register来实现mtd的添加;switch的spi接口是用来跟switch进行数据通信,probe函数里面就可以使用register_chrdev_region来添加个字符设备进行操作。

调试驱动的时候最常用的方法就是使用printk来进行交互,进行定位、验证,但是要在哪边进行printk呢,个人觉得调试spi驱动有一个地方一定要进行printk,那就是位于spi.c下的spi_match_id()函数,如下:

static const struct spi_device_id *spi_match_id(const struct spi_device_id *id,
                                                const struct spi_device *sdev)
{
        while (id->name[0]) {
                if (!strcmp(sdev->modalias, id->name))
                        return id;
                id++;
        }
        return NULL;
}

总结:分析调试I2C/SPI的驱动后,有以下几点感悟:

  • 不管什么驱动,大致都可以分为两个动作响应,或者说两个probe接口,一个是控制器的probe(根据cpu的不同进行改变),一个是设备的probe(根据设备的不同就行改变)。
  • 每一个probe接口有与之对于的device和driver,并且需要一种总线相连。
  • 控制器的probe一般使用platform总线,对应platform_deviceplatform_driver;设备的probe使用相关的总线,如i2c总线,则对应i2c_devicei2c_driver,i2c总线,则对应spi_devicespi_driver
  • probe函数的触发都是对应的match_id函数,如platform_match_idi2c_match_idspi_match_id
  • platform的device的注册在arch下面,使用platform_add_devices添加,platform对应的driver,位于各目录下,如i2c位于i2c/busses/里面,使用i2c_register_adapter来添加。
  • 驱动如i2c的device的注册位于arch下,使用i2c_register_board_info或dts添加,对应i2c的driver位于i2c/chips/下面,使用i2c_add_driver来添加。

Linux spi system的分析就到这边,有感悟时会持续会更新。

注:以上内容都是本人在学习过程积累的一些心得,难免会有参考到其他文章的一些知识,如有侵权,请及时通知我,我将及时删除或标注内容出处,如有错误之处也请指出,进行探讨学习。文章只是起一个引导作用,详细的数据解析内容还请查看Linux相关教程,感谢您的查阅。

Linux print system

阅读数 1032

Linux print system

博文 来自: Creator_Ly

system函数-linux

阅读数 771

linux system init

阅读数 1932

没有更多推荐了,返回首页