root@linux:~# uname -a
Linux bogon 4.19.115 #25 SMP PREEMPT Fri Oct 15 10:46:25 CST 2021 aarch64 aarch64 aarch64 GNU/Linux
uart@28001000 {
compatible = "arm,pl011", "arm,primecell"; // 看到"arm,primecell"会将platform device转换成smba device
reg = <0x00000000 0x28001000 0x00000000 0x00001000>;
baud = <0x0001c200>;
reg-shift = <0x00000002>;
reg-io-width = <0x00000004>;
interrupts = <0x00000000 0x00000007 0x00000004>;
clocks = <0x00000003 0x00000003>;
clock-names = "uartclk", "apb_pclk";
status = "ok";
};
int __init pl011_init(void)
{
printk(KERN_INFO "Serial: AMBA PL011 UART driver\n");
return amba_driver_register(&pl011_driver);
==>struct amba_driver pl011_driver = {
.drv = {
.name = "uart-pl011",
.pm = &pl011_dev_pm_ops,
.suppress_bind_attrs = IS_BUILTIN(CONFIG_SERIAL_AMBA_PL011),
},
.id_table = pl011_ids,
==>const struct amba_id pl011_ids[] = {
{
.id = 0x00041011,
.mask = 0x000fffff,
.data = &vendor_arm,
==>struct vendor_data vendor_arm = {
.reg_offset = pl011_std_offsets,
==>u16 pl011_std_offsets[REG_ARRAY_SIZE] = {
[REG_DR] = UART01x_DR,
[REG_FR] = UART01x_FR,
[REG_LCRH_RX] = UART011_LCRH,
[REG_LCRH_TX] = UART011_LCRH,
[REG_IBRD] = UART011_IBRD,
[REG_FBRD] = UART011_FBRD,
[REG_CR] = UART011_CR,
[REG_IFLS] = UART011_IFLS,
[REG_IMSC] = UART011_IMSC,
[REG_RIS] = UART011_RIS,
[REG_MIS] = UART011_MIS,
[REG_ICR] = UART011_ICR,
[REG_DMACR] = UART011_DMACR,
};
.ifls = UART011_IFLS_RX4_8|UART011_IFLS_TX4_8,
.fr_busy = UART01x_FR_BUSY,
.fr_dsr = UART01x_FR_DSR,
.fr_cts = UART01x_FR_CTS,
.fr_ri = UART011_FR_RI,
.oversampling = false,
.dma_threshold = false,
.cts_event_workaround = false,
.always_enabled = false,
.fixed_options = false,
.get_fifosize = get_fifosize_arm,
};
},
{
.id = 0x00380802,
.mask = 0x00ffffff,
.data = &vendor_st,
},
{
.id = AMBA_LINUX_ID(0x00, 0x1, 0xffe),
.mask = 0x00ffffff,
.data = &vendor_zte,
},
{ 0, 0 },
};
.probe = pl011_probe, // 后面详细描述
.remove = pl011_remove,
};
=>int amba_driver_register(struct amba_driver *drv)
{
drv->drv.bus = &amba_bustype;
==>struct bus_type amba_bustype = {
.name = "amba",
.dev_groups = amba_dev_groups,
.match = amba_match,
=>int amba_match(struct device *dev, struct device_driver *drv)
{
struct amba_device *pcdev = to_amba_device(dev);
struct amba_driver *pcdrv = to_amba_driver(drv);
/* When driver_override is set, only bind to the matching driver */
if (pcdev->driver_override)
return !strcmp(pcdev->driver_override, drv->name);
return amba_lookup(pcdrv->id_table, pcdev) != NULL;
=>const struct amba_id *amba_lookup(const struct amba_id *table, struct amba_device *dev)
{
int ret = 0;
while (table->mask) {
ret = (dev->periphid & table->mask) == table->id;
if (ret)
break;
table++;
}
return ret ? table : NULL;
}
}
.uevent = amba_uevent,
.dma_configure = platform_dma_configure,
.pm = &amba_pm,
};
#define SETFN(fn) if (drv->fn) drv->drv.fn = amba_##fn
SETFN(probe);
==>drv->drv.probe = amba_probe
/* amba_probe 是 amba_driver和amba_device match后调用,
我们只看到了amba_drivers的注册,而amba_devices如何通过platform_devices生成,
可以通过参考文档1了解
*/
==>int amba_probe(struct device *dev)
{
struct amba_device *pcdev = to_amba_device(dev);
struct amba_driver *pcdrv = to_amba_driver(dev->driver);
const struct amba_id *id = amba_lookup(pcdrv->id_table, pcdev);
int ret;
do {
ret = of_clk_set_defaults(dev->of_node, false);
ret = dev_pm_domain_attach(dev, true);
ret = amba_get_enable_pclk(pcdev);
pm_runtime_get_noresume(dev);
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
ret = pcdrv->probe(pcdev, id);
=>pl011_probe
if (ret == 0)
break;
pm_runtime_disable(dev);
pm_runtime_set_suspended(dev);
pm_runtime_put_noidle(dev);
amba_put_disable_pclk(pcdev);
dev_pm_domain_detach(dev, true);
} while (0);
return ret;
}
SETFN(remove);
SETFN(shutdown);
return driver_register(&drv->drv);
}
}
int pl011_probe(struct amba_device *dev, const struct amba_id *id)
{
struct vendor_data *vendor = id->data; // id 实际为 pl011_ids
struct uart_amba_port *uap = devm_kzalloc(&dev->dev, sizeof(struct uart_amba_port), GFP_KERNEL);
int portnr = pl011_find_free_port();
=>int pl011_find_free_port(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(amba_ports); i++)
if (amba_ports[i] == NULL) // static struct uart_amba_port *amba_ports[UART_NR]; // #define UART_NR 14
return i;
return -EBUSY;
}
uap->clk = devm_clk_get(&dev->dev, NULL);
uap->reg_offset = vendor->reg_offset;
uap->vendor = vendor;
uap->fifosize = vendor->get_fifosize(dev);
uap->port.iotype = vendor->access_32b ? UPIO_MEM32 : UPIO_MEM;
uap->port.irq = dev->irq[0];
uap->port.ops = &amba_pl011_pops;
==>const struct uart_ops amba_pl011_pops = {
.tx_empty = pl011_tx_empty,
.set_mctrl = pl011_set_mctrl,
.get_mctrl = pl011_get_mctrl,
.stop_tx = pl011_stop_tx,
.start_tx = pl011_start_tx,
.stop_rx = pl011_stop_rx,
.enable_ms = pl011_enable_ms,
.break_ctl = pl011_break_ctl,
.startup = pl011_startup,
.shutdown = pl011_shutdown,
.flush_buffer = pl011_dma_flush_buffer,
.set_termios = pl011_set_termios,
.type = pl011_type,
.release_port = pl011_release_port,
.request_port = pl011_request_port,
.config_port = pl011_config_port,
.verify_port = pl011_verify_port,
#ifdef CONFIG_CONSOLE_POLL
.poll_init = pl011_hwinit,
.poll_get_char = pl011_get_poll_char,
.poll_put_char = pl011_put_poll_char,
#endif
};
ret = pl011_setup_port(&dev->dev, uap, &dev->res, portnr);
=>int pl011_setup_port(struct device *dev, struct uart_amba_port *uap, struct resource *mmiobase, int index)
{
void __iomem *base = devm_ioremap_resource(dev, mmiobase);
index = pl011_probe_dt_alias(index, dev);
uap->old_cr = 0;
uap->port.dev = dev;
uap->port.mapbase = mmiobase->start;
uap->port.membase = base;
uap->port.fifosize = uap->fifosize;
uap->port.flags = UPF_BOOT_AUTOCONF;
uap->port.line = index;
amba_ports[index] = uap;
return 0;
}
amba_set_drvdata(dev, uap);
=>#define amba_set_drvdata(d,p) dev_set_drvdata(&d->dev, p)
=>inline void dev_set_drvdata(struct device *dev, void *data)
{
dev->driver_data = data;
}
return pl011_register_port(uap);
=>int pl011_register_port(struct uart_amba_port *uap)
{
int ret;
/* Ensure interrupts from this UART are masked and cleared */
pl011_write(0, uap, REG_IMSC);
pl011_write(0xffff, uap, REG_ICR);
if (!amba_reg.state) { // 驱动只需要注册一次,设备树定义了4个设备,需要初始化4个设备
ret = uart_register_driver(&amba_reg); // 以uart_driver为中心,向上注册
=>int uart_register_driver(struct uart_driver *drv)
{
/*
* Maybe we should be using a slab cache for this, especially if
* we have a large number of ports to handle.
*/
// 按照最大串口数分配, 驱动与设备是1对多的关系
drv->state = kcalloc(drv->nr, sizeof(struct uart_state), GFP_KERNEL);
struct tty_driver *normal = alloc_tty_driver(drv->nr);
drv->tty_driver = normal;
normal->driver_name = drv->driver_name;
normal->name = drv->dev_name;
normal->major = drv->major;
normal->minor_start = drv->minor;
normal->type = TTY_DRIVER_TYPE_SERIAL;
normal->subtype = SERIAL_TYPE_NORMAL;
normal->init_termios = tty_std_termios;
normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;
normal->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
normal->driver_state = drv;
tty_set_operations(normal, &uart_ops);
/*
* Initialise the UART state(s).
*/
for (i = 0; i < drv->nr; i++) {
struct uart_state *state = drv->state + i;
struct tty_port *port = &state->port;
tty_port_init(port);
=>void tty_port_init(struct tty_port *port)
{
memset(port, 0, sizeof(*port));
tty_buffer_init(port);
init_waitqueue_head(&port->open_wait);
init_waitqueue_head(&port->delta_msr_wait);
mutex_init(&port->mutex);
mutex_init(&port->buf_mutex);
spin_lock_init(&port->lock);
port->close_delay = (50 * HZ) / 100;
port->closing_wait = (3000 * HZ) / 100;
port->client_ops = &tty_port_default_client_ops;
==>const struct tty_port_client_operations tty_port_default_client_ops = {
.receive_buf = tty_port_default_receive_buf,
.write_wakeup = tty_port_default_wakeup,
};
kref_init(&port->kref);
}
port->ops = &uart_port_ops;
==>const struct tty_port_operations uart_port_ops = {
.carrier_raised = uart_carrier_raised,
.dtr_rts = uart_dtr_rts,
.activate = uart_port_activate,
.shutdown = uart_tty_port_shutdown,
};
}
retval = tty_register_driver(normal);
=>int tty_register_driver(struct tty_driver *driver)
{
int error;
int i;
dev_t dev;
struct device *d;
if (!driver->major) {
error = alloc_chrdev_region(&dev, driver->minor_start,
driver->num, driver->name);
if (!error) {
driver->major = MAJOR(dev);
driver->minor_start = MINOR(dev);
}
} else {
dev = MKDEV(driver->major, driver->minor_start);
error = register_chrdev_region(dev, driver->num, driver->name);
}
if (driver->flags & TTY_DRIVER_DYNAMIC_ALLOC) { // 也不走这个流程
error = tty_cdev_add(driver, dev, 0, driver->num);
}
mutex_lock(&tty_mutex);
list_add(&driver->tty_drivers, &tty_drivers); // 注册
mutex_unlock(&tty_mutex);
if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) { // 不走这个流程
for (i = 0; i < driver->num; i++) {
d = tty_register_device(driver, i, NULL);
}
}
proc_tty_register_driver(driver);
driver->flags |= TTY_DRIVER_INSTALLED;
return 0;
}
}
}
==>static struct uart_driver amba_reg = {
.owner = THIS_MODULE,
.driver_name = "ttyAMA",
.dev_name = "ttyAMA",
.major = SERIAL_AMBA_MAJOR, // #define SERIAL_AMBA_MAJOR 204
.minor = SERIAL_AMBA_MINOR, // #define SERIAL_AMBA_MINOR 64
.nr = UART_NR,
.cons = AMBA_CONSOLE,
};
ret = uart_add_one_port(&amba_reg, &uap->port);
=>int uart_add_one_port(struct uart_driver *drv, struct uart_port *uport)
{
struct uart_state *state;
struct tty_port *port;
int ret = 0;
struct device *tty_dev;
int num_groups;
state = drv->state + uport->line;
port = &state->port;
mutex_lock(&port_mutex);
mutex_lock(&port->mutex);
/* Link the port to the driver state table and vice versa */
atomic_set(&state->refcount, 1);
init_waitqueue_head(&state->remove_wait);
state->uart_port = uport;
uport->state = state;
state->pm_state = UART_PM_STATE_UNDEFINED;
uport->cons = drv->cons;
uport->minor = drv->tty_driver->minor_start + uport->line;
uport->name = kasprintf(GFP_KERNEL, "%s%d", drv->dev_name, drv->tty_driver->name_base + uport->line);
/*
* If this port is a console, then the spinlock is already
* initialised.
*/
if (!(uart_console(uport) && (uport->cons->flags & CON_ENABLED))) {
spin_lock_init(&uport->lock);
lockdep_set_class(&uport->lock, &port_lock_key);
}
=>#define uart_console(port) ((port)->cons && (port)->cons->index == (port)->line)
if (uport->cons && uport->dev)
of_console_check(uport->dev->of_node, uport->cons->name, uport->line);
tty_port_link_device(port, drv->tty_driver, uport->line);
=>void tty_port_link_device(struct tty_port *port, struct tty_driver *driver, unsigned index)
{
driver->ports[index] = port;
}
uart_configure_port(drv, state, uport);
=>void uart_configure_port(struct uart_driver *drv, struct uart_state *state, struct uart_port *port)
{
unsigned int flags = 0;
/*
* Now do the auto configuration stuff. Note that config_port
* is expected to claim the resources and map the port for us.
*/
if (port->flags & UPF_AUTO_IRQ)
flags |= UART_CONFIG_IRQ;
if (port->flags & UPF_BOOT_AUTOCONF) {
if (!(port->flags & UPF_FIXED_TYPE)) {
port->type = PORT_UNKNOWN;
flags |= UART_CONFIG_TYPE;
}
port->ops->config_port(port, flags);
}
if (port->type != PORT_UNKNOWN) {
unsigned long flags;
uart_report_port(drv, port);
/* Power up port for set_mctrl() */
uart_change_pm(state, UART_PM_STATE_ON);
/*
* Ensure that the modem control lines are de-activated.
* keep the DTR setting that is set in uart_set_options()
* We probably don't need a spinlock around this, but
*/
spin_lock_irqsave(&port->lock, flags);
port->ops->set_mctrl(port, port->mctrl & TIOCM_DTR);
spin_unlock_irqrestore(&port->lock, flags);
/*
* If this driver supports console, and it hasn't been
* successfully registered yet, try to re-register it.
* It may be that the port was not available.
*/
if (port->cons && !(port->cons->flags & CON_ENABLED))
register_console(port->cons); // 一个大函数
/*
* Power down all ports by default, except the
* console if we have one.
*/
if (!uart_console(port))
uart_change_pm(state, UART_PM_STATE_OFF);
}
}
port->console = uart_console(uport);
num_groups = 2;
if (uport->attr_group)
num_groups++;
uport->tty_groups = kcalloc(num_groups, sizeof(*uport->tty_groups), GFP_KERNEL);
uport->tty_groups[0] = &tty_dev_attr_group;
if (uport->attr_group)
uport->tty_groups[1] = uport->attr_group;
/*
* Register the port whether it's detected or not. This allows
* setserial to be used to alter this port's parameters.
*/
tty_dev = tty_port_register_device_attr_serdev(port, drv->tty_driver, uport->line, uport->dev, port, uport->tty_groups);
=>struct device *tty_port_register_device_attr_serdev(struct tty_port *port,
struct tty_driver *driver, unsigned index,
struct device *device, void *drvdata,
const struct attribute_group **attr_grp)
{
struct device *dev;
tty_port_link_device(port, driver, index);
=>void tty_port_link_device(struct tty_port *port, struct tty_driver *driver, unsigned index)
{
driver->ports[index] = port;
}
dev = serdev_tty_port_register(port, device, driver, index);
=>struct device *serdev_tty_port_register(struct tty_port *port, struct device *parent, struct tty_driver *drv, int idx)
{
struct serdev_controller *ctrl = serdev_controller_alloc(parent, sizeof(struct serport));
struct serport *serport = serdev_controller_get_drvdata(ctrl);
int ret;
serport->port = port;
serport->tty_idx = idx;
serport->tty_drv = drv;
ctrl->ops = &ctrl_ops;
==>const struct serdev_controller_ops ctrl_ops = {
.write_buf = ttyport_write_buf,
.write_flush = ttyport_write_flush,
.write_room = ttyport_write_room,
.open = ttyport_open,
.close = ttyport_close,
.set_flow_control = ttyport_set_flow_control,
.set_parity = ttyport_set_parity,
.set_baudrate = ttyport_set_baudrate,
.wait_until_sent = ttyport_wait_until_sent,
.get_tiocm = ttyport_get_tiocm,
.set_tiocm = ttyport_set_tiocm,
};
port->client_ops = &client_ops;
==>const struct tty_port_client_operations client_ops = {
.receive_buf = ttyport_receive_buf,
.write_wakeup = ttyport_write_wakeup,
};
port->client_data = ctrl;
ret = serdev_controller_add(ctrl);
dev_info(&ctrl->dev, "tty port %s%d registered\n", drv->name, idx);
return &ctrl->dev;
}
return tty_register_device_attr(driver, index, device, drvdata, attr_grp);
=>struct device *tty_register_device_attr(struct tty_driver *driver,
unsigned index, struct device *device,
void *drvdata,
const struct attribute_group **attr_grp)
{
char name[64];
dev_t devt = MKDEV(driver->major, driver->minor_start) + index;
struct ktermios *tp;
struct device *dev;
int retval;
if (driver->type == TTY_DRIVER_TYPE_PTY)
pty_line_name(driver, index, name);
else
tty_line_name(driver, index, name);
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
dev->devt = devt;
dev->class = tty_class;
dev->parent = device;
dev->release = tty_device_create_release;
dev_set_name(dev, "%s", name);
dev->groups = attr_grp;
dev_set_drvdata(dev, drvdata);
dev_set_uevent_suppress(dev, 1);
retval = device_register(dev);
if (!(driver->flags & TTY_DRIVER_DYNAMIC_ALLOC)) { // 走这个流程
/*
* Free any saved termios data so that the termios state is
* reset when reusing a minor number.
*/
tp = driver->termios[index];
if (tp) {
driver->termios[index] = NULL;
kfree(tp);
}
retval = tty_cdev_add(driver, devt, index, 1); // 注册设备节点
if (retval)
goto err_del;
}
dev_set_uevent_suppress(dev, 0);
kobject_uevent(&dev->kobj, KOBJ_ADD); // 用户态创建设备节点并且加入到sys文件系统
return dev;
}
}
if (likely(!IS_ERR(tty_dev))) {
device_set_wakeup_capable(tty_dev, 1);
}
/*
* Ensure UPF_DEAD is not set.
*/
uport->flags &= ~UPF_DEAD;
out:
mutex_unlock(&port->mutex);
mutex_unlock(&port_mutex);
return ret;
}
return ret;
}
}
参考文档
1 linux设备驱动(20)设备树详解4-kernel解析dts
https://www.cnblogs.com/xinghuo123/p/12977093.html
2 tty驱动框架分析
https://blog.csdn.net/qq_37600027/article/details/84504042
3 Linux设备模型之tty驱动架构分析
https://blog.csdn.net/pan0755/article/details/51693178/
4 浅析usb转serial串口设备在linux内核中枚举创建及生成tty设备的全过程
http://blog.chinaunix.net/uid-23869969-id-2654949.html
5 linux的串口驱动分析
https://www.cnblogs.com/chd-zhangbo/p/5410336.html
6 Linux驱动之串口(UART)
https://www.cnblogs.com/big-devil/p/8590050.html
7 linux设备模型之uart驱动架构分析
http://www.eepw.com.cn/article/201610/305916.htm
8 ARM平台AMBA总线uart驱动和console初始化
https://blog.csdn.net/vince_/article/details/104660642
9 linux pl011串口简述
https://blog.csdn.net/flfihpv259/article/details/53759069
10 ARM AMBA 外围设备 的datasheet
https://www.cnblogs.com/pengdonglin137/articles/12179834.html
11 PrimeCell UART (PL011) Technical Reference Manual
https://developer.arm.com/documentation/ddi0183/g/Babhjagh