Linux 设备驱动的软件架构思想(五)(摘录)

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 内核目前有百多个驱动子系统,一个个去学肯定是不现实的,在方法上也是错误的。我们要掌握其规律,以不变应万变,以无招胜有招。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值