http://www.arm9home.net/read.php?tid-10788.html
下图是安装成功后,sys目录下的主要结构,由于目录非常复杂仅仅列出了主要的结构
sys目录下spi子系统结构
接下来将从各struct开始进行分析,struct是构成内核对象的基础,函数是动态的构建和执行的工具。所以梳理脉络就从结构开始。
linux下的设备模型包括几个主要的概念
sysfs (dev是用户空间接口,根据sysfs下的class目录由mdev负责建立)
bus总线,linux下的设备都是建立在总线上的,platform总线是一个虚拟的总线,所有的的片上设备基本上都接在这个虚拟总线上
device是设备
device_driver是设备驱动
class是类别,从功能角度对设备进行分类
注意,在sys/bus目录下有platform目录和spi目录
这两个目录下面的设备分别代表什么呢?
platform下的设备有s3c64xx-spi0和s3c64xx-spi1分别对应了s3c6410上的spi0和spi1接口
一、先说说platform
platform.txt是需要阅读的参考文档
platform子系统是linux对不同的架构下设备的抽象归纳,即所有片上的设备都会放在这个子目录下
我们先看一下platform相关的struct
struct platform_device {
const char * name;
int id;
struct device dev;
u32 num_resources;
struct resource * resource;
const struct platform_device_id *id_entry;
/* arch specific additions */
struct pdev_archdata archdata;
};
platform_device从字面理解就是平台设备,注意它内含了一个名为dev的device结构,这有点像C++的类继承的关系,linux内核大量利用了这种类似继承的结构实现了C语言下的面向对象编程。
后面谈到的SPI的设备继承了platform_device的结构。 另外,还包含了一个resource 结构的指针,注意和dev的区别。也就是说资源是需要另外定义然后,将对象指针赋予这里的结构指针。资源包含IOMEM和IRQ。
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;
};
platform_driver结构就不多说了,主要是用来说明platform驱动的结构。
下面看看对象实例s3c64xx-spi.0的定义
static struct resource s3c64xx_spi0_resource[] = {
[0] = {
.start = S3C64XX_PA_SPI0,
.end = S3C64XX_PA_SPI0 + 0x100 - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = DMACH_SPI0_TX,
.end = DMACH_SPI0_TX,
.flags = IORESOURCE_DMA,
},
[2] = {
.start = DMACH_SPI0_RX,
.end = DMACH_SPI0_RX,
.flags = IORESOURCE_DMA,
},
[3] = {
.start = IRQ_SPI0,
.end = IRQ_SPI0,
.flags = IORESOURCE_IRQ,
},
};
static struct s3c64xx_spi_info s3c64xx_spi0_pdata = {
.cfg_gpio = s3c64xx_spi_cfg_gpio,
.fifo_lvl_mask = 0x7f,
.rx_lvl_offset = 13,
};
static u64 spi_dmamask = DMA_BIT_MASK(32);
struct platform_device s3c64xx_device_spi0 = {
.name = "s3c64xx-spi",
.id = 0,
.num_resources = ARRAY_SIZE(s3c64xx_spi0_resource),
.resource = s3c64xx_spi0_resource,
.dev = {
.dma_mask = &spi_dmamask,
.coherent_dma_mask = DMA_BIT_MASK(32),
.platform_data = &s3c64xx_spi0_pdata,
},
};
从上面的定义可以看出,s3c64xx_device_spi0是s3c64xx-spi.0的定义,系统根据这个变量实现了s3c64xx-spi.0,而它的类型是platform_device,也就是说这是一个平台设备。s3c64xx-spi.1就不说了同理。
下面是驱动s3c64xx_spi_driver 的定义,系统根据这个定义实现了平台驱动s3c64xx-spi
static struct platform_driver s3c64xx_spi_driver = {
.driver = {
.name = "s3c64xx-spi",
.owner = THIS_MODULE,
},
.remove = s3c64xx_spi_remove,
.suspend = s3c64xx_spi_suspend,
.resume = s3c64xx_spi_resume,
};
注意驱动的定义中含有很多的函数指针,这些函数是用来控制设备工作的标准动作。具体功能这里就不解释了,各位看源码。
各位看到这里可能要问了那s3c64xx-spi.0下的spi0.0是在哪里实现的?它的类结构是什么?
首先,我们得明白s3c64xx-spi.0代表了s3c6410下的spi0接口,那么spi0.0代表什么呢?注意spi0.0有两个数字,第一个0代表spi0接口,第2个0呢?它代表这spi0总线上的片外设备,这意味着一个spi0总线可以接多个片外设备,通过cs进行片选。属于分时复用,那么spi0.0的定义在什么地方呢?首先,它和开发板的定义高度相关,也就是说它不会存在于内核代码,也不会存在于芯片厂商三星公司提供的s3c64xx系列代码中,那么它应该在什么地方呢?答案是它应该在开发板的初始化代码中,即在友善之臂的mach_mini6410.c中,很遗憾友善之臂暂时没有支持spi,spi的相关初始化代码需要我们自行定义,如何定义这里暂且不说,先说说这个设备的类型定义在哪里?
下面就是片外设备的定义,s3c64xx_spi_csinfo是片外设备需要的片选信号的定义,这个片选信号的定义是必须的,很多网上的帖子并没有介绍到,这也是我之前很郁闷的地方一直无法成功的加载设备。
/**
* struct s3c64xx_spi_csinfo - ChipSelect description
* @fb_delay: Slave specific feedback delay.
* Refer to FB_CLK_SEL register definition in SPI chapter.
* @line: Custom 'identity' of the CS line.
* @set_level: CS line control.
*
* This is per SPI-Slave Chipselect information.
* Allocate and initialize one in machine init code and make the
* spi_board_info.controller_data point to it.
*/
struct s3c64xx_spi_csinfo {
u8 fb_delay;
unsigned line;
void (*set_level)(unsigned line_id, int lvl);
};
/*
* INTERFACE between board init code and SPI infrastructure.
*
* No SPI driver ever sees these SPI device table segments, but
* it's how the SPI core (or adapters that get hotplugged) grows
* the driver model tree.
*
* As a rule, SPI devices can't be probed. Instead, board init code
* provides a table listing the devices which are present, with enough
* information to bind and set up the device's driver. There's basic
* support for nonstatic configurations too; enough to handle adding
* parport adapters, or microcontrollers acting as USB-to-SPI bridges.
*/
/**
* struct spi_board_info - board-specific template for a SPI device
* @modalias: Initializes spi_device.modalias; identifies the driver.
* @platform_data: Initializes spi_device.platform_data; the particular
* data stored there is driver-specific.
* @controller_data: Initializes spi_device.controller_data; some
* controllers need hints about hardware setup, e.g. for DMA.
* @irq: Initializes spi_device.irq; depends on how the board is wired.
* @max_speed_hz: Initializes spi_device.max_speed_hz; based on limits
* from the chip datasheet and board-specific signal quality issues.
* @bus_num: Identifies which spi_master parents the spi_device; unused
* by spi_new_device(), and otherwise depends on board wiring.
* @chip_select: Initializes spi_device.chip_select; depends on how
* the board is wired.
* @mode: Initializes spi_device.mode; based on the chip datasheet, board
* wiring (some devices support both 3WIRE and standard modes), and
* possibly presence of an inverter in the chipselect path.
*
* When adding new SPI devices to the device tree, these structures serve
* as a partial device template. They hold information which can't always
* be determined by drivers. Information that probe() can establish (such
* as the default transfer wordsize) is not included here.
*
* These structures are used in two places. Their primary role is to
* be stored in tables of board-specific device descriptors, which are
* declared early in board initialization and then used (much later) to
* populate a controller's device tree after the that controller's driver
* initializes. A secondary (and atypical) role is as a parameter to
* spi_new_device() call, which happens after those controller drivers
* are active in some dynamic board configuration models.
*/
struct spi_board_info {
/* the device name and module name are coupled, like platform_bus;
* "modalias" is normally the driver name.
*
* platform_data goes to spi_device.dev.platform_data,
* controller_data goes to spi_device.controller_data,
* irq is copied too
*/
char modalias[SPI_NAME_SIZE];
const void *platform_data;
void *controller_data;
int irq;
/* slower signaling on noisy or low voltage boards */
u32 max_speed_hz;
/* bus_num is board specific and matches the bus_num of some
* spi_master that will probably be registered later.
*
* chip_select reflects how this chip is wired to that master;
* it's less than num_chipselect.
*/
u16 bus_num;
u16 chip_select;
/* mode becomes spi_device.mode, and is essential for chips
* where the default of SPI_CS_HIGH = 0 is wrong.
*/
u8 mode;
/* ... may need additional spi_device chip config data here.
* avoid stuff protocol drivers can set; but include stuff
* needed to behave without being bound to a driver:
* - quirks like clock rate mattering when not selected
*/
};
上面的介绍已经将平台下的各设备及驱动的定义讲清楚了
二、接下来介绍SPI目录下的各对象的定义。
spi-summary.txt是必须阅读的参考文档
从spi目录可以看出这是与平台无关的,也就是linux对spi接口进行抽象,形成的用户层相关代码,而platform下面的代码是具体进行工作的代码,它们与平台相关,而spi代码是与用户空间接口相关的代码它必须与平台无关,即无论平台如何更换,对用户空间的接口都是一致的,这就是linux设备驱动架构的精髓。
我们注意到spi目录下只有一个spidev的驱动,其他都是指向platform下的设备链接,这里先不说整个spi子系统的运作机制,先搞清楚spi子系统的静态结构
spidev是由spidev_spi_driver 定义的,它的结构类型是spi_driver
static struct spi_driver spidev_spi_driver = {
.driver = {
.name = "spidev",
.owner = THIS_MODULE,
},
.probe = spidev_probe,
.remove = __devexit_p(spidev_remove),
/* NOTE: suspend/resume methods are not necessary here.
* We don't do anything except pass the requests to/from
* the underlying controller. The refrigerator handles
* most issues; the controller driver handles the rest.
*/
};
spi_driver 的定义如下:
/**
* struct spi_driver - Host side "protocol" driver
* @id_table: List of SPI devices supported by this driver
* @probe: Binds this driver to the spi device. Drivers can verify
* that the device is actually present, and may need to configure
* characteristics (such as bits_per_word) which weren't needed for
* the initial configuration done during system setup.
* @remove: Unbinds this driver from the spi device
* @shutdown: Standard shutdown callback used during system state
* transitions such as powerdown/halt and kexec
* @suspend: Standard suspend callback used during system state transitions
* @resume: Standard resume callback used during system state transitions
* @driver: SPI device drivers should initialize the name and owner
* field of this structure.
*
* This represents the kind of device driver that uses SPI messages to
* interact with the hardware at the other end of a SPI link. It's called
* a "protocol" driver because it works through messages rather than talking
* directly to SPI hardware (which is what the underlying SPI controller
* driver does to pass those messages). These protocols are defined in the
* specification for the device(s) supported by the driver.
*
* As a rule, those device protocols represent the lowest level interface
* supported by a driver, and it will support upper level interfaces too.
* Examples of such upper levels include frameworks like MTD, networking,
* MMC, RTC, filesystem character device nodes, and hardware monitoring.
*/
struct spi_driver {
const struct spi_device_id *id_table;
int (*probe)(struct spi_device *spi);
int (*remove)(struct spi_device *spi);
void (*shutdown)(struct spi_device *spi);
int (*suspend)(struct spi_device *spi, pm_message_t mesg);
int (*resume)(struct spi_device *spi);
struct device_driver driver;
};
static inline struct spi_driver *to_spi_driver(struct device_driver *drv)
{
return drv ? container_of(drv, struct spi_driver, driver) : NULL;
}
extern int spi_register_driver(struct spi_driver *sdrv);
/**
* spi_unregister_driver - reverse effect of spi_register_driver
* @sdrv: the driver to unregister
* Context: can sleep
*/
static inline void spi_unregister_driver(struct spi_driver *sdrv)
{
if (sdrv)
driver_unregister(&sdrv->driver);
}
三、下面介绍class目录下的对象定义结构
spi_master的定义如下
static struct class spi_master_class = {
.name = "spi_master",
.owner = THIS_MODULE,
.dev_release = spi_master_release,
};
spidev的类定义如下:
/*-------------------------------------------------------------------------*/
/* The main reason to have this class is to make mdev/udev create the
* /dev/spidevB.C character device nodes exposing our userspace API.
* It also simplifies memory management.
*/
static struct class *spidev_class;
文章写到这里,spi子系统主要的设备、驱动、类定义的结构已经介绍清楚了,即静态的结构大家已经明白了。
下面我们开始介绍SPI子系统的初始化过程,这也是我和大伙都很头痛的地方,SPI子系统是如何初始化的?
四、SPI子系统的初始化过程
先说一下和SPI子系统初始化相关的主要代码
spi.c是spi子系统初始化的核心代码,由内核负责初始化
spidev.c是spi用户接口初始化的代码,编译的时候需要选择该模块
spi_s3c64xx.c是平台驱动的初始化代码,编译时需要选择spi s3c64xx模块
mach-mini6410.c是开发板初始化的代码
上述核心设备、驱动、类的初始化过程就是在上述代码中实现的,它们分别负责那些对象的初始化过程,次序是什么,需要各位思考一下,我也没有完全搞清楚。
先说说spi.c中的初始化代码
static int __init spi_init(void)
{
int status;
buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL);
if (!buf) {
status = -ENOMEM;
goto err0;
}
status = bus_register(&spi_bus_type);
if (status < 0)
goto err1;
status = class_register(&spi_master_class);
if (status < 0)
goto err2;
return 0;
err2:
bus_unregister(&spi_bus_type);
err1:
kfree(buf);
buf = NULL;
err0:
return status;
}
从这段代码,我们可以看出,系统注册及初始化了总线spi和类spi_master
总线spi的定义代码前面没有列出,这里补充如下:
struct bus_type spi_bus_type = {
.name = "spi",
.dev_attrs = spi_dev_attrs,
.match = spi_match_device,
.uevent = spi_uevent,
.suspend = spi_suspend,
.resume = spi_resume,
};
这个初始化过程是内核在初始化过程中,调用spi_init(void)函数执行的,由宏postcore_initcall(spi_init);加入到启动代码。
platform总线的初始化代码由platform.c中的函数platform_bus_init执行
int __init platform_bus_init(void)
{
int error;
early_platform_cleanup();
error = device_register(&platform_bus);
if (error)
return error;
error = bus_register(&platform_bus_type);
if (error)
device_unregister(&platform_bus);
return error;
}
下面是用户空间接口的初始化代码,从代码可以看出,初始化了字符设备spi
注册&初始化类spidev
注册&初始化驱动spidev
static int __init spidev_init(void)
{
int status;
/* Claim our 256 reserved device numbers. Then register a class
* that will key udev/mdev to add/remove /dev nodes. Last, register
* the driver which manages those device numbers.
*/
BUILD_BUG_ON(N_SPI_MINORS > 256);
status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);
if (status < 0)
return status;
spidev_class = class_create(THIS_MODULE, "spidev");
if (IS_ERR(spidev_class)) {
unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
return PTR_ERR(spidev_class);
}
status = spi_register_driver(&spidev_spi_driver);
if (status < 0) {
class_destroy(spidev_class);
unregister_chrdev(SPIDEV_MAJOR, spidev_spi_driver.driver.name);
}
return status;
}
module_init(spidev_init);
下面平台驱动的初始化过程
static int __init s3c64xx_spi_init(void)
{
return platform_driver_probe(&s3c64xx_spi_driver, s3c64xx_spi_probe);
}
int __init_or_module platform_driver_probe(struct platform_driver *drv,
int (*probe)(struct platform_device *))
{
int retval, code;
/* make sure driver won't have bind/unbind attributes */
drv->driver.suppress_bind_attrs = true;
/* temporary section violation during probe() */
drv->probe = probe;
retval = code = platform_driver_register(drv);
/*
* Fixup that section violation, being paranoid about code scanning
* the list of drivers in order to probe new devices. Check to see
* if the probe was successful, and make sure any forced probes of
* new devices fail.
*/
spin_lock(&platform_bus_type.p->klist_drivers.k_lock);
drv->probe = NULL;
if (code == 0 && list_empty(&drv->driver.p->klist_devices.k_list))
retval = -ENODEV;
drv->driver.probe = platform_drv_probe_fail;
spin_unlock(&platform_bus_type.p->klist_drivers.k_lock);
if (code != retval)
platform_driver_unregister(drv);
return retval;
}
上面这段代码注册了平台SPI驱动s3c64xx-spi
看到这里,我们可以发现还有什么活没有干?
s3c64xx-spi.0和s3c64xx-spi.1设备没有建立,它们下面的spi0.0和spi1.0设备也没有建立。
spidev类下面的spidev0.0和spidev1.0也没有建立
spi_master类下面的spi0和spi1也没有建立
这些都是在什么地方建立的?分析一下这些都与具体的开发板有关系,也就是说这部分代码必须由开发板的初始化代码来实现,这里必须由mach-mini6410.c负责实现。
mach-mini6410.c的初始化函数是
static void __init mini6410_machine_init(void)
也就是说上述工作必须在这个函数中实现。
这里到了本文的高潮部分了,也是很多兄弟姐妹们迫切希望直接拷贝然后加入到自己的代码中去的地方。但是请注意,我也会犯错,这里的代码我也许说的并不对,各位看官如果发现了错误请指出及批判。
我们先分析s3c64xx-spi.0和s3c64xx-spi.1是在什么地方定义的,因为它与平台架构相关,所以我们到arch/arm/mach-s3c64xx目录中找到了dev-spi.c,这个代码中定义了s3c64xx_device_spi0和s3c64xx_device_spi1也就是设备s3c64xx-spi.0和s3c64xx-spi.1的实现代码,所以dev-spi.c必须加入到编译中,需要修改相关的编译配置文件。
然后,我们在分析mini6410的初始化代码,注意代码中的这个函数
platform_add_devices(mini6410_devices, ARRAY_SIZE(mini6410_devices));
可以看出该函数是批量初始化了一批设备,所以将
&s3c64xx_device_spi0,
&s3c64xx_device_spi1,
加入到mini6410_devices结构中,就可以将设备s3c64xx-spi.0和s3c64xx-spi.1进行初始化,
但是事情没有做完,s3c64xx_device_spi0和s3c64xx_device_spi1结构中的片选数量和时钟并未定义,需要根据开发板的具体情况进行定义,
函数原型 void __init s3c64xx_spi_set_info(int cntrlr, int src_clk_nr, int num_cs)
这里具体的执行代码如下:
s3c64xx_spi_set_info(0,0,1);
s3c64xx_spi_set_info(1,0,1);
到这里各位已经可以在平台目录中看见s3c64xx_device_spi0和s3c64xx_device_spi1,在类spi_master目录下看见spi0和spi1了
下面介绍如何增加spi0.0和spi1.0设备?
各位请仔细分析在spi.h中的这段代码
/*---------------------------------------------------------------------------*/
/*
* INTERFACE between board init code and SPI infrastructure.
*
* No SPI driver ever sees these SPI device table segments, but
* it's how the SPI core (or adapters that get hotplugged) grows
* the driver model tree.
*
* As a rule, SPI devices can't be probed. Instead, board init code
* provides a table listing the devices which are present, with enough
* information to bind and set up the device's driver. There's basic
* support for nonstatic configurations too; enough to handle adding
* parport adapters, or microcontrollers acting as USB-to-SPI bridges.
*/
/**
* struct spi_board_info - board-specific template for a SPI device
* @modalias: Initializes spi_device.modalias; identifies the driver.
* @platform_data: Initializes spi_device.platform_data; the particular
* data stored there is driver-specific.
* @controller_data: Initializes spi_device.controller_data; some
* controllers need hints about hardware setup, e.g. for DMA.
* @irq: Initializes spi_device.irq; depends on how the board is wired.
* @max_speed_hz: Initializes spi_device.max_speed_hz; based on limits
* from the chip datasheet and board-specific signal quality issues.
* @bus_num: Identifies which spi_master parents the spi_device; unused
* by spi_new_device(), and otherwise depends on board wiring.
* @chip_select: Initializes spi_device.chip_select; depends on how
* the board is wired.
* @mode: Initializes spi_device.mode; based on the chip datasheet, board
* wiring (some devices support both 3WIRE and standard modes), and
* possibly presence of an inverter in the chipselect path.
*
* When adding new SPI devices to the device tree, these structures serve
* as a partial device template. They hold information which can't always
* be determined by drivers. Information that probe() can establish (such
* as the default transfer wordsize) is not included here.
*
* These structures are used in two places. Their primary role is to
* be stored in tables of board-specific device descriptors, which are
* declared early in board initialization and then used (much later) to
* populate a controller's device tree after the that controller's driver
* initializes. A secondary (and atypical) role is as a parameter to
* spi_new_device() call, which happens after those controller drivers
* are active in some dynamic board configuration models.
*/
struct spi_board_info {
/* the device name and module name are coupled, like platform_bus;
* "modalias" is normally the driver name.
*
* platform_data goes to spi_device.dev.platform_data,
* controller_data goes to spi_device.controller_data,
* irq is copied too
*/
char modalias[SPI_NAME_SIZE];
const void *platform_data;
void *controller_data;
int irq;
/* slower signaling on noisy or low voltage boards */
u32 max_speed_hz;
/* bus_num is board specific and matches the bus_num of some
* spi_master that will probably be registered later.
*
* chip_select reflects how this chip is wired to that master;
* it's less than num_chipselect.
*/
u16 bus_num;
u16 chip_select;
/* mode becomes spi_device.mode, and is essential for chips
* where the default of SPI_CS_HIGH = 0 is wrong.
*/
u8 mode;
/* ... may need additional spi_device chip config data here.
* avoid stuff protocol drivers can set; but include stuff
* needed to behave without being bound to a driver:
* - quirks like clock rate mattering when not selected
*/
};
这段代码中,spi_board_info 是spi总线上从设备的定义由于和硬件的具体种类有非常大的关系,所以需要针对具体的设备类型进行定义,这里先给一个例子
先在mach-mini6410.c中增加如下代码
//zhuyong add start
static void cs_set_level(unsigned line_id, int lvl) {
gpio_direction_output(line_id, lvl);
};
static struct s3c64xx_spi_csinfo s3c64xx_spi0_csinfo = {
.fb_delay=100,
.line=S3C64XX_GPC(3),
.set_level=cs_set_level,
};
static struct spi_board_info s3c6410_spi0_board[] = {
[0] = {
.modalias = "spidev",
.bus_num= 0,
.chip_select= 0, //必须小于s3c6410_spi0_platdata.num_cs
.irq =IRQ_SPI0,
.max_speed_hz= 500*1000,
.mode=SPI_MODE_0,
.controller_data=&s3c64xx_spi0_csinfo,
},
};
static struct s3c64xx_spi_csinfo s3c64xx_spi1_csinfo = {
.fb_delay=100,
.line=S3C64XX_GPC(7),
.set_level=cs_set_level,
};
static struct spi_board_info s3c6410_spi1_board[] = {
[0] = {
.modalias = "spidev",
.bus_num= 1,//代表使用芯片的第二个spi模块
.chip_select= 0, //必须小于s3c6410_spi1_platdata.num_cs
.irq = IRQ_SPI1,
.max_speed_hz = 500*1000,
.mode=SPI_MODE_0,
.controller_data=&s3c64xx_spi1_csinfo,
},
};
//zhuyong add end
为什么要定义s3c64xx_spi0_csinfo?这是spi从设备必须的片选信号的定义,片选信号中有一个关键的函数set_level需要各位自己进行定义,这个函数由系统在打开设备时调用,即设置该SPI从设备的片选信号,一开始由于没有设置该设备的片选信号,导致spi0.0和spi1.0一直加载不成功。
需要注意是,这里仅仅只是给出了一个例子,s3c64xx_spi0_info中还有很多具体的设置需要在该结构中进行定义。
最后在函数 mini6410_machine_init中,增加如下代码
spi_register_board_info(s3c6410_spi0_board, ARRAY_SIZE(s3c6410_spi0_board));
spi_register_board_info(s3c6410_spi1_board, ARRAY_SIZE(s3c6410_spi1_board));
这段代码必须加在
s3c64xx_spi_set_info(0,0,1);
s3c64xx_spi_set_info(1,0,1);
之后,至于为什么请各位自己思考,到这里所有的spi设备都已经初始化结束,在目录中已经可以看见本文开头的文件结构了。
下面就有两个议题了,
spi用户空间接口如何使用?
spi子系统是如何运行的?
在黄埔江畔的星巴克写完这篇文章,已经是华灯初上了,对岸的外滩建筑美丽而宁静,这篇文章也用来纪念前段时间死去的成千上万的脑细胞。希望还能挽救各位兄弟姐妹的脑细胞。
email:[email protected]
如果本文中有错误,请各位及时指出。
下面讲用户空间接口
说到用户空间接口就不得不提到cdev结构,这个是字符设备的结构struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
我们分析一下这个结构,包含了kobject 对象,file_operations 函数结构,kobject 我们都知道这是linux文件系统结构中最重要的结构,用来表示文件对象,这里就不细说了,而file_operations 都包含了什么呢?
/*
* NOTE:
* all file operations except setlease can be called without
* the big kernel lock held in all filesystems.
*/
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
};
上面是file_operations 的结构,也就是文件可以操作的API
再回过来看看spidev中的初始化函数中的cdev的注册函数
status = register_chrdev(SPIDEV_MAJOR, "spi", &spidev_fops);
这里注册了spi设备,spidev_fops的定义如下:
static const struct file_operations spidev_fops = {
.owner = THIS_MODULE,
/* REVISIT switch to aio primitives, so that userspace
* gets more complete API coverage. It'll simplify things
* too, except for the locking.
*/
.write = spidev_write,
.read = spidev_read,
.unlocked_ioctl = spidev_ioctl,
.open = spidev_open,
.release = spidev_release,
};
也就是说spi用户空间接口提供了,write 、read、unlocked_ioctl 、open 、release 等五个cdev设备的标准操作。
看到这里大家应该知道该如何操作spi设备了吧。
spidev0.0和spidev1.0都是挂接到spidev字符设备下子设备,它们共用一套设备操作函数。
这里要重点分析的是spidev_ioctl函数,它用来设置spi设备的状态等等操作。
这个函数中以下这一段代码
switch (cmd) {
/* read requests */
case SPI_IOC_RD_MODE:
retval = __put_user(spi->mode & SPI_MODE_MASK,
(__u8 __user *)arg);
break;
case SPI_IOC_RD_LSB_FIRST:
retval = __put_user((spi->mode & SPI_LSB_FIRST) ? 1 : 0,
(__u8 __user *)arg);
break;
case SPI_IOC_RD_BITS_PER_WORD:
retval = __put_user(spi->bits_per_word, (__u8 __user *)arg);
break;
case SPI_IOC_RD_MAX_SPEED_HZ:
retval = __put_user(spi->max_speed_hz, (__u32 __user *)arg);
break;
/* write requests */
case SPI_IOC_WR_MODE:
retval = __get_user(tmp, (u8 __user *)arg);
if (retval == 0) {
u8 save = spi->mode;
if (tmp & ~SPI_MODE_MASK) {
retval = -EINVAL;
break;
}
tmp |= spi->mode & ~SPI_MODE_MASK;
spi->mode = (u8)tmp;
retval = spi_setup(spi);
if (retval < 0)
spi->mode = save;
else
dev_dbg(&spi->dev, "spi mode %02x\n", tmp);
}
break;
case SPI_IOC_WR_LSB_FIRST:
retval = __get_user(tmp, (__u8 __user *)arg);
if (retval == 0) {
u8 save = spi->mode;
if (tmp)
spi->mode |= SPI_LSB_FIRST;
else
spi->mode &= ~SPI_LSB_FIRST;
retval = spi_setup(spi);
if (retval < 0)
spi->mode = save;
else
dev_dbg(&spi->dev, "%csb first\n",
tmp ? 'l' : 'm');
}
break;
case SPI_IOC_WR_BITS_PER_WORD:
retval = __get_user(tmp, (__u8 __user *)arg);
if (retval == 0) {
u8 save = spi->bits_per_word;
spi->bits_per_word = tmp;
retval = spi_setup(spi);
if (retval < 0)
spi->bits_per_word = save;
else
dev_dbg(&spi->dev, "%d bits per word\n", tmp);
}
break;
case SPI_IOC_WR_MAX_SPEED_HZ:
retval = __get_user(tmp, (__u32 __user *)arg);
if (retval == 0) {
u32 save = spi->max_speed_hz;
spi->max_speed_hz = tmp;
retval = spi_setup(spi);
if (retval < 0)
spi->max_speed_hz = save;
else
dev_dbg(&spi->dev, "%d Hz (max)\n", tmp);
}
break;
可以看出它支持如下命令
SPI_IOC_RD_MODE 设置读的模式
SPI_IOC_RD_LSB_FIRST 设置读字的格式
SPI_IOC_RD_BITS_PER_WORD 设置读字长
SPI_IOC_RD_MAX_SPEED_HZ 设置读的速度
SPI_IOC_WR_MODE 设置写的模式
SPI_IOC_WR_LSB_FIRST 设置写的字格式
SPI_IOC_WR_BITS_PER_WORD 设置写的字长
SPI_IOC_WR_MAX_SPEED_HZ 设置写的速度
mode的取值可以是如下常量的组合
#define SPI_CPHA 0x01 /* clock phase */
#define SPI_CPOL 0x02 /* clock polarity */
#define SPI_MODE_0 (0|0) /* (original MicroWire) */
#define SPI_MODE_1 (0|SPI_CPHA)
#define SPI_MODE_2 (SPI_CPOL|0)
#define SPI_MODE_3 (SPI_CPOL|SPI_CPHA)
#define SPI_CS_HIGH 0x04 /* chipselect active high? */
#define SPI_LSB_FIRST 0x08 /* per-word bits-on-wire */
#define SPI_3WIRE 0x10 /* SI/SO signals shared */
#define SPI_LOOP 0x20 /* loopback mode */
#define SPI_NO_CS 0x40 /* 1 dev/bus, no chipselect */
#define SPI_READY 0x80 /* slave pulls low to pause */
深入SPI运行机制
前面简要讨论了如何构建和运行SPI,使得大家对如何用SPI有了一个基本的了解,下面我们将深入SPI内部的运行机制的讨论。同样,我们将从系统运行时的对象开始讨论,而这又离不开对struct的分析,以及系统运行时的内核SPI对象的分析。
我们首先从spi.h文件中定义的各个struct对象开始分析
在这个文件中定义了如下结构
spi_device、spi_driver、spi_master、spi_transfer、spi_message、spi_board_info
spi_device 是SPI从设备,即连接在SPI总线上的外围设备
spi_driver是SPI主机边的协议驱动
spi_master是spi控制器,即s3c6410中的spi端口的软件控制器
spi_transfer :a read/write buffer pair
struct spi_message : one multi-segment SPI transaction
在spidev.h中定义了如下结构
struct spi_ioc_transfer - describes a single SPI transfer
This structure is mapped directly to the kernel spi_transfer structure;
从注释中可以知道这个结构是对内核中的spi_transfer 的映射
我们再看看s3c64xx-spi.h中的定义,这里定义了
struct s3c64xx_spi_csinfo - ChipSelect description
struct s3c64xx_spi_info - SPI Controller defining structure
在spidev.c中定义了
spidev_data结构
在spi.c中定义了
boardinfo结构
在spi-s3c64xx.c中定义了
struct s3c64xx_spi_driver_data - Runtime info holder for SPI driver.
s3c64xx_spi_driver_data 是SPI驱动的运行时信息
上述这些结构构成了SPI运行的基础架构,也就是spi的静态结构,但是要搞清楚spi的运行机制从用户接口的read和write开始顺藤摸瓜会比较简单
让我们看看函数
static ssize_t
spidev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
这个函数是spi用户接口的函数
这个函数中status = spidev_sync_read(spidev, count);负责从spi总线上读出数据
missing = copy_to_user(buf, spidev->buffer, status);负责将数据从内核空间拷贝到用户空间
我们再分析spidev_sync_read函数
static inline ssize_t
spidev_sync_read(struct spidev_data *spidev, size_t len)
{
struct spi_transfer t = {
.rx_buf = spidev->buffer,
.len = len,
};
struct spi_message m;
spi_message_init(&m);
spi_message_add_tail(&t, &m);
return spidev_sync(spidev, &m);
}
这个函数生成了一个spi_message 和一个spi_transfer
并把他们加入到了一个传输队列中。spi_message_init函数就不细讲了,就是初始化spi_message 的结构
而spi_message_add_tail函数做了什么工作呢?是将spi_transfer加入到spi_message的传输任务列表
即spi_transfer其实是一个原子的传输任务单元
最终由spidev_sync负责执行,这个函数是同步执行的所以函数内部由异步执行的
status = spi_async(spidev->spi, message);
和wait_for_completion(&done);转换为同步执行
我们再看spi_async函数都做了什么?
它执行了一个内部函数ret = __spi_async(spi, message);
这个内部函数真正的传输动作是master->transfer(spi, message);
而我们追踪到这里发现spi_master结构中transfer仅仅是一个预定义的函数指针
我们回过头来从spi_master的初始化过程开始追踪,发现在spi_s3c64xx.c 中s3c64xx_spi_probe这个函数初始化spi_master时将函数s3c64xx_spi_transfer安装到了spi_master的函数指针transfer上,这种方法被大量用在linux内核中,这里暂时偏题一下,这种方法有什么好处呢?就是可以预先定义行为,但是却不用给出行为的实现,而行为的实现由于和具体的芯片架构有关系,所以由架构类的代码在初始化进行组装,最终形成完整的对象。就好比linux内核负责提供了汽车底盘,而大量的芯片厂商负责了给底盘组装轮子等外围设备,所以底盘厂商只要定义好接口就可以了。
让我们看看这个函数都干了什么?
static int s3c64xx_spi_transfer(struct spi_device *spi,
struct spi_message *msg)
{
struct s3c64xx_spi_driver_data *sdd;
unsigned long flags;
sdd = spi_master_get_devdata(spi->master);
spin_lock_irqsave(&sdd->lock, flags);
if (sdd->state & SUSPND) {
spin_unlock_irqrestore(&sdd->lock, flags);
return -ESHUTDOWN;
}
msg->status = -EINPROGRESS;
msg->actual_length = 0;
list_add_tail(&msg->queue, &sdd->queue);
queue_work(sdd->workqueue, &sdd->work);
spin_unlock_irqrestore(&sdd->lock, flags);
return 0;
}
注意加粗的函数,原来它仅仅是将传输任务加到了spi_master的运行时数据s3c64xx_spi_driver_data的任务队列中
而queue_work是干什么的?
分析s3c64xx_spi_driver_data这个结构对work和workqueue的作用不是很了解,而work和workqueue似乎又和执行transfer有非常大的关系。
分析spi_master的初始化过程发现work是由这个宏命令初始化的
INIT_WORK(&sdd->work, s3c64xx_spi_work);
分析s3c64xx_spi_work函数,发现传输任务最终由handle_msg函数负责执行
也就是说 handle_msg 函数最终操纵芯片执行了传输任务
我们先搁下handle_msg不表,先来回忆一下这个过程,可以发现整个过程是这样的,具体的传输任务其实最后都是由芯片厂商提供的代码完成,而linux内核团队负责制定了spi的用户标准,并为芯片厂商制定了向下的接口标准,这就是linux内核负责的核心负责的工作,它是与架构无关的,它负责制定标准。芯片厂商根据linux内核的标准,又提供了一些芯片相关的基础设施,而由主板厂商负责进行设备的组装。最终完成了spi控制器和spi从设备在内核中的安装工作,包括静态的数据结构和动态的函数功能的组装。具体的函数功能主要由芯片厂商的代码负责完成。
整个linux体系的生态链完整的呈现在我们的面前,linux内核研发团队、芯片厂商、主板厂商、应用厂商的分工明确而清晰。
到这里整个SPI的read过程的运行机制已经十分的清晰,而write过程与此类似就不剖析了。
下一帖,我们再来详细剖析handle_msg的工作,由于它其实是s3c64xx的代码,与架构相关,并且利用了片上的DMA控制器。