linux的uart驱动示例剖析

linux源码相关文件:
serial-core.c
include/linux/serial_core.h
unsetunset
一、底层串行硬件驱动程序unset
底层串行硬件的驱动程序负责向serial核心驱动程序提供由struct uart_port定义的端口信息和一组由struct uart_ops定义的控制方法,底层驱动程序还负责处理端口的中断,并提供对控制台的支持。
unsetunset
二、console支持unset
serial核心提供了一些助手函数:
uart_get_console()识别正确的端口结构。
uart_parse_options()解析命令行参数。
uart_console_write()用于执行逐字符写入,将换行符转换为crlf序列。在驱动程序编写的时候建议使用此函数,而不是实现新的写入接口。
unsetunset
三、锁支持
unset
底层硬件驱动程序负责使用port->lock执行必要的锁定。支持两把锁:一个是端口自旋锁,另一个是overall信号量。从uart核心驱动程序的角度来看,port->lock用于锁定以下的数据:
port->mctrlport->icountport->state->xmit.head (circ_buf->head)port->state->xmit.tail (circ_buf->tail)
底层驱动程序可以自由地使用该锁来实现额外的锁定,port_mutex互斥量用于防止在不适当的时间添加、删除或重新配置端口。
unsetunset
四、核心数据结构unset
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命令必须使用中找到的标准编号系统来定义。无锁定,中断设置依赖于调用者。
unsetunset
四、常用api总结unset
//调度写处理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)  
unsetunset
五、uart驱动示例剖析unset
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 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, 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, 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 dev, clk_set_rate() failed);   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); irq = platform_get_irq_optional(pdev, 0); /* no interrupt -> fall back to polling */ if (irq == -enxio)  irq = 0; if (irq 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); 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); 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); 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 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);  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端口。
unsetunset
六、总结unset
1、本文描述了linux下的uart框架,因uart隶属于tty,故而芯片原厂一般会将与uart相关的驱动放置于/drivers/tty/serial目录中。
2、关于linux下的uart驱动,芯片原厂一般都会去实现,而不用再去开发这一层的驱动。但是uart驱动框架还是值得去了解和学习。本文总结了一些常用的api(以具体linux版本为主),也简要分析了两款芯片的uart驱动程序。
3、基于linux,作为uart的使用者,只需要通过设备树传递uart相关的参数(假如linux支持设备树),这时候uart驱动程序会自动加载运行,向用户空间暴露出设备节点,这时候用户空间就可以方便的使用uart进行通信了。
4、芯片原厂设计的驱动,往往具有兼容性,支持多款同系列或者同类型的芯片!


物联网可以如何去增强ERP软件
人工智能正在兴起新一轮的产业变革
弘凯光电发表一系列全彩自带IC LED
澎湃微PT32x033系列|血糖仪专题技术文章连载_04
云天励飞为园区智慧化升级提供了一站式的产品和方案
linux的uart驱动示例剖析
关于FPGA上HBM 425GB/s内存带宽的实测
鼎阳SDS1102X示波器拆解分析
富士通在不同业务场景下的具体变革
扫地狗S600扫地机器人评测 支持自动回充不用担心续航问题
ES32M0502系列电机控制MCU新品发布
TMI6283H解决阀门/小电机供电电流和电压瞬间冲击的问题
九联助力OpenHarmony人才生态建设
高通宣布与环旭电子及华硕合作 将在巴西建新工厂
新能源汽车关键部件的技术发展趋势详解
氧气压力表与普通压力表的区别
直流电源使用的新概念: 低纹波且操作方便的数字直流电源
选购固定式条码扫描器需要注意哪几个方面?
阻容吸收器的作用分析
PCB拼板规范