慢慢欣赏linux之串口驱动代码分析 - 基于arm64 4.19.x版本

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

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值