linux源码相关文件:
- serial-core.c
- include/linux/serial_core.h
一、底层串行硬件驱动程序
底层串行硬件的驱动程序负责向serial核心驱动程序提供由struct uart_port
定义的端口信息和一组由struct uart_ops
定义的控制方法,底层驱动程序还负责处理端口的中断,并提供对控制台的支持。
二、Console支持
serial核心提供了一些助手函数:
uart_get_console()
识别正确的端口结构。uart_parse_options()
解析命令行参数。uart_console_write()
用于执行逐字符写入,将换行符转换为CRLF序列。在驱动程序编写的时候建议使用此函数,而不是实现新的写入接口。
三、锁支持
底层硬件驱动程序负责使用port->lock
执行必要的锁定。支持两把锁:一个是端口自旋锁,另一个是overall
信号量。从uart核心驱动程序的角度来看,port->lock
用于锁定以下的数据:
port->mctrl
port->icount
port->state->xmit.head (circ_buf->head)
port->state->xmit.tail (circ_buf->tail)
底层驱动程序可以自由地使用该锁来实现额外的锁定,port_mutex
互斥量用于防止在不适当的时间添加、删除或重新配置端口。
四、核心数据结构
1、struct uart_driver
struct uart_driver
结构表示具体UART驱动。该结构定义如下(/include/linux/serial_core.h):
struct uart_driver {
struct module *owner; //驱动模块的拥有者
const char *driver_name; //驱动名称
const char *dev_name; //设备名称
int major; //主设备号
int minor; //从设备号
int nr;
struct console *cons; //console
/*
* these are private; the low level driver should not
* touch these; they should be initialised to NULL
*/
struct uart_state *state; //uart状态
struct tty_driver *tty_driver; //描述ttydriver
};
2、struct uart_port
struct uart_port
表示一个具体的port,该结构定义如下(include/linux/serial_core.h):
struct uart_port {
spinlock_t lock; /* port 锁 */
unsigned long iobase; /* 输入/输出地址 */
unsigned char __iomem *membase; /* read/write[bwl] */
unsigned int (*serial_in)(struct uart_port *, int);
void (*serial_out)(struct uart_port *, int, int);
void (*set_termios)(struct uart_port *,
struct ktermios *new,
struct ktermios *old);
void (*set_mctrl)(struct uart_port *, unsigned int);
int (*startup)(struct uart_port *port);
void (*shutdown)(struct uart_port *port);
void (*throttle)(struct uart_port *port);
void (*unthrottle)(struct uart_port *port);
int (*handle_irq)(struct uart_port *);
void (*pm)(struct uart_port *, unsigned int state,
unsigned int old);
void (*handle_break)(struct uart_port *);
int (*rs485_config)(struct uart_port *,
struct serial_rs485 *rs485);
unsigned int irq; /* irq number */
unsigned long irqflags; /* irq flags */
unsigned int uartclk; /* base uart clock */
unsigned int fifosize; /* tx fifo size */
unsigned char x_char; /* xon/xoff char */
unsigned char regshift; /* reg offset shift */
unsigned char iotype; /* io access style */
unsigned char unused1;
unsigned int read_status_mask; /* driver specific */
unsigned int ignore_status_mask; /* driver specific */
struct uart_state *state; /* 指向父状态的指针 */
struct uart_icount icount; /* 通信信息 */
struct console *cons; /* struct console, if any */
#if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(SUPPORT_SYSRQ)
unsigned long sysrq; /* sysrq timeout */
#endif
/* flags must be updated while holding port mutex */
upf_t flags;
#if __UPF_CHANGE_MASK > ASYNC_FLAGS
#error Change mask not equivalent to userspace-visible bit defines
#endif
/*
* Must hold termios_rwsem, port mutex and port lock to change;
* can hold any one lock to read.
*/
upstat_t status;
int hw_stopped; /* sw-assisted CTS flow state */
unsigned int mctrl; /* 当前调制解调器CTRL设置 */
unsigned int timeout; /* character-based timeout */
unsigned int type; /* port 类型 */
const struct uart_ops *ops;
unsigned int custom_divisor;
unsigned int line; /* port 索引号 */
unsigned int minor;
resource_size_t mapbase; /* 用于 ioremap */
resource_size_t mapsize;
struct device *dev; /* 父 device */
unsigned char hub6; /* 应该在8250驱动程序中使用 */
unsigned char suspended;
unsigned char irq_wake;
unsigned char unused[2];
struct attribute_group *attr_group; /* port 特殊的属性 */
const struct attribute_group **tty_groups; /* 所有的属性 (仅限于serial core 使用) */
struct serial_rs485 rs485;
void *private_data; /* 通用 platform data 指针 */
};
3、struct uart_ops
struct uart_ops
用于描述serial核心和驱动程序之间的接口,实现如下:
struct uart_ops {
unsigned int (*tx_empty)(struct uart_port *);
void (*set_mctrl)(struct uart_port *, unsigned int mctrl);
unsigned int (*get_mctrl)(struct uart_port *);
void (*stop_tx)(struct uart_port *);
void (*start_tx)(struct uart_port *);
void (*throttle)(struct uart_port *);
void (*unthrottle)(struct uart_port *);
void (*send_xchar)(struct uart_port *, char ch);
void (*stop_rx)(struct uart_port *);
void (*start_rx)(struct uart_port *);
void (*enable_ms)(struct uart_port *);
void (*break_ctl)(struct uart_port *, int ctl);
int (*startup)(struct uart_port *);
void (*shutdown)(struct uart_port *);
void (*flush_buffer)(struct uart_port *);
void (*set_termios)(struct uart_port *, struct ktermios *new, const struct ktermios *old);
void (*set_ldisc)(struct uart_port *, struct ktermios *);
void (*pm)(struct uart_port *, unsigned int state, unsigned int oldstate);
const char *(*type)(struct uart_port *);
void (*release_port)(struct uart_port *);
int (*request_port)(struct uart_port *);
void (*config_port)(struct uart_port *, int);
int (*verify_port)(struct uart_port *, struct serial_struct *);
int (*ioctl)(struct uart_port *, unsigned int, unsigned long);
#ifdef CONFIG_CONSOLE_POLL;
int (*poll_init)(struct uart_port *);
void (*poll_put_char)(struct uart_port *, unsigned char);
int (*poll_get_char)(struct uart_port *);
#endif;
};
tx_empty:此函数用于测试端口的发送FIFO和移位器是否为空,如果是空的,这个函数应该返回TIOCSER_TEMT,否则返回0。如果端口不支持此操作,则应该返回TIOCSER_TEMT。没有锁定。中断:依赖于调用者,这个调用不能导致睡眠。
set_mctrl:此功能将端口的调制解调器控制线设置为
mcctrl
所描述的状态。mctrl
支持的参数是:
TIOCM_RTS表示RTS信号,TIOCM_DTR表示DTR信号,TIOCM_OUT1表示OUT1信号,TIOCM_OUT2表示OUT2信号,TIOCM_LOOP表示设置端口为环回模式。如果设置了合适的位,则信号应被驱动激活;如果该位被清除,则信号应被驱动为非激活状态。
在port->lock
获取的情况下锁定。禁用本地中断。该调用不能睡眠。
- get_mctrl:返回端口调制解调器控制输入的当前状态。输出的状态不应该被返回,因为内核会跟踪它们的状态。状态信息应包括:TIOCM_CAR表示DCD的信号状态。TIOCM_CTS表示CTS的信号状态,TIOCM_DSR表示DSR信号状态,TIOCM_RI表示RI信号状态。
如果设置该位,则将信号驱动为激活状态,如果端口不支持CTS, DCD或DSR,驱动程序应该表明信号是永久激活的。如果RI不可用,信号不应该显示为激活状态。在port->lock
获取的情况下锁定。禁用本地中断。调用该函数必须不能睡眠。
stop_tx:停止传输字符。这可能是由于CTS线路没有激活,或者tty层表明由于XOFF字符而停止传输。驱动程序应该尽快停止传输字符。在
port->lock
获取的情况下锁定,禁用本地中断,该调用不能睡眠。start_tx:开始传输字符。在
port->lock
获取的情况下锁定,禁用本地中断,该调用不能睡眠。throttle:通知串行驱动程序,line规则的输入缓冲区已接近满,并且它应该以某种方式发出信号,不应该再向串行端口发送字符,只有在启用了硬件辅助流控制时才会调用该函数。由tty层通过
unthrottle()
和termios
修改序列化锁定。unthrottle:通知串行驱动程序,字符现在可以发送到串行端口,而不必担心超出line规则的输入缓冲区。只有在启用了硬件辅助流控制时才会调用该函数。
send_xchar:发送一个高优先级字符,即使端口停止。这是用来实现XON/XOFF流量控制和
tcflow()
。如果串行驱动程序没有实现这个函数,那么tty内核将把字符附加到循环缓冲区,然后调用start_tx()/stop_tx()来清除数据。如果ch == '0' (__DISABLED_CHAR
)则不传输。不需要锁定,中断情况依赖于调用者。stop_rx:停止接收字符,该端口正在关闭中。在
port->lock
获取的情况下锁定。禁用本地中断,该调用必须不能睡眠。start_rx:开始接收字符。在
port->lock
获取的情况下锁定。禁用本地中断,该调用必须不能睡眠。enable_ms:启用modem状态中断。这个方法可以被多次调用。在调用shutdown()方法时,应该禁用调制解调器状态中断。在``port->lock```获取的情况下锁定。禁用本地中断,该调用必须不能睡眠。
break_ctl:控制中断信号的传输,如果ctl不为零,则应发送断路信号。当ctl=0进行另一个调用时,信号应该终止。调用者持有
tty_port->mutex
锁定。startup:获取中断资源并初始化所有底层驱动程序状态。启用接收端口。该函数不应该激活RTS或DTR;这将通过单独调用
set_mctrl()
来完成。此方法仅在端口初始化打开时调用。shutdown:禁用端口,禁用可能生效的中断条件,并释放中断资源。该函数不应该禁用RTS或DTR;这已经通过对
set_mctrl()
的单独调用完成。一旦调用完成,驱动程序不能访问port->state
。此方法仅在该端口没有更多用户时调用。flush_buffer:刷新所有写缓冲区,重置所有DMA状态,并停止所有正在进行的DMA传输。当清除了
port->state->xmit
循环缓冲区时,将调用该函数。在port->lock
获取的情况下锁定。禁用本地中断,该调用必须不能睡眠。set_termios:更改端口参数,包括字长,奇偶校验,停止位。更新port->read_status_mask和port->ignore_status_mask,以指示接收的事件类型。
set_ldisc:描述线变更通知。在
tty_port->mutex
持有的情况下调用锁定。pm:在指定端口上执行电源管理相关活动。
state
指示由enum uart_pm_state
定义的状态,oldstate
指示前一个状态。该函数不应该用来获取任何资源。该函数将在端口最初打开并最终关闭时被调用,除非端口也是系统控制台。即使没有设置CONFIG_PM,也会发生这种情况。无锁定的情况下调用。type:返回一个指向描述指定端口的字符串常量的指针,或者返回NULL,在这种情况下,字符串'unknown'被替换。无锁定,中断设置依赖于调用者。
release_port:释放端口当前正在使用的所有内存和IO区域资源。没有锁定,中断设置依赖于调用者。
request_port:请求端口所需的任何内存和IO区域资源。如果任何一个请求失败,当这个函数返回时不应该注册任何资源,并且它应该在失败时返回
-EBUSY
。无锁定,中断设置依赖于调用者。config_port:执行
port
所需的自动配置步骤。type
包含所需配置的位掩码。UART_CONFIG_TYPE
表示该端口需要检测和识别。port->type
应该设置为找到的类型,如果没有检测到端口,则设置为PORT_UNKNOWN
。
UART_CONFIG_IRQ
表示中断信号的自动配置,应该使用标准内核自动探测技术进行探测。在端口有内部硬连线中断的平台上(例如,片上系统实现),这是不必要的。无锁定,中断设置依赖于调用者。
verify_port:验证
serinfo
中包含的新串行端口信息是否适合此端口类型。无锁定,中断设置依赖于调用者。ioctl:执行任何端口特定的ioctl。IOCTL命令必须使用<asm/ioctl.h>中找到的标准编号系统来定义。无锁定,中断设置依赖于调用者。
四、常用API总结
//调度写处理
void uart_write_wakeup(struct uart_port *port)
//更新每个端口帧定时信息
void uart_update_timeout(struct uart_port *port, unsigned int cflag, unsigned int baud)
//返回特定端口的波特率
unsigned int uart_get_baud_rate(struct uart_port *port, struct ktermios *termios,const struct ktermios *old, unsigned int min, unsigned int max)
//返回uart的时钟分频系数
unsigned int uart_get_divisor(struct uart_port *port, unsigned int baud)
//获取行状态寄存器信息
int uart_get_lsr_info(struct tty_struct *tty, struct uart_state *state, unsigned int __user *value)
//将控制台(console)消息写入串口
void uart_console_write(struct uart_port *port, const char *s, unsigned int count, void (*putchar)(struct uart_port*, unsigned char))
//获取控制台(console)的端口
struct uart_port *uart_get_console(struct uart_port *ports, int nr, struct console *co)
//解析earlycon选项参数
int uart_parse_earlycon(char *p, unsigned char *iotype, resource_size_t *addr, char **options)
//解析串口baud/parity/bits/flow控制
void uart_parse_options(const char *options, int *baud, int *parity, int *bits, int *flow)
//设置串口控制台参数
int uart_set_options(struct uart_port *port, struct console *co, int baud, int parity, int bits, int flow)
//----------------------------Port/driver注册和移除----------------------------//
//向uart核心层注册一个驱动程序
int uart_register_driver(struct uart_driver *drv)
//从uart核心层移除驱动程序。
//如果底层驱动程序在uart_add_one_port()中注册了端口,则必须通过uart_remove_one_port()删除已经注册的端口。
void uart_unregister_driver(struct uart_driver *drv)
int uart_add_one_port(struct uart_driver *reg, struct uart_port *port);
void uart_remove_one_port(struct uart_driver *reg, struct uart_port *port);
//判断两个端口是否相等。
//此函数可用于确定两个uart_port结构是否描述相同的端口。
bool uart_match_port(const struct uart_port *port1, const struct uart_port *port2)
//电源管理
int uart_suspend_port(struct uart_driver *reg, struct uart_port *port);
int uart_resume_port(struct uart_driver *reg, struct uart_port *port);
- 底层驱动的助手函数
void uart_handle_dcd_change(struct uart_port *uport, bool active)
void uart_handle_cts_change(struct uart_port *uport, bool active)
void uart_insert_char(struct uart_port *port, unsigned int status,unsigned int overrun, u8 ch, u8 flag);
void uart_xchar_out(struct uart_port *uport, int offset);
bool uart_try_toggle_sysrq(struct uart_port *port, u8 ch)
uart_port_tx_limited (port, ch, count, tx_ready, put_char, tx_done)
//uart端口的发送助手函数
uart_port_tx (port, ch, tx_ready, put_char)
五、uart驱动示例剖析
1、原厂设计的uart驱动
有些芯片原厂会针对自家的芯片设计开发出uart驱动,例如nxp的imx6ull,针对该系列的SOC,NXP原厂设计出了一个名为imx.c
的驱动,位于/drivers/tty/serial目录中。该驱动以平台驱动为框架设计:
static struct platform_driver serial_imx_driver = {
.probe = serial_imx_probe,
.remove = serial_imx_remove,
.suspend = serial_imx_suspend,
.resume = serial_imx_resume,
.id_table = imx_uart_devtype,
.driver = {
.name = "imx-uart",
.of_match_table = imx_uart_dt_ids,
},
};
设备树匹配表是:

.probe
对应的serial_imx_probe()
实现如下:
static int serial_imx_probe(struct platform_device *pdev)
{
struct imx_port *sport;
void __iomem *base;
int ret = 0;
struct resource *res;
int txirq, rxirq, rtsirq;
sport = devm_kzalloc(&pdev->dev, sizeof(*sport), GFP_KERNEL);
if (!sport)
return -ENOMEM;
ret = serial_imx_probe_dt(sport, pdev);
if (ret > 0)
serial_imx_probe_pdata(sport, pdev);
else if (ret < 0)
return ret;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))
return PTR_ERR(base);
rxirq = platform_get_irq(pdev, 0);
txirq = platform_get_irq(pdev, 1);
rtsirq = platform_get_irq(pdev, 2);
sport->port.dev = &pdev->dev;
sport->port.mapbase = res->start;
sport->port.membase = base;
sport->port.type = PORT_IMX,
sport->port.iotype = UPIO_MEM;
sport->port.irq = rxirq;
sport->port.fifosize = 32;
sport->port.ops = &imx_pops;
sport->port.rs485_config = imx_rs485_config;
sport->port.rs485.flags =
SER_RS485_RTS_ON_SEND | SER_RS485_RX_DURING_TX;
sport->port.flags = UPF_BOOT_AUTOCONF;
init_timer(&sport->timer);
sport->timer.function = imx_timeout;
sport->timer.data = (unsigned long)sport;
sport->clk_ipg = devm_clk_get(&pdev->dev, "ipg");
if (IS_ERR(sport->clk_ipg)) {
ret = PTR_ERR(sport->clk_ipg);
dev_err(&pdev->dev, "failed to get ipg clk: %d\n", ret);
return ret;
}
sport->clk_per = devm_clk_get(&pdev->dev, "per");
if (IS_ERR(sport->clk_per)) {
ret = PTR_ERR(sport->clk_per);
dev_err(&pdev->dev, "failed to get per clk: %d\n", ret);
return ret;
}
sport->port.uartclk = clk_get_rate(sport->clk_per);
if (sport->port.uartclk > IMX_MODULE_MAX_CLK_RATE) {
ret = clk_set_rate(sport->clk_per, IMX_MODULE_MAX_CLK_RATE);
if (ret < 0) {
dev_err(&pdev->dev, "clk_set_rate() failed\n");
return ret;
}
}
sport->port.uartclk = clk_get_rate(sport->clk_per);
/*
* Allocate the IRQ(s) i.MX1 has three interrupts whereas later
* chips only have one interrupt.
*/
if (txirq > 0) {
ret = devm_request_irq(&pdev->dev, rxirq, imx_rxint, 0,
dev_name(&pdev->dev), sport);
if (ret)
return ret;
ret = devm_request_irq(&pdev->dev, txirq, imx_txint, 0,
dev_name(&pdev->dev), sport);
if (ret)
return ret;
} else {
ret = devm_request_irq(&pdev->dev, rxirq, imx_int, 0,
dev_name(&pdev->dev), sport);
if (ret)
return ret;
}
imx_ports[sport->port.line] = sport;
platform_set_drvdata(pdev, sport);
return uart_add_one_port(&imx_reg, &sport->port);
}
从上述代码可知,依然是常规驱动程序设计的思路。在.probe
中进行的步骤有:
- (1)为描述imx的uart的
struct imx_port
分配内存。 - (2)解析设备树中信息,获取resource。
- (3)获取中断相关配置参数。
- (4)初始化
struct imx_port
中的组成元素。 - (5)uart时钟参数配置和使能。
- (6)使用
uart_add_one_port()
向uart_driver
添加uart_port
,在这里就是向imx_reg
添加sport->port
。imx_reg
是struct uart_driver
的具体实例;sport->port
是struct imx_port
中关联的struct uart_port
。
2、8250标准uart驱动
本小节中的uart驱动指单纯针对一款SOC设计的驱动,该部分驱动一般由芯片原厂提供。除此之外,有些SOC设计公司会基于标准(例如16550A)的uart通信机制设计UART硬件部分。从而软件驱动上也能使用标准的uart驱动进行通信。例如:8250。linux内核中,8250串口通用驱动的主要文件如下:
- drivers/tty/serial/8250/8250_core.c :8250串口驱动核心。
- drivers/tty/serial/8250/8250_dw.c :Synopsis DesignWare 8250串口驱动。
- drivers/tty/serial/8250/8250_dma.c :8250串口DMA驱动。
- drivers/tty/serial/8250/8250_port.c :8250串口端口操作。
- drivers/tty/serial/8250/8250_early.c :8250串口early console驱动。
例如rk3568,对于rk3568关于uart的设备,是使用设备树进行描述:

主机侧对应的驱动程序则是Synopsis DesignWare 8250串口驱动,由drivers/tty/serial/8250/8250_dw.c文件描述。在该驱动程序中,使用platform驱动方案实现驱动的设计:

当设备和驱动匹配后,会执行dw8250_probe()
函数,该函数实现如下:
static int dw8250_probe(struct platform_device *pdev)
{
struct uart_8250_port uart = {}, *up = &uart;
struct uart_port *p = &up->port;
struct device *dev = &pdev->dev;
struct dw8250_data *data;
struct resource *regs;
int irq;
int err;
u32 val;
regs = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!regs)
return dev_err_probe(dev, -EINVAL, "no registers defined\n");
irq = platform_get_irq_optional(pdev, 0);
/* no interrupt -> fall back to polling */
if (irq == -ENXIO)
irq = 0;
if (irq < 0)
return irq;
spin_lock_init(&p->lock);
p->mapbase = regs->start;
p->irq = irq;
p->handle_irq = dw8250_handle_irq;
p->pm = dw8250_do_pm;
p->type = PORT_8250;
p->flags = UPF_SHARE_IRQ | UPF_FIXED_PORT;
p->dev = dev;
p->iotype = UPIO_MEM;
p->serial_in = dw8250_serial_in;
p->serial_out = dw8250_serial_out;
p->set_ldisc = dw8250_set_ldisc;
p->set_termios = dw8250_set_termios;
p->membase = devm_ioremap(dev, regs->start, resource_size(regs));
if (!p->membase)
return -ENOMEM;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->data.dma.fn = dw8250_fallback_dma_filter;
data->pdata = device_get_match_data(p->dev);
p->private_data = &data->data;
data->uart_16550_compatible = device_property_read_bool(dev,
"snps,uart-16550-compatible");
err = device_property_read_u32(dev, "reg-shift", &val);
if (!err)
p->regshift = val;
err = device_property_read_u32(dev, "reg-io-width", &val);
if (!err && val == 4) {
p->iotype = UPIO_MEM32;
p->serial_in = dw8250_serial_in32;
p->serial_out = dw8250_serial_out32;
}
if (device_property_read_bool(dev, "dcd-override")) {
/* Always report DCD as active */
data->msr_mask_on |= UART_MSR_DCD;
data->msr_mask_off |= UART_MSR_DDCD;
}
if (device_property_read_bool(dev, "dsr-override")) {
/* Always report DSR as active */
data->msr_mask_on |= UART_MSR_DSR;
data->msr_mask_off |= UART_MSR_DDSR;
}
if (device_property_read_bool(dev, "cts-override")) {
/* Always report CTS as active */
data->msr_mask_on |= UART_MSR_CTS;
data->msr_mask_off |= UART_MSR_DCTS;
}
if (device_property_read_bool(dev, "ri-override")) {
/* Always report Ring indicator as inactive */
data->msr_mask_off |= UART_MSR_RI;
data->msr_mask_off |= UART_MSR_TERI;
}
/* Always ask for fixed clock rate from a property. */
device_property_read_u32(dev, "clock-frequency", &p->uartclk);
/* If there is separate baudclk, get the rate from it. */
data->clk = devm_clk_get_optional(dev, "baudclk");
if (data->clk == NULL)
data->clk = devm_clk_get_optional(dev, NULL);
if (IS_ERR(data->clk))
return PTR_ERR(data->clk);
INIT_WORK(&data->clk_work, dw8250_clk_work_cb);
data->clk_notifier.notifier_call = dw8250_clk_notifier_cb;
err = clk_prepare_enable(data->clk);
if (err)
return dev_err_probe(dev, err, "could not enable optional baudclk\n");
err = devm_add_action_or_reset(dev, dw8250_clk_disable_unprepare, data->clk);
if (err)
return err;
if (data->clk)
p->uartclk = clk_get_rate(data->clk);
/* If no clock rate is defined, fail. */
if (!p->uartclk)
return dev_err_probe(dev, -EINVAL, "clock rate not defined\n");
data->pclk = devm_clk_get_optional(dev, "apb_pclk");
if (IS_ERR(data->pclk))
return PTR_ERR(data->pclk);
err = clk_prepare_enable(data->pclk);
if (err)
return dev_err_probe(dev, err, "could not enable apb_pclk\n");
err = devm_add_action_or_reset(dev, dw8250_clk_disable_unprepare, data->pclk);
if (err)
return err;
data->rst = devm_reset_control_get_optional_exclusive(dev, NULL);
if (IS_ERR(data->rst))
return PTR_ERR(data->rst);
reset_control_deassert(data->rst);
err = devm_add_action_or_reset(dev, dw8250_reset_control_assert, data->rst);
if (err)
return err;
dw8250_quirks(p, data);
/* If the Busy Functionality is not implemented, don't handle it */
if (data->uart_16550_compatible)
p->handle_irq = NULL;
if (!data->skip_autocfg)
dw8250_setup_port(p);
/* If we have a valid fifosize, try hooking up DMA */
if (p->fifosize) {
data->data.dma.rxconf.src_maxburst = p->fifosize / 4;
data->data.dma.txconf.dst_maxburst = p->fifosize / 4;
up->dma = &data->data.dma;
}
data->data.line = serial8250_register_8250_port(up);
if (data->data.line < 0)
return data->data.line;
/*
* Some platforms may provide a reference clock shared between several
* devices. In this case any clock state change must be known to the
* UART port at least post factum.
*/
if (data->clk) {
err = clk_notifier_register(data->clk, &data->clk_notifier);
if (err)
return dev_err_probe(dev, err, "Failed to set the clock notifier\n");
queue_work(system_unbound_wq, &data->clk_work);
}
platform_set_drvdata(pdev, data);
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
return 0;
}
在上述probe中,主要执行的操作如下:
- (1)从platform_device中提取中
struct resource
。 - (2)设置
struct uart_port
中组成元素的初始化参数值和一些必要的callback。 - (3)读取dev中的参数值。
- (4)设置时钟。
- (5)调用
serial8250_register_8250_port()
注册8250端口。
六、总结
1、本文描述了linux下的uart框架,因uart隶属于tty,故而芯片原厂一般会将与uart相关的驱动放置于/drivers/tty/serial目录中。
2、关于linux下的uart驱动,芯片原厂一般都会去实现,而不用再去开发这一层的驱动。但是uart驱动框架还是值得去了解和学习。本文总结了一些常用的API(以具体linux版本为主),也简要分析了两款芯片的uart驱动程序。
3、基于linux,作为uart的使用者,只需要通过设备树传递uart相关的参数(假如linux支持设备树),这时候uart驱动程序会自动加载运行,向用户空间暴露出设备节点,这时候用户空间就可以方便的使用uart进行通信了。
4、芯片原厂设计的驱动,往往具有兼容性,支持多款同系列或者同类型的芯片!
参考链接
https://docs.kernel.org/driver-api/serial/driver.html?highlight=uart#c.uart_ops