luo-qi

导航

 

前言

最近研究在linux 内核下,主机通过SPI与另外一个板卡通信,该板卡将收到的信息加密后又通过SPI给到主机。其实就是将另外一个板卡看作一个外置的SPI设备,更贴切来说就甚至可以看作一个spi flash, in out 模型,给数据,吐数据而已。关于SPI的基本的硬件知识,我想这是最基本的,这里就不过多赘述了。这里想提的是,其实对大多数外设而言,我们cpu对它们的访问本质上都是对相应的控制器的寄存器的配置与访问,再直接一点来说,实际是对地址空间的访问,无论是否该空间与真正的内存统一编址,还是说访问这些控制器指令与真实访问内存的指令是否加以区别,但是不可否认的是,我们所谓的寄存器配置实际就是往对应的地址写入对应的值。那所谓的手册无非就是告诉我们,应该往哪里写,些什么而已。

SPI驱动基本模型

依照前言,那我们在内核中,做SPI的工作就肯定涉及两个部分SPI控制器和SPI设备,并且SPI设备的操作,一定是依赖SPI控制器的。对cpu而言,无论是SPI控制器,还是具体的SPI设备,其实都应该是看作是设备,只是一个是直接访问控制,一个是间接的操作。所以两个设备都应该有相应的驱动,对于SPI控制器,采用的是platform_device_Bus,而SPI设备就是SPI_Bus,这里简单说一下总线的作用就是设备和驱动都挂在上面(注册),核心作用是完成设备和驱动的匹配。SPI控制器驱动主要是完成具体的数据传输过程(所以才会有重要的成员transfer),重点在于怎么传。而SPI设备驱动主要是完成提供给应用层接口,同时也是不同SPI设备的差异化体现,重点在于传什么。在同一平台下,不同的SPI设备的SPI控制器驱动可以相同,这里很好的体现linux内核的分层思想。而我们还没提及的,也就是SPI核心层,其实就是完成了依赖关系,即如何从SPI设备能够最终关联到相应的控制器。以上其实就从大体上讲了一下,自己对整个SPI子系统的认识和理解。

 

 

SPI控制器驱动

 

 

 这里我用的自己实际使用的,也就是龙芯相应的驱动来做分析,其实每个平台都是一样的,这个文件我会详细分析,分析完后你再去分析你自己想分析的文件,如果能够自己分析下来说明就是真的理解了。这里用subsys_initcall,其实是为了保证这个初始化能够更早进行,因为这是SPI控制器驱动,即总线驱动的初始化,感兴趣可以搜索一下这个函数,不同函数,系统在内核中的加载顺序是有讲究的,不同的xxx_initcall,会被放在不同的.init段中不同的位置,内核启动这部分也应该去看看,大概了解了解。

 

ls_spi_init就是干了一件事,注册驱动到platform_bus。

 

 

 

 LS_DEV_PM_OPS就是电源管理相关的ops实现

 

 

  

of _match_table就是提供了支持设备树匹配的相应表项,而我们实际上也是使用这种方式,of_match_ptr就是一个内核对设备树支持与否的条件检查,当支持时,这个table就是图中ls_spi_id_table,不支持时就会把它变成NULL(就在上图的两个位置)。其实设备树的本质就是将设备树信息转化为platform_device,然后最终device跟driver就够去做匹配。在很早的版本内核还不支持设备树的时候,device也是我们在C文件中自己去创建的。言归正传,我们继续分析,现在device与driver都有了。进probe看看。

 

 

这个函数其实也特别有意思,因为从它的传参可以看到它其实不是分配的master结构体大小,而且还传入了一个指针是我们platform_device的成员dev。按照正常思路,如果给一个结构体分配空间,唯一需要给出的不就是它自己的大小吗,说明这里面一定有什么特别之处,直接跳到最后的函数调用来分析(摘选了几个重要的)

 

ctlr = kzalloc(size + sizeof(*ctlr), GFP_KERNEL);

ctlr->dev.parent = dev;

spi_controller_set_devdata(ctlr, &ctlr[1]);

 

1:首先就是分配的大小其实是master+ls_spi的大小,而且这段空间是连续的

2:完成了pdev-> dev 和 master的绑定,即可以通过master访问到pdev->dev

3:&ctl[1]其实就是1中分配的ls_spi的起始地址。

最后3的最后函数实现为:dev->driver_data = data;翻译一下:ctlr->dev->driver_data = &ls_spi,就说明其实就是把ls_2k绑定到了master的dev_>dirver_data下。所以有必要看一下,ls_spi我们这个自己实现的数据结构里究竟有什么。

  

 进去probe之后主要做的事情是分配并且实例化一个master,把master放在了pdev->dev->drv_data里,以及实例化了ls_2k这个数据结构。

 

这里只选取了部分的代码,可以看出都是在实例化master。(实例化这种说法在C++中有说到,简单来说就是就是填充master的各个成员,有点类似于初始化但是我发现内核虽然是C写的,但是大量使用了C++的思想,特别是C++的几大特点都有体现和巧妙的使用)

由上面我们可以知道它不仅给master分配了空间,同时还给Ls_spi分配了空间。所以这里也有ls_spi的实例化。包括

 

 

 

  最后把该master注册,而由master也能访问到我们的ls_spi,所以理论上也带上了ls_spi数据结构。很多东西也就传递过去了。(这种分配空间->实例化->注册是内核很常见的操作方式)

 

SPI设备驱动

这里我们分析spidev.c这个文件,这个文件是内核提供的一个供参考的或者说通用的设备驱动。

status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);

spidev_class = class_create(THIS_MODULE, "spidev");

 

status = spi_register_driver(&spidev_spi_driver);

这里分析驱动,虽然重要的是ops结构体和probe,但是我一般还是先从module init开始,上面是module_init的主要内容,注册了一个字符设备,绑定了主设备号与file_ops结构体。

 

 创建了一个class

向spi总线注册了一个driver

接下来就去分析driver的Probe,probe这个函数,简单来说是当我们的设备和驱动在总线上匹配时,会进入的一个回调函数,也是驱动的重点,里面一般会有具体设备结点的创建,因为只有当驱动与设备匹配上时,产生的设备节点才是有意义的,才是可用的,才是可供应用层操作的。而且这里往往会做一些后面会用到数据的初始化。也就是为后面file_ops里的各个调用做好初始化工作或者说准备工作。所以我们可以来分析一下这里做了什么。probe传入参数是一个spi_device  的指针,这应该是设备树信息转换得到的,在probe 中总的来说就是用这个指针和其他信息来填充了spidev_data数据结构,并且把该结构放入了spi_device的driver_data中去。

 

 

 

因为我们可以看见随后真正在device_create时用到的是spidev_data这个数据结构,它是对spi本身信息以及像次设备号,锁,等其他资源的一个集合。又把这个指针给到传入的spi_device本身。从整体来分析,即是说在设备树中指定资源,若内核认领资源则发生probe,再构成具体可操作的节点(即把提供使用这些资源的方式接口)。

 

 

SPI核心层

SPI核心层,其实在很多框架的核心层都是呈上启下的作用。我们从驱动来分析,这种处于两个驱动之间的核心层,当我们与SPI设备交互时(人与人交流时),设备驱动更在意数据本身的意义(对话的内容),总线驱动更在意数据是如何传输的,(你每一个字每一个音是怎么发出的),最终我们说的每一个字都是需要落实到舌头和喉咙的具体动作的。所以中间抽象出核心层,能够很好地形成通用框架,以供复用,隔离分层。芯片原厂(cpu的)去做底层驱动的工作,而设备厂商去做设备的驱动工作,他们互不影响,却又因为核心层彼此约束,息息相关。

总结:

posted on 2022-11-03 16:16  L棋  阅读(727)  评论(0编辑  收藏  举报