[RFC PATCH 1/3] UART: Add UART subsystem as a bus.

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Tranditional UARTs are used as communication pipes between the hosts
while the modern computing systems equipped with HSU (High Speed UARTs)
would connect on-board target devices using the UART ports. The role of
the UART controllers are changed from the communication facility to the
platform bus.

In the recent ACPI 5.0 specification updates, firmwares are provided the
possibilities to enumerate the UART target devices known to the platform
vendors.
Thus there is the needs for enumerating the UART target devices:
1. hotplug uevent
2. serial configuration
Currently, only serial cards on the specific bus (ex. PCMCIA) can be
enumerated and userspace can obtain the hotplug event of the UART target
devices. Linux kernel is lack of an overall enumeration mechanism for
UART target devices.
In order to send uevent, a device need to be a class device or a bus
device. This patch introduces a bus_type subsystem to manage the new
UART target device type for the purpose of the possible future
extensions.
When the UART target devices are created, userspace uevent rules can
pass the creation details to the userspace driver managers
(ex. hciattach). Here is an example of the uevents and exported
attributes of the new type of devices:

Test DSDT (dummy UART host adapter INTF000 and target device INTF001):
  Device (UA00)
  {
      Name (_HID, "INTF000")  // _HID: Hardware ID
      Name (RBUF, ResourceTemplate ()
      {
          Memory32Fixed (ReadWrite,
              0x00000000,         // Address Base
              0x00001000)         // Address Length
      })
      Method (_CRS, 0, NotSerialized)  // _CRS: Current Resource Settings
      {
          Return (RBUF)
      }
      Method (_STA, 0, NotSerialized)  // _STA: Status
      {
          Return (0x0F)
      }
      Device (BTH0)
      {
          Name (_HID, "INTF001")  // _HID: Hardware ID
          Method (_CRS, 0, NotSerialized)  // _CRS: Current Resource Settings
          {
              Name (UBUF, ResourceTemplate ()
              {
                  UartSerialBus (0x0001C200, DataBitsEight, StopBitsOne,
                      0xC0, LittleEndian, ParityTypeNone, FlowControlHardware,
                      0x0020, 0x0020, "\\_SB.PCI0.UA00",
                      0x00, ResourceConsumer, ,
                      )
              })
              Return (UBUF)
          }
          Method (_STA, 0, NotSerialized)  // _STA: Status
          {
              Return (0x0F)
          }
      }
  }

uevent and environments:
KERNEL[252.443458] add      /bus/uart (bus)
ACTION=add
DEVPATH=/bus/uart
SEQNUM=1142
SUBSYSTEM=bus

KERNEL[268.491709] add      /devices/platform/INTF000:00/INTF001:00 (uart)
ACTION=add
DEVPATH=/devices/platform/INTF000:00/INTF001:00
DEVTYPE=uart_device
MODALIAS=uart:INTF001:00
SEQNUM=1144
SUBSYSTEM=uart

kobject attribute files:
# cat /sys/bus/uart/devices/INTF001:00/modalias
uart:INTF001:00
# cat /sys/bus/uart/devices/INTF001:00/tty_dev
ttyS0
# cat /sys/bus/uart/devices/INTF001:00/tty_attrs
115200 8N0 HW
# cat /sys/bus/uart/devices/INTF001:00/modem_lines
LE:RTS,CTS,

kobject sysfs links:
# ls -l /sys/bus/uart/devices
INTF001:00 -> ../../../devices/platform/INTF000:00/INTF001:00
# ls -l /sys/devices/platform/INTF000:00/INTF001:00
subsystem -> ../../../../bus/uart
host_node -> ../tty/ttyS0
# ls -l /sys/devices/platform/INTF000:00/tty/ttyS0
target_node -> ../../INTF001:00

Signed-off-by: Lv Zheng <lv.zheng@xxxxxxxxx>
---
 drivers/tty/serial/Makefile     |    2 +-
 drivers/tty/serial/serial_bus.c |  409 +++++++++++++++++++++++++++++++++++++++
 include/linux/mod_devicetable.h |    5 +
 include/linux/serial_core.h     |   56 ++++++
 4 files changed, 471 insertions(+), 1 deletion(-)
 create mode 100644 drivers/tty/serial/serial_bus.c

diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile
index 4f694da..4400521 100644
--- a/drivers/tty/serial/Makefile
+++ b/drivers/tty/serial/Makefile
@@ -2,7 +2,7 @@
 # Makefile for the kernel serial device drivers.
 #
 
-obj-$(CONFIG_SERIAL_CORE) += serial_core.o
+obj-$(CONFIG_SERIAL_CORE) += serial_core.o serial_bus.o
 obj-$(CONFIG_SERIAL_21285) += 21285.o
 
 # These Sparc drivers have to appear before others such as 8250
diff --git a/drivers/tty/serial/serial_bus.c b/drivers/tty/serial/serial_bus.c
new file mode 100644
index 0000000..a6674da
--- /dev/null
+++ b/drivers/tty/serial/serial_bus.c
@@ -0,0 +1,409 @@
+/*
+ * serial_bus.c - UART bus implementation
+ *
+ * Copyright (c) 2012, Intel Corporation
+ * Author: Lv Zheng <lv.zheng@xxxxxxxxx>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2
+ * of the License.
+ */
+
+/*
+ * Tranditional UARTs are used as communication pipes between the hosts
+ * while the modern computing systems equipped with HSU (High Speed UARTs)
+ * would connect on-board target devices using the UART ports. The role of
+ * the UART controllers are changed from the communication facility to the
+ * platform bus.
+ *
+ * UART target devices are created in the kernel as struct uart_device.
+ * It is defined for the following purposes:
+ * 1. Sending hotplug notifications to the userspace
+ * 2. Exporting serial configuration parameters to the userspace
+ * 3. Allowing target device based PM to be added easily
+ *
+ */
+
+#include <linux/tty.h>
+#include <linux/serial.h>
+#include <linux/serial_core.h>
+#include <linux/device.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+
+
+struct uart_match {
+	dev_t devt;
+};
+
+static int do_uart_tty_find(struct device *dev, void *data)
+{
+	struct uart_match *match = data;
+	return dev->devt == match->devt;
+}
+
+/**
+ * uart_tty_find - find the associated TTY device for the UART target
+ *		   device
+ * @drv: the low level UART driver
+ * @dev: the parent physical device for the TTY devices
+ * @line: the line number of the UART target device
+ *
+ * Return a matching TTY device for the UART target device.
+ */
+struct device *uart_tty_find(struct uart_driver *drv,
+			     struct device *dev, unsigned int line)
+{
+	struct uart_match match;
+
+	match.devt = MKDEV(drv->tty_driver->major,
+			   drv->tty_driver->minor_start + line);
+
+	return device_find_child(dev, &match, do_uart_tty_find);
+}
+EXPORT_SYMBOL_GPL(uart_tty_find);
+
+/**
+ * uart_tty_name - get the associated TTY device name for the UART target
+ *		   device
+ * @drv: the low level UART driver
+ * @line: the line number of the UART target device
+ * @p: pointer to the buffer containing the returned name
+ *
+ * Return a TTY device name for the UART target device.
+ */
+void uart_tty_name(struct uart_driver *driver, int line, char *p)
+{
+	struct tty_driver *drv;
+
+	BUG_ON(!driver || !driver->tty_driver || !p);
+	drv = driver->tty_driver;
+
+	if (drv->flags & TTY_DRIVER_UNNUMBERED_NODE)
+		strcpy(p, drv->name);
+	else
+		sprintf(p, "%s%d", drv->name, line + drv->name_base);
+}
+EXPORT_SYMBOL_GPL(uart_tty_name);
+
+static ssize_t uart_dev_show_name(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct uart_device *udev = to_uart_device(dev);
+	return sprintf(buf, "%s\n", udev->name);
+}
+
+static ssize_t uart_dev_show_modalias(struct device *dev,
+				      struct device_attribute *attr, char *buf)
+{
+	struct uart_device *udev = to_uart_device(dev);
+	return sprintf(buf, "%s%s\n", UART_MODULE_PREFIX, udev->name);
+}
+
+static ssize_t uart_dev_show_tty_dev(struct device *dev,
+				     struct device_attribute *attr, char *buf)
+{
+	struct uart_device *udev = to_uart_device(dev);
+	return sprintf(buf, "%s\n", udev->tty_name);
+}
+
+static ssize_t uart_dev_show_tty_attrs(struct device *dev,
+				       struct device_attribute *attr,
+				       char *buf)
+{
+	struct uart_device *udev = to_uart_device(dev);
+	int len = 0;
+
+	/* baud rate */
+	len += sprintf(buf+len, "%d ", udev->baud);
+
+	/* data bits */
+	switch (udev->cflag & CSIZE) {
+	case CS5:
+		len += sprintf(buf+len, "5");
+		break;
+	case CS6:
+		len += sprintf(buf+len, "6");
+		break;
+	case CS7:
+		len += sprintf(buf+len, "7");
+		break;
+	case CS8:
+	default:
+		len += sprintf(buf+len, "8");
+		break;
+	}
+
+	/* parity */
+	if (udev->cflag & PARODD)
+		len += sprintf(buf+len, "O");
+	else if (udev->cflag & PARENB)
+		len += sprintf(buf+len, "E");
+	else
+		len += sprintf(buf+len, "N");
+
+	/* stop bits */
+	len += sprintf(buf+len, "%d", udev->cflag & CSTOPB ? 1 : 0);
+
+	/* HW/SW control */
+	if (udev->cflag & CRTSCTS)
+		len += sprintf(buf+len, " HW");
+	if ((udev->iflag & (IXON|IXOFF|IXANY)) != 0)
+		len += sprintf(buf+len, " SW");
+
+	len += sprintf(buf+len, "\n");
+	return len;
+}
+
+static ssize_t uart_dev_show_modem_lines(struct device *dev,
+					 struct device_attribute *attr,
+					 char *buf)
+{
+	struct uart_device *udev = to_uart_device(dev);
+	int len = 0;
+
+	/* endian */
+	if (udev->mctrl & TIOCM_LE)
+		len += sprintf(buf+len, "LE:");
+	else
+		len += sprintf(buf+len, "BE:");
+
+	/* terminal lines */
+	if (udev->mctrl & TIOCM_DTR)
+		len += sprintf(buf+len, "DTR,");
+	if (udev->mctrl & TIOCM_RTS)
+		len += sprintf(buf+len, "RTS,");
+
+	/* modem lines */
+	if (udev->mctrl & TIOCM_CTS)
+		len += sprintf(buf+len, "CTS,");
+	if (udev->mctrl & TIOCM_CAR)
+		len += sprintf(buf+len, "CAR,");
+	if (udev->mctrl & TIOCM_RNG)
+		len += sprintf(buf+len, "RNG,");
+	if (udev->mctrl & TIOCM_DSR)
+		len += sprintf(buf+len, "DSR,");
+
+	len += sprintf(buf+len, "\n");
+	return len;
+}
+
+static DEVICE_ATTR(name, S_IRUGO, uart_dev_show_name, NULL);
+static DEVICE_ATTR(modalias, S_IRUGO, uart_dev_show_modalias, NULL);
+static DEVICE_ATTR(tty_dev, S_IRUGO, uart_dev_show_tty_dev, NULL);
+static DEVICE_ATTR(tty_attrs, S_IRUGO, uart_dev_show_tty_attrs, NULL);
+static DEVICE_ATTR(modem_lines, S_IRUGO, uart_dev_show_modem_lines, NULL);
+
+static struct attribute *uart_dev_attrs[] = {
+	&dev_attr_name.attr,
+	/* coldplug: modprobe $(cat .../modalias) */
+	&dev_attr_modalias.attr,
+	&dev_attr_tty_dev.attr,
+	&dev_attr_tty_attrs.attr,
+	&dev_attr_modem_lines.attr,
+	NULL,
+};
+
+static struct attribute_group uart_dev_attr_group = {
+	.attrs	= uart_dev_attrs,
+};
+
+static const struct attribute_group *uart_dev_attr_groups[] = {
+	&uart_dev_attr_group,
+	NULL,
+};
+
+#ifdef CONFIG_HOTPLUG
+static int uart_device_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+	struct uart_device *udev = to_uart_device(dev);
+
+	if (add_uevent_var(env, "MODALIAS=%s%s",
+			   UART_MODULE_PREFIX, udev->name))
+		return -ENOMEM;
+
+	dev_dbg(dev, "uevent\n");
+	return 0;
+}
+#else
+#define uart_device_uevent	NULL
+#endif
+
+static void uart_device_release(struct device *dev)
+{
+	struct uart_device *udev = to_uart_device(dev);
+
+	put_device(udev->tty);
+	kfree(udev);
+}
+
+struct device_type uart_device_type = {
+	.name		= "uart_device",
+	.groups		= uart_dev_attr_groups,
+	.uevent		= uart_device_uevent,
+	.release	= uart_device_release,
+};
+EXPORT_SYMBOL_GPL(uart_device_type);
+
+/**
+ * uart_register_device - instantiate a UART device
+ * @drv: low level driver structure
+ * @adap: pointer to the UART target device manager
+ * @device: the physical device communicating with the target device
+ * @info: describes one UART target
+ *
+ * Create a UART target device.
+ *
+ * This returns the new UART target device, which may be saved for later use
+ * with uart_unregister_device; or NULL to indicate an error.
+ */
+struct uart_device *uart_register_device(struct uart_driver *drv,
+					 struct klist *adap,
+					 struct device *dev,
+					 struct uart_board_info const *info)
+{
+	struct uart_device *udev;
+	struct device *tty;
+	int status;
+
+	BUG_ON(info->line >= drv->nr);
+	BUG_ON((!adap && !dev) || !drv);
+
+	udev = kzalloc(sizeof(struct uart_device), GFP_KERNEL);
+	if (!udev)
+		return NULL;
+
+	strlcpy(udev->name, info->type, sizeof(udev->name));
+
+	udev->baud = info->baud;
+	udev->cflag = info->cflag;
+	udev->iflag = info->iflag;
+	udev->mctrl = info->mctrl;
+
+	udev->dev.parent = dev;
+	udev->dev.bus = &uart_bus_type;
+	udev->dev.type = &uart_device_type;
+
+	tty = uart_tty_find(drv, dev, info->line);
+	if (!tty) {
+		dev_err(&udev->dev, "Cannot find associated tty device.\n");
+		goto fail;
+	}
+	udev->tty = get_device(tty);
+	uart_tty_name(drv, info->line, udev->tty_name);
+
+	dev_set_name(&udev->dev, "%s", udev->name);
+	status = device_register(&udev->dev);
+	if (status) {
+		dev_err(&udev->dev, "Failed to register uart device.\n");
+		goto fail2;
+	}
+
+	status = sysfs_create_link(&tty->kobj, &udev->dev.kobj, "target_node");
+	status = sysfs_create_link(&udev->dev.kobj, &tty->kobj, "host_node");
+
+	if (adap) {
+		udev->adap = adap;
+		klist_add_tail(&udev->klist_parent, adap);
+	}
+
+	return udev;
+
+fail2:
+	put_device(tty);
+fail:
+	kfree(udev);
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(uart_register_device);
+
+/**
+ * uart_unregister_device - unregister a UART device
+ * @device: value returned from uart_register_device()
+ *
+ * Reverse effect of uart_register_device().
+ */
+void uart_unregister_device(struct uart_device *udev)
+{
+	if (udev->adap)
+		klist_del(&udev->klist_parent);
+	sysfs_remove_link(&udev->dev.kobj, "host_node");
+	if (udev->tty)
+		sysfs_remove_link(&udev->tty->kobj, "target_node");
+	device_unregister(&udev->dev);
+}
+EXPORT_SYMBOL_GPL(uart_unreigster_device);
+
+static void klist_uart_get(struct klist_node *n)
+{
+	struct uart_device *udev = uart_device_from_parent(n);
+	get_device(&udev->dev);
+}
+
+static void klist_uart_put(struct klist_node *n)
+{
+	struct uart_device *udev = uart_device_from_parent(n);
+	put_device(&udev->dev);
+}
+
+static struct uart_device *uart_next_device(struct klist_iter *i)
+{
+	struct klist_node *n = klist_next(i);
+	return n ? uart_device_from_parent(n) : NULL;
+}
+
+/**
+ * uart_add_adapter - register a UART adapter
+ * @adap: pointer to the device manager
+ *
+ * Initialize a UART target device manager - adapter.
+ */
+int uart_add_adapter(struct klist *adap)
+{
+	klist_init(adap, klist_uart_get, klist_uart_put);
+	return 0;
+}
+EXPORT_SYMBOL_GPL(uart_add_adapter);
+
+/**
+ * uart_del_adapter - unregister a UART adapter
+ * @adap: pointer to the device manager
+ *
+ * Reverse effect of uart_add_adapter().
+ */
+void uart_del_adapter(struct klist *adap)
+{
+	struct klist_iter i;
+	struct uart_device *udev;
+
+	klist_iter_init(adap, &i);
+	while ((udev = uart_next_device(&i)))
+		uart_unregister_device(udev);
+	klist_iter_exit(&i);
+}
+EXPORT_SYMBOL_GPL(uart_del_adapter);
+
+struct bus_type uart_bus_type = {
+	.name		= "uart",
+};
+EXPORT_SYMBOL_GPL(uart_bus_type);
+
+static int __init uart_bus_init(void)
+{
+	int retval;
+
+	retval = bus_register(&uart_bus_type);
+	if (retval)
+		return retval;
+
+	return 0;
+}
+
+static void __exit uart_bus_exit(void)
+{
+	bus_unregister(&uart_bus_type);
+}
+
+subsys_initcall(uart_bus_init);
+module_exit(uart_bus_exit);
diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h
index fed3def..28df140 100644
--- a/include/linux/mod_devicetable.h
+++ b/include/linux/mod_devicetable.h
@@ -455,6 +455,11 @@ struct spi_device_id {
 			__attribute__((aligned(sizeof(kernel_ulong_t))));
 };
 
+/* uart */
+
+#define UART_NAME_SIZE	32
+#define UART_MODULE_PREFIX "uart:"
+
 /* dmi */
 enum dmi_field {
 	DMI_NONE,
diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h
index 3c43022..422f8cc4 100644
--- a/include/linux/serial_core.h
+++ b/include/linux/serial_core.h
@@ -31,10 +31,13 @@
 #include <linux/sysrq.h>
 #include <linux/pps_kernel.h>
 #include <uapi/linux/serial_core.h>
+#include <linux/mod_devicetable.h>
 
 struct uart_port;
+struct uart_device;
 struct serial_struct;
 struct device;
+struct uart_board_info;
 
 /*
  * This structure describes all the operations that can be
@@ -367,4 +370,57 @@ static inline int uart_handle_break(struct uart_port *port)
 					 (cflag) & CRTSCTS || \
 					 !((cflag) & CLOCAL))
 
+/*
+ * UART Bus
+ */
+struct uart_board_info {
+	char type[UART_NAME_SIZE];
+	unsigned int line;	/* port index */
+	unsigned int cflag;	/* termio cflag */
+	unsigned int iflag;	/* termio iflag */
+	unsigned int mctrl;	/* modem ctrl settings */
+	unsigned int baud;
+	int irq;
+};
+
+extern struct bus_type uart_bus_type;
+
+int uart_add_adapter(struct klist *adap);
+void uart_del_adapter(struct klist *adap);
+
+struct uart_device {
+	char			name[UART_NAME_SIZE];
+	char			tty_name[64];
+	unsigned int		cflag;	/* termio cflag */
+	unsigned int		iflag;	/* termio iflag */
+	unsigned int		mctrl;	/* modem ctrl settings */
+	unsigned int		baud;
+	struct device		dev;
+	struct device		*tty;
+	struct klist_node	klist_parent;
+	struct klist		*adap;	/* set for multi-port adapter */
+};
+
+extern struct device_type uart_device_type;
+
+#define is_uart_device(d) ((d) && (d)->type == &uart_device_type)
+#define to_uart_device(d) container_of(d, struct uart_device, dev)
+#define uart_device_from_parent(n)	\
+	container_of(n, struct uart_device, klist_parent)
+
+static inline struct uart_device *uart_verify_device(struct device *dev)
+{
+	return is_uart_device(dev) ? to_uart_device(dev) : NULL;
+}
+
+struct uart_device *uart_register_device(struct uart_driver *drv,
+					 struct klist *adap,
+					 struct device *dev,
+					 struct uart_board_info const *info);
+void uart_unregister_device(struct uart_device *udev);
+
+struct device *uart_tty_find(struct uart_driver *drv,
+			     struct device *dev, unsigned int line);
+void uart_tty_name(struct uart_driver *driver, int index, char *p);
+
 #endif /* LINUX_SERIAL_CORE_H */
-- 
1.7.10

--
To unsubscribe from this list: send the line "unsubscribe linux-acpi" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at  http://vger.kernel.org/majordomo-info.html


[Index of Archives]     [Linux IBM ACPI]     [Linux Power Management]     [Linux Kernel]     [Linux Laptop]     [Kernel Newbies]     [Share Photos]     [Security]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Samba]     [Video 4 Linux]     [Device Mapper]     [Linux Resources]

  Powered by Linux