Linux 设备驱动的软件架构思想(五)(摘录)
驱动核心层
核心层肩负的 3 大职责:
1 )对上提供接口。 file_operations 的读、写、 ioctl 都被中间层搞定,各种 I/O 模型也被处理掉了。
2 )中间层实现通用逻辑。可以被底层各种实例共享的代码都被中间层搞定,避免底层重复实现。
3 )对下定义框架。底层的驱动不再需要关心 Linux 内核 VFS 的接口和各种可能的 I/O 模型,而只需处理与具体硬件相关的访问。
这种分层有时候还不是两层,可以有更多层,在软件上呈现为面向对象里类继承和多态的状态。上一节介绍的终端设备驱动,在软件层次上类似图。
主机驱动与外设驱动分离的设计思想
Linux 中的 SPI 、 I 2 C 、 USB 等子系统都利用了典型的把主机驱动和外设驱动分离的想法,让主机端只负责产生总线上的传输波形,而外设端只是通过标准的 API 来让主机端以适当的波形访问自身。因此这里面就涉及了 4 个软件模块:
1 )主机端的驱动。根据具体的 I 2 C 、 SPI 、 USB 等控制器的硬件手册,操作具体的 I 2 C 、 SPI 、 USB 等控制器,产生总线的各种波形。
2 )连接主机和外设的纽带。外设不直接调用主机端的驱动来产生波形,而是调一个标准的 API 。由这个标准的API 把这个波形的传输请求间接 “ 转发 ” 给了具体的主机端驱动。当然,在这里,最好把关于波形的描述也以某种数据结构标准化。
3 )外设端的驱动。外设接在 I 2 C 、 SPI 、 USB 这样的总线上,但是它们本身可以是触摸屏、网卡、声卡或者任意一种类型的设备。我们在相关的 i2c_driver 、 spi_driver 、 usb_driver 这种 xxx_driver 的 probe ()函数中去注册它具体的类型。当这些外设要求 I 2 C 、 SPI 、 USB 等去访问它的时候,它调用 “ 连接主机和外设的纽带 ” 模块的标准 API 。
4 )板级逻辑。板级逻辑用来描述主机和外设是如何互联的,它相当于一个 “ 路由表 ” 。假设板子上有多个 SPI 控制器和多个 SPI 外设,那究竟谁接在谁上面管理互联关系,既不是主机端的责任,也不是外设端的责任,这属于板级逻辑的责任。这部分通常出现在 arch/arm/mach-xxx 下面或者 arch/arm/boot/dts 下面。
什么叫良好的软件设计一言以蔽之,让正确的代码出现在正确的位置。不要在错误的时间、错误的地点,编写一段错误的代码。在 LKML 中,关于代码出现在错误的位置,常见的台词是代码 “out of place” 。
Linux 通过上述的设计方法,把一堆杂乱不友好的代码变成了 4 个轻量级的小模块,每个模块都各得其所。每个模块都觉得很 “ 爽 ” ,站在主机端想一想,它其实也是很 “ 爽 ” 的,因为它的职责本来就是产生波形,而现在我们就让它只产生波形不干别的;站在外设端想一想,它也变得一身轻松,因为它根本就不需要知道自己接在主机的哪个控制器上,根本不关心对方是张三、李四、王五还是六麻子;站在板级逻辑的角度上,你做了一个板子,自己自然要知道谁接在谁上面了。
下面以 SPI 子系统为例来展开说明,后续章节的 I 2 C 、 USB 等是类似的。
在 Linux 中,用代码清单 12.24 的 spi_master 结构体来描述一个 SPI 主机控制器驱动,其主要成员是主机控制器的序号(系统中可能存在多个 SPI 主机控制器)、片选数量、 SPI 模式、时钟设置用到的和数据传输用到的函数等。
spi_master 结构体
#define spi_master spi_controller
struct spi_controller {
struct device dev;
struct list_head list;
/* other than negative (== assign one dynamically), bus_num is fully
* board-specific. usually that simplifies to being SOC-specific.
* example: one SOC has three SPI controllers, numbered 0..2,
* and one board's schematics might show it using SPI-2. software
* would normally use bus_num=2 for that controller.
*/
s16 bus_num;
/* chipselects will be integral to many controllers; some others
* might use board-specific GPIOs.
*/
u16 num_chipselect;
/* some SPI controllers pose alignment requirements on DMAable
* buffers; let protocol drivers know about these requirements.
*/
u16 dma_alignment;
/* spi_device.mode flags understood by this controller driver */
u16 mode_bits;
/* bitmask of supported bits_per_word for transfers */
u32 bits_per_word_mask;
#define SPI_BPW_MASK(bits) BIT((bits) - 1)
#define SPI_BIT_MASK(bits) (((bits) == 32) ? ~0U : (BIT(bits) - 1))
#define SPI_BPW_RANGE_MASK(min, max) (SPI_BIT_MASK(max) - SPI_BIT_MASK(min - 1))
/* limits on transfer speed */
u32 min_speed_hz;
u32 max_speed_hz;
/* other constraints relevant to this driver */
u16 flags;
#define SPI_CONTROLLER_HALF_DUPLEX BIT(0) /* can't do full duplex */
#define SPI_CONTROLLER_NO_RX BIT(1) /* can't do buffer read */
#define SPI_CONTROLLER_NO_TX BIT(2) /* can't do buffer write */
#define SPI_CONTROLLER_MUST_RX BIT(3) /* requires rx */
#define SPI_CONTROLLER_MUST_TX BIT(4) /* requires tx */
#define SPI_MASTER_GPIO_SS BIT(5) /* GPIO CS must select slave */
/* flag indicating this is an SPI slave controller */
bool slave;
/*
* on some hardware transfer / message size may be constrained
* the limit may depend on device transfer settings
*/
size_t (*max_transfer_size)(struct spi_device *spi);
size_t (*max_message_size)(struct spi_device *spi);
/* I/O mutex */
struct mutex io_mutex;
/* lock and mutex for SPI bus locking */
spinlock_t bus_lock_spinlock;
struct mutex bus_lock_mutex;
/* flag indicating that the SPI bus is locked for exclusive use */
bool bus_lock_flag;
/* Setup mode and clock, etc (spi driver may call many times).
*
* IMPORTANT: this may be called when transfers to another
* device are active. DO NOT UPDATE SHARED REGISTERS in ways
* which could break those transfers.
*/
int (*setup)(struct spi_device *spi);
...
}
分配、注册和注销 SPI 主机的 API 由 SPI 核心提供
struct spi_master * spi_alloc_master(struct device *host, unsigned size);
int spi_register_master(struct spi_master *master);
void spi_unregister_master(struct spi_master *master);
在 Linux 中,用 spi_driver 结构体来描述一个 SPI 外设驱动,这个外设驱动可以认为是 spi_master 的客户端驱动。
spi_driver 结构体
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);
struct device_driver driver;
};
可以看出, spi_driver 结构体和 platform_driver 结构体有极大的相似性,都有 probe ()、
remove ()、suspend ()、 resume ()这样的接口和 device_driver 的实例。是的,
这几乎是一切客户端驱动的常用模板。
在 SPI 外设驱动中,当通过 SPI 总线进行数据传输的时候,使用了一套与 CPU 无关的统一的接口。这套接口的第 1个关键数据结构就是 spi_transfer ,它用于描述 SPI 传输
struct spi_transfer {
/* it's ok if tx_buf == rx_buf (right?)
* for MicroWire, one buffer must be null
* buffers must work with dma_*map_single() calls, unless
* spi_message.is_dma_mapped reports a pre-existing mapping
*/
const void *tx_buf;
void *rx_buf;
unsigned len;
dma_addr_t tx_dma;
dma_addr_t rx_dma;
struct sg_table tx_sg;
struct sg_table rx_sg;
unsigned cs_change:1;
unsigned tx_nbits:3;
unsigned rx_nbits:3;
#define SPI_NBITS_SINGLE 0x01 /* 1bit transfer */
#define SPI_NBITS_DUAL 0x02 /* 2bits transfer */
#define SPI_NBITS_QUAD 0x04 /* 4bits transfer */
u8 bits_per_word;
u16 delay_usecs;
u32 speed_hz;
u16 word_delay;
struct list_head transfer_list;
};
而一次完整的 SPI 传输流程可能不是只包含一次 spi_transfer ,它可能包含一个或多个 spi_transfer ,这些 spi_transfer最终通过 spi_message 组织在一起
struct spi_message {
struct list_head transfers;
struct spi_device *spi;
unsigned is_dma_mapped:1;
/* REVISIT: we might want a flag affecting the behavior of the
* last transfer ... allowing things like "read 16 bit length L"
* immediately followed by "read L bytes". Basically imposing
* a specific message scheduling algorithm.
*
* Some controller drivers (message-at-a-time queue processing)
* could provide that as their default scheduling algorithm. But
* others (with multi-message pipelines) could need a flag to
* tell them about such special cases.
*/
/* completion is reported through a callback */
void (*complete)(void *context);
void *context;
unsigned frame_length;
unsigned actual_length;
int status;
/* for optional use by whatever driver currently owns the
* spi_message ... between calls to spi_async and then later
* complete(), that's the spi_controller controller driver.
*/
struct list_head queue;
void *state;
/* list of spi_res reources when the spi message is processed */
struct list_head resources;
};
通过 spi_message_init ()可以初始化 spi_message ,而将 spi_transfer 添加到 spi_message 队列的方法则是:
void spi_message_add_tail(struct spi_transfer *t, struct spi_message *m);
发起一次 spi_message 的传输有同步和异步两种方式,使用同步 API 时,会阻塞等待这个消息被处理完。同步操作时使用的 API 是:
int spi_sync(struct spi_device *spi, struct spi_message *message);
使用异步 API 时,不会阻塞等待这个消息被处理完,但是可以在 spi_message 的 complete 字段挂接一个回调函数,当消息被处理完成后,该函数会被调用。在异步操作时使用的 API 是:
int spi_async(struct spi_device *spi, struct spi_message *message);
下列代码是非常典型的初始化 spi_transfer 、 spi_message 并进行 SPI 数据传输的例子,同时 spi_write ()、spi_read ()也是 SPI 核心层的两个通用快捷 API ,在 SPI 外设驱动中可以直接调用它们进行简单的纯写和纯读操作。
static inline int spi_write(struct spi_device *spi, const u8*buf, size_t len)
{
struct spi_transfer t = {
.tx_buf = buf,
.len = len,
};
struct spi_message m;
spi_message_init(&m);
spi_message_add_tail(&t, &m);
return spi_sync(spi, &m);
}
static inline int spi_read(struct spi_device *spi, u8*buf, size_t len)
{
struct spi_transfer t = {
.rx_buf = buf,
.len = len,
};
struct spi_message m;
spi_message_init(&m);
spi_message_add_tail(&t, &m);
return spi_sync(spi, &m);
}
SPI 主机控制器驱动位于 drivers/spi/ ,这些驱动的主体是实现了 spi_master 的 transfer ()、 transfer_one ()、setup ()这样的成员函数,当然,也可能是实现 spi_bitbang 的 txrx_bufs ()、 setup_transfer ()、 chipselect ()这样的成员函数。
SPI 主机端驱动完成的波形传输
static int pl022_transfer_one_message(struct spi_master *master,
struct spi_message *msg)
{
struct pl022*pl022= spi_master_get_devdata(master);
/* Initial message state */
pl022->cur_msg = msg;
msg->state = STATE_START;
pl022->cur_transfer = list_entry(msg->transfers.next,
struct spi_transfer, transfer_list);
/* Setup the SPI using the per chip configuration */
pl022->cur_chip = spi_get_ctldata(msg->spi);
pl022->cur_cs = pl022->chipselects[msg->spi->chip_select];
restore_state(pl022);
flush(pl022);
if (pl022->cur_chip->xfer_type == POLLING_TRANSFER)
do_polling_transfer(pl022);
else
do_interrupt_dma_transfer(pl022);
return 0;
}
static int pl022_setup(struct spi_device *spi)
{
...
/* Stuff that is common for all versions */
if (spi->mode & SPI_CPOL)
tmp = SSP_CLK_POL_IDLE_HIGH;
else
tmp = SSP_CLK_POL_IDLE_LOW;
SSP_WRITE_BITS(chip->cr0, tmp, SSP_CR0_MASK_SPO, 6);
if (spi->mode & SPI_CPHA)
tmp = SSP_CLK_SECOND_EDGE;
else
tmp = SSP_CLK_FIRST_EDGE;
SSP_WRITE_BITS(chip->cr0, tmp, SSP_CR0_MASK_SPH, 7);
...
}
static int pl022_probe(struct amba_device *adev, const struct amba_id *id)
{
...
/*
* Bus Number Which has been Assigned to this SSP controller
* on this board
*/
master->bus_num = platform_info->bus_id;
master->num_chipselect = num_cs;
master->cleanup = pl022_cleanup;
master->setup = pl022_setup;
master->auto_runtime_pm = true;60 master->transfer_one_message = pl022_transfer_one_message;
master->unprepare_transfer_hardware = pl022_unprepare_transfer_hardware;
master->rt = platform_info->rt;
master->dev.of_node = dev->of_node;
...
}
SPI 外设驱动遍布于内核的 drivers 、 sound 的各个子目录之下, SPI 只是一种总线, spi_driver 的作用只是将 SPI 外设挂接在该总线上,因此在 spi_driver 的 probe ()成员函数中,将注册 SPI 外设本身所属设备驱动的类型。
和 platform_driver 对应着一个 platform_device 一样, spi_driver 也对应着一个spi_device ;platform_device 需要在 BSP的板文件中添加板信息数据,而 spi_device 也同样需要。 spi_device 的板信息用 spi_board_info 结构体描述,该结构体记录着 SPI 外设使用的主机控制器序号、片选序号、数据比特率、 SPI 传输模式(即 CPOL 、 CPHA )等。诺基亚 770 上的两个 SPI 设备的板信息数据如代码清单所示,位于板文件 arch/arm/mach-omap1/board-nokia770.c中。
诺基亚 770 板文件中的 spi_board_info
static struct spi_board_info nokia770_spi_board_info[] __initdata = {
[0] = {
.modalias = "lcd_mipid",
.bus_num = 2, /*用到的SPI 主机控制器序号 */
.chip_select = 3, /*使用哪个片选*/
.max_speed_hz = 12000000, /* SPI 数据传输比特率 */
.platform_data = &nokia770_mipid_platform_data,
},
[1] = {
.modalias = "ads7846",
.bus_num = 2,
.chip_select = 0,
.max_speed_hz = 2500000,
.irq = OMAP_GPIO_IRQ(15),
.platform_data = &nokia770_ads7846_platform_data,
},
};
在 Linux 启动过程中,在机器的 init_machine ()函数中,会通过如下语句注册这些 spi_board_info :
spi_register_board_info(nokia770_spi_board_info,
ARRAY_SIZE(nokia770_spi_board_info));
这一点和启动时通过 platform_add_devices ()添加 platform_device 非常相似。
ARM Linux 3.x 之后的内核在改为设备树后,不再需要在 arch/arm/mach-xxx 中编码 SPI 的板级信息了,而倾向于在SPI 控制器节点下填写子节点,如下代码清单给出了 arch/arm/boot/dts/omap3-overo-common-lcd43.dtsi 中包含的ads7846 节点。
通过设备树添加 SPI 外设
&mcspi1{
pinctrl-names = "default";
pinctrl-0= <&mcspi1_pins>;
/* touch controller */
ads7846@0{
pinctrl-names = "default";
pinctrl-0= <&ads7846_pins>;
compatible = "ti,ads7846";
vcc-supply = <&ads7846reg>;
reg = <0>;
/* CS0*/
spi-max-frequency = <1500000>;
interrupt-parent = <&gpio4>;
interrupts = <180>;
pendown-gpio = <&gpio4180>;
/* gpio_114*/
ti,x-min = /bits/ 16<0x0>;
ti,x-max = /bits/ 16<0x0fff>;
ti,y-min = /bits/ 16<0x0>;
ti,y-max = /bits/ 16<0x0fff>;
ti,x-plate-ohms = /bits/ 16<180>;
ti,pressure-max = /bits/ 16<255>;
linux,wakeup;
};
};
真实生活中的驱动它往往包含了 platform 、分层、分离等诸多概念。Linux 内核目前有百多个驱动子系统,一个个去学肯定是不现实的,在方法上也是错误的。我们要掌握其规律,以不变应万变,以无招胜有招。