linux kernel中module_platform_driver实现原理分析

介绍

在linux kernel中通过module_platform_driver来实现模块化平台驱动。
大量的设备驱动程序都基于该种方式来实现,使用频次非常的高,在linux kernel 5.4.124的代码中搜索module_platform_driver共有2356次引用。


这个宏的使用方式大相径庭,有一套成熟的代码书写方式,将驱动程序入口符号作为宏的参数,基本格式如下:

历史

它的定义在include/linux/platform_device.h中,从文件的名字来看可知它存在的意义是基于platform_device的。platform_device.h这个文件在2005年的linux-2.6.15就存在了。

platform_device.h在创建初期并没有现在这么多丰富的功能,通过platform_xxx_register来注册驱动和设备,并没有提供module_platform_driver这个辅助宏。

 /*
 * platform_device.h - generic, centralized driver model
 *
 * Copyright (c) 2001-2003 Patrick Mochel <mochel@osdl.org>
 *
 * This file is released under the GPLv2
 *
 * See Documentation/driver-model/ for more information.
 */

#ifndef _PLATFORM_DEVICE_H_
#define _PLATFORM_DEVICE_H_

#include <linux/device.h>

struct platform_device {
	const char	* name;
	u32		id;
	struct device	dev;
	u32		num_resources;
	struct resource	* resource;
};

#define to_platform_device(x) container_of((x), struct platform_device, dev)

extern int platform_device_register(struct platform_device *);
extern void platform_device_unregister(struct platform_device *);

extern struct bus_type platform_bus_type;
extern struct device platform_bus;

extern struct resource *platform_get_resource(struct platform_device *, unsigned int, unsigned int);
extern int platform_get_irq(struct platform_device *, unsigned int);
extern struct resource *platform_get_resource_byname(struct platform_device *, unsigned int, char *);
extern int platform_get_irq_byname(struct platform_device *, char *);
extern int platform_add_devices(struct platform_device **, int);

extern struct platform_device *platform_device_register_simple(char *, unsigned int, struct resource *, unsigned int);

extern struct platform_device *platform_device_alloc(const char *name, unsigned int id);
extern int platform_device_add_resources(struct platform_device *pdev, struct resource *res, unsigned int num);
extern int platform_device_add_data(struct platform_device *pdev, void *data, size_t size);
extern int platform_device_add(struct platform_device *pdev);
extern void platform_device_put(struct platform_device *pdev);

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

extern int platform_driver_register(struct platform_driver *);
extern void platform_driver_unregister(struct platform_driver *);

#define platform_get_drvdata(_dev)	dev_get_drvdata(&(_dev)->dev)
#define platform_set_drvdata(_dev,data)	dev_set_drvdata(&(_dev)->dev, (data))

#endif /* _PLATFORM_DEVICE_H_ */

platform_xxx_register这类宏在linux kernel 5.4.124中也在使用。

不能通过引用计数少或者版本迭代的原因来评价这两类宏谁好谁坏,各自有各自的应用场景。当使用platform_xxx_register时,基本格式也是比较固定的,例如:

static int __init ehci_platform_init(void)
{
	if (usb_disabled())
		return -ENODEV;

	ehci_init_driver(&ehci_platform_hc_driver, &platform_overrides);
	return platform_driver_register(&ehci_mv_driver);
}
module_init(ehci_platform_init);

static void __exit ehci_platform_cleanup(void)
{
	platform_driver_unregister(&ehci_mv_driver);
}
module_exit(ehci_platform_cleanup);

MODULE_DESCRIPTION("Marvell EHCI driver");
MODULE_AUTHOR("Chao Xie <chao.xie@marvell.com>");
MODULE_AUTHOR("Neil Zhang <zhangwm@marvell.com>");
MODULE_ALIAS("mv-ehci");
MODULE_LICENSE("GPL");
MODULE_DEVICE_TABLE(of, ehci_mv_dt_ids);

从2012年linux kernel 3.xx开始增加了module_platform_driver这个宏,一直延续至今。从module_platform_driver的定义处可以发现,它是platform_driver_register的一个封装应用。

#define module_platform_driver(__platform_driver) \
	module_driver(__platform_driver, platform_driver_register, \
			platform_driver_unregister)

存在的意义和原理

正如前面介绍的module_init这个宏,在使用它的时候要定义两个函数以及生命两个宏。而使用了module_platform_driver这个宏之后,只需要一行代码就可以实现这些功能。
module_platform_driver这个宏展开之后,就是module_init这一部分代码内容。

#define module_platform_driver(__platform_driver) \
	module_driver(__platform_driver, platform_driver_register, \
			platform_driver_unregister)


#define module_driver(__driver, __register, __unregister, ...) \
static int __init __driver##_init(void) \
{ \
	return __register(&(__driver) , ##__VA_ARGS__); \
} \
module_init(__driver##_init); \
static void __exit __driver##_exit(void) \
{ \
	__unregister(&(__driver) , ##__VA_ARGS__); \
} \
module_exit(__driver##_exit);

驱动的注册与卸载方法采用了platform.c中提供的通用API。

/**
 * __platform_driver_register - register a driver for platform-level devices
 * @drv: platform driver structure
 * @owner: owning module/driver
 */
int __platform_driver_register(struct platform_driver *drv,
				struct module *owner)
{
	drv->driver.owner = owner;
	drv->driver.bus = &platform_bus_type;
	drv->driver.probe = platform_drv_probe;
	drv->driver.remove = platform_drv_remove;
	drv->driver.shutdown = platform_drv_shutdown;

	return driver_register(&drv->driver);
}
EXPORT_SYMBOL_GPL(__platform_driver_register);

module_init这个宏在include/linux/module.h中定义,在kernel初始化过程中调用do_initcall()或插入驱动ko文件时得到执行。每个驱动模块仅需实现一个module_init与module_exit即可。
驱动代码在使用module_platform_driver注册驱动时,经过编译后的文件内容如下:

module_init宏最终是调用了__initcall(x),定义了程序链接时的初始化等级为1。

#define module_init(x)	__initcall(x);
#define __initcall(fn) __define_initcall("1", fn)

关于initcall:

#ifdef CONFIG_HAVE_ARCH_PREL32_RELOCATIONS
#define ___define_initcall(fn, id, __sec)			\
	__ADDRESSABLE(fn)					\
	asm(".section	\"" #__sec ".init\", \"a\"	\n"	\
	"__initcall_" #fn #id ":			\n"	\
	    ".long	" #fn " - .			\n"	\
	    ".previous					\n");
#else
#define ___define_initcall(fn, id, __sec) \
	static initcall_t __initcall_##fn##id __used \
		__attribute__((__section__(#__sec ".init"))) = fn;
#endif

而通过module_init定义的驱动API编译后的符号表示都增加了initcall的前缀


最后,透过一张图看清module_platform_driver声明的驱动调用流程:

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Chapter 1. Introduction Section 1.1. Why Linux? Section 1.2. Embedded Linux Today Section 1.3. Open Source and the GPL Section 1.4. Standards and Relevant Bodies Section 1.5. Chapter Summary Chapter 2. Your First Embedded Experience Section 2.1. Embedded or Not? Section 2.2. Anatomy of an Embedded System Section 2.3. Storage Considerations Section 2.4. Embedded Linux Distributions Section 2.5. Chapter Summary Chapter 3. Processor Basics Section 3.1. Stand-alone Processors Section 3.2. Integrated Processors: Systems on Chip Section 3.3. Hardware Platforms Section 3.4. Chapter Summary Chapter 4. The Linux KernelA Different Perspective Section 4.1. Background Section 4.2. Linux Kernel Construction Section 4.3. Kernel Build System Section 4.4. Obtaining a Linux Kernel Section 4.5. Chapter Summary Chapter 5. Kernel Initialization Section 5.1. Composite Kernel Image: Piggy and Friends Section 5.2. Initialization Flow of Control Section 5.3. Kernel Command Line Processing Section 5.4. Subsystem Initialization Section 5.5. The init Thread Section 5.6. Chapter Summary Chapter 6. System Initialization Section 6.1. Root File System Section 6.2. Kernel's Last Boot Steps Section 6.3. The Init Process Section 6.4. Initial RAM Disk Section 6.5. Using initramfs Section 6.6. Shutdown Section 6.7. Chapter Summary Chapter 7. Bootloaders Section 7.1. Role of a Bootloader Section 7.2. Bootloader Challenges Section 7.3. A Universal Bootloader: Das U-Boot Section 7.4. Porting U-Boot Section 7.5. Other Bootloaders Section 7.6. Chapter Summary Chapter 8. Device Driver Basics Section 8.1. Device Driver Concepts Section 8.2. Module Utilities Section 8.3. Driver Methods Section 8.4. Bringing It All Together Section 8.5. Device Drivers and the GPL Section 8.6. Chapter Summary Chapter 9. File Systems Section 9.1. Linux File System Concepts Section 9.2. ext2 Section 9.3. ext3 Section 9.4. ReiserFS Section 9.5. JFFS2 Section 9.6. cramfs Section 9.7. Network File System Section 9.8. Pseudo File Sys
以下是 Linux kernel 4.14 AXI UART 16550 驱动程序的完整源代码,供参考: ```c /* * AXI 16550 UART driver */ #include <linux/clk.h> #include <linux/console.h> #include <linux/delay.h> #include <linux/interrupt.h> #include <linux/io.h> #include <linux/module.h> #include <linux/serial_core.h> #include <linux/tty.h> #include <linux/tty_flip.h> #include <linux/tty_port.h> #define UART_LSR_THRE 0x20 /* Transmit-hold-register empty */ #define XUARTPS_CR_STOPBRK 0x00000001 /* Stop transmitter break */ #define XUARTPS_CR_STARTBRK 0x00000002 /* Start transmitter break */ #define XUARTPS_CR_TORST 0x00000008 /* Transmit out-of-band reset */ #define XUARTPS_CR_TX_DIS 0x00000010 /* Transmit disable */ #define XUARTPS_CR_TX_EN 0x00000020 /* Transmit enable */ #define XUARTPS_CR_RX_DIS 0x00000040 /* Receive disable */ #define XUARTPS_CR_RX_EN 0x00000080 /* Receive enable */ /* RS485 support */ #define AXI_RS485_ENABLED (1 << 0) #define AXI_RS485_RTS_ACTIVE_HIGH (1 << 1) #define AXI_RS485_RX_DURING_TX (1 << 2) /* AXI UART 16550 registers */ struct axi_uart16550 { u32 rxfifo; u32 txdata; u32 ctrl; u32 intr_enable; u32 intr_status; u32 baud_rate_gen; u32 rcv_timeout; u32 flow_ctrl; u32 mode; }; /* AXI UART 16550 driver data */ struct axi_uart16550_data { struct uart_port port; struct clk *clk; void __iomem *regs; struct tty_port tty_port; unsigned int ns16550_irq; spinlock_t lock; u32 ctrl_reg; unsigned int rs485_flags; }; /* AXI UART 16550 driver functions */ static void axi_uart16550_set_mctrl(struct uart_port *port, u_int mctrl) { struct axi_uart16550_data *data = container_of(port, struct axi_uart16550_data, port); unsigned long flags; spin_lock_irqsave(&data->lock, flags); if (mctrl & TIOCM_RTS) { if (data->rs485_flags & AXI_RS485_ENABLED) { if (data->rs485_flags & AXI_RS485_RTS_ACTIVE_HIGH) data->ctrl_reg |= XUARTPS_CR_TX_EN; else data->ctrl_reg &= ~XUARTPS_CR_TX_EN; } else { data->ctrl_reg |= XUARTPS_CR_TX_EN; } } else { data->ctrl_reg &= ~XUARTPS_CR_TX_EN; } if (mctrl & TIOCM_DTR) { data->ctrl_reg |= XUARTPS_CR_RX_EN; } else { data->ctrl_reg &= ~XUARTPS_CR_RX_EN; } writel(data->ctrl_reg, data->regs + offsetof(struct axi_uart16550, ctrl)); spin_unlock_irqrestore(&data->lock, flags); } static u_int axi_uart16550_get_mctrl(struct uart_port *port) { struct axi_uart16550_data *data = container_of(port, struct axi_uart16550_data, port); unsigned long flags; u_int status = 0; spin_lock_irqsave(&data->lock, flags); if (data->ctrl_reg & XUARTPS_CR_TX_EN) status |= TIOCM_RTS; if (data->ctrl_reg & XUARTPS_CR_RX_EN) status |= TIOCM_DTR; spin_unlock_irqrestore(&data->lock, flags); return status; } static void axi_uart16550_stop_tx(struct uart_port *port) { struct axi_uart16550_data *data = container_of(port, struct axi_uart16550_data, port); unsigned long flags; spin_lock_irqsave(&data->lock, flags); data->ctrl_reg &= ~XUARTPS_CR_TX_EN; writel(data->ctrl_reg, data->regs + offsetof(struct axi_uart16550, ctrl)); spin_unlock_irqrestore(&data->lock, flags); } static void axi_uart16550_start_tx(struct uart_port *port) { struct axi_uart16550_data *data = container_of(port, struct axi_uart16550_data, port); unsigned long flags; spin_lock_irqsave(&data->lock, flags); if (data->rs485_flags & AXI_RS485_ENABLED) { if (data->rs485_flags & AXI_RS485_RX_DURING_TX) data->ctrl_reg |= XUARTPS_CR_RX_EN; else data->ctrl_reg &= ~XUARTPS_CR_RX_EN; if (data->rs485_flags & AXI_RS485_RTS_ACTIVE_HIGH) data->ctrl_reg |= XUARTPS_CR_TX_EN; else data->ctrl_reg &= ~XUARTPS_CR_TX_EN; } else { data->ctrl_reg |= XUARTPS_CR_TX_EN; data->ctrl_reg |= XUARTPS_CR_RX_EN; } writel(data->ctrl_reg, data->regs + offsetof(struct axi_uart16550, ctrl)); spin_unlock_irqrestore(&data->lock, flags); } static void axi_uart16550_stop_rx(struct uart_port *port) { struct axi_uart16550_data *data = container_of(port, struct axi_uart16550_data, port); unsigned long flags; spin_lock_irqsave(&data->lock, flags); data->ctrl_reg &= ~XUARTPS_CR_RX_EN; writel(data->ctrl_reg, data->regs + offsetof(struct axi_uart16550, ctrl)); spin_unlock_irqrestore(&data->lock, flags); } static void axi_uart16550_enable_ms(struct uart_port *port) { struct axi_uart16550_data *data = container_of(port, struct axi_uart16550_data, port); data->ctrl_reg |= XUARTPS_CR_RX_EN | XUARTPS_CR_TX_EN; writel(data->ctrl_reg, data->regs + offsetof(struct axi_uart16550, ctrl)); } static void axi_uart16550_break_ctl(struct uart_port *port, int break_state) { struct axi_uart16550_data *data = container_of(port, struct axi_uart16550_data, port); if (break_state == -1) { data->ctrl_reg &= ~XUARTPS_CR_STARTBRK; data->ctrl_reg |= XUARTPS_CR_STOPBRK; } else if (break_state == 1) { data->ctrl_reg |= XUARTPS_CR_STARTBRK; data->ctrl_reg &= ~XUARTPS_CR_STOPBRK; } writel(data->ctrl_reg, data->regs + offsetof(struct axi_uart16550, ctrl)); } static int axi_uart16550_startup(struct uart_port *port) { struct axi_uart16550_data *data = container_of(port, struct axi_uart16550_data, port); int ret; /* Enable clock */ ret = clk_prepare_enable(data->clk); if (ret < 0) { dev_err(port->dev, "failed to enable clock: %d\n", ret); return ret; } /* Initialize port */ uart_port_set_ops(port, &serial8250_ops); port->flags = UPF_BOOT_AUTOCONF | UPF_FIXED_PORT; port->iotype = UPIO_MEM; port->regshift = 2; port->mapbase = (unsigned long)data->regs; port->type = PORT_16550A; port->irq = data->ns16550_irq; spin_lock_init(&data->lock); tty_port_init(&data->tty_port); port->private_data = &data->tty_port; /* Register port */ ret = uart_add_one_port(&serial8250_reg, port); if (ret < 0) { dev_err(port->dev, "failed to register port: %d\n", ret); clk_disable_unprepare(data->clk); return ret; } /* Enable IRQ */ writel(0xff, data->regs + offsetof(struct axi_uart16550, intr_enable)); return 0; } static void axi_uart16550_shutdown(struct uart_port *port) { struct axi_uart16550_data *data = container_of(port, struct axi_uart16550_data, port); /* Disable IRQ */ writel(0x00, data->regs + offsetof(struct axi_uart16550, intr_enable)); /* Unregister port */ uart_remove_one_port(&serial8250_reg, port); /* Free resources */ clk_disable_unprepare(data->clk); } static int axi_uart16550_request_port(struct uart_port *port) { return 0; } static void axi_uart16550_config_port(struct uart_port *port, int flags) { struct axi_uart16550_data *data = container_of(port, struct axi_uart16550_data, port); unsigned long baud_divisor; u32 mode_reg = 0; /* Set baud rate */ baud_divisor = uart_get_baud_rate(port, flags, port->uartclk, 115200, 4000000); uart_update_timeout(port, baud_divisor, 115200); writel(baud_divisor, data->regs + offsetof(struct axi_uart16550, baud_rate_gen)); /* Set data format */ mode_reg |= (3 << 0); /* 8 data bits */ mode_reg |= (0 << 3); /* 1 stop bit */ mode_reg |= (0 << 4); /* no parity */ mode_reg |= (0 << 6); /* no break */ writel(mode_reg, data->regs + offsetof(struct axi_uart16550, mode)); } static void axi_uart16550_release_port(struct uart_port *port) { /* Do nothing */ } static const struct uart_ops axi_uart16550_ops = { .tx_empty = serial8250_tx_empty, .set_mctrl = axi_uart16550_set_mctrl, .get_mctrl = axi_uart16550_get_mctrl, .stop_tx = axi_uart16550_stop_tx, .start_tx = axi_uart16550_start_tx, .stop_rx = axi_uart16550_stop_rx, .enable_ms = axi_uart16550_enable_ms, .break_ctl = axi_uart16550_break_ctl, .startup = axi_uart16550_startup, .shutdown = axi_uart16550_shutdown, .flush_buffer = serial8250_clear_and_reinit_fifos, .set_termios = serial8250_set_termios, .type = serial8250_type, .release_port = axi_uart16550_release_port, .request_port = axi_uart16550_request_port, .config_port = axi_uart16550_config_port, }; /* AXI UART 16550 driver initialization */ static int axi_uart16550_probe(struct platform_device *pdev) { struct

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Linux与SoC

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值