[RFC PATCH v4 1/3] TTY: Add TTY slave enumeration support.

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

 



In the recent ACPI 5.0 specification updates, firmwares are provided the
possibilities to enumerate the UART slave devices known to the platform
vendors.
There are the needs in Linux to utilize the benefits:
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 slave devices.
In order to send uevent, a device need to be a class device or a bus
device. This patch introduces a tty_enum bus since the enumerated slave
devices are expected to be physical devices.
When the UART slave devices are created, userspace uevent rules can
pass the creation details to the userspace driver managers
(ex. hciattach), then the device managers can read hardware IDs and the
serial configurations from the exported device attributes to match and
configure a userspace TTY device driver.
The created slave devices will be automatically unregistered when the
associated TTY ports are destructed.

Signed-off-by: Lv Zheng <lv.zheng@xxxxxxxxx>
---
 drivers/tty/Kconfig              |    3 +
 drivers/tty/Makefile             |    1 +
 drivers/tty/serial/Kconfig       |    1 +
 drivers/tty/serial/serial_core.c |    2 +-
 drivers/tty/tty_enum.c           |  356 ++++++++++++++++++++++++++++++++++++++
 drivers/tty/tty_port.c           |   42 ++++-
 include/linux/mod_devicetable.h  |    6 +
 include/linux/tty.h              |  104 +++++++++++
 8 files changed, 511 insertions(+), 4 deletions(-)
 create mode 100644 drivers/tty/tty_enum.c

diff --git a/drivers/tty/Kconfig b/drivers/tty/Kconfig
index 0ecf22b..e0ef9db 100644
--- a/drivers/tty/Kconfig
+++ b/drivers/tty/Kconfig
@@ -151,6 +151,9 @@ config LEGACY_PTY_COUNT
 	  When not in use, each legacy PTY occupies 12 bytes on 32-bit
 	  architectures and 24 bytes on 64-bit architectures.
 
+config TTY_ENUM
+	bool
+
 config BFIN_JTAG_COMM
 	tristate "Blackfin JTAG Communication"
 	depends on BLACKFIN
diff --git a/drivers/tty/Makefile b/drivers/tty/Makefile
index 2953059..f4eb30c 100644
--- a/drivers/tty/Makefile
+++ b/drivers/tty/Makefile
@@ -1,5 +1,6 @@
 obj-y				+= tty_io.o n_tty.o tty_ioctl.o tty_ldisc.o \
 				   tty_buffer.o tty_port.o tty_mutex.o
+obj-$(CONFIG_TTY_ENUM)		+= tty_enum.o
 obj-$(CONFIG_LEGACY_PTYS)	+= pty.o
 obj-$(CONFIG_UNIX98_PTYS)	+= pty.o
 obj-$(CONFIG_AUDIT)		+= tty_audit.o
diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig
index 59c23d0..c3dee46 100644
--- a/drivers/tty/serial/Kconfig
+++ b/drivers/tty/serial/Kconfig
@@ -751,6 +751,7 @@ config SERIAL_HS_LPC32XX_CONSOLE
 
 config SERIAL_CORE
 	tristate
+	select TTY_ENUM
 
 config SERIAL_CORE_CONSOLE
 	bool
diff --git a/drivers/tty/serial/serial_core.c b/drivers/tty/serial/serial_core.c
index 2c7230a..d6cb417 100644
--- a/drivers/tty/serial/serial_core.c
+++ b/drivers/tty/serial/serial_core.c
@@ -2662,7 +2662,7 @@ int uart_remove_one_port(struct uart_driver *drv, struct uart_port *uport)
 	/*
 	 * Remove the devices from the tty layer
 	 */
-	tty_unregister_device(drv->tty_driver, uport->line);
+	tty_port_unregister_device(port, drv->tty_driver, uport->line);
 
 	if (port->tty)
 		tty_vhangup(port->tty);
diff --git a/drivers/tty/tty_enum.c b/drivers/tty/tty_enum.c
new file mode 100644
index 0000000..2e91cb6
--- /dev/null
+++ b/drivers/tty/tty_enum.c
@@ -0,0 +1,356 @@
+
+/*
+ * tty_enum.c - TTY enumeration support
+ *
+ * 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.
+ */
+
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/tty.h>
+#include <linux/tty_driver.h>
+#include <linux/string.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/init.h>
+
+
+static LIST_HEAD(tty_bus_id_list);
+DEFINE_MUTEX(tty_bus_lock);
+
+struct tty_bus_id {
+	char bus_id[TTY_NAME_SIZE];
+	unsigned int instance_no;
+	struct list_head node;
+};
+
+static ssize_t tty_slave_show_name(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct tty_slave *tts = to_tty_slave(dev);
+	return sprintf(buf, "%s\n", tts->name);
+}
+
+static int create_modalias(struct tty_slave *tts, char *modalias, int size)
+{
+	int len;
+	int count;
+	int i;
+
+	if (!tts->nr_ids)
+		return 0;
+
+	len = snprintf(modalias, size, "%s:", TTY_MODULE_PREFIX);
+	size -= len;
+
+	for (i = 0; i < tts->nr_ids; i++) {
+		count = snprintf(&modalias[len], size, "%s:", tts->ids[i]);
+		if (count < 0 || count >= size)
+			return -EINVAL;
+		len += count;
+		size -= count;
+	}
+
+	modalias[len] = '\0';
+	return len;
+}
+
+static ssize_t tty_slave_show_modalias(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct tty_slave *tts = to_tty_slave(dev);
+	size_t len;
+
+	/* Device has no IDs or string is >1024 */
+	len = create_modalias(tts, buf, 1024);
+	if (len <= 0)
+		return 0;
+	buf[len++] = '\n';
+	return len;
+}
+
+static ssize_t tty_slave_show_tty_attrs(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct tty_slave *tts = to_tty_slave(dev);
+	int len = 0;
+
+	/* baud rate */
+	len += sprintf(buf+len, "%d ", tts->baud);
+
+	/* data bits */
+	switch (tts->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 (tts->cflag & PARODD)
+		len += sprintf(buf+len, "O");
+	else if (tts->cflag & PARENB)
+		len += sprintf(buf+len, "E");
+	else
+		len += sprintf(buf+len, "N");
+
+	/* stop bits */
+	len += sprintf(buf+len, "%d", tts->cflag & CSTOPB ? 1 : 0);
+
+	/* HW/SW control */
+	if (tts->cflag & CRTSCTS)
+		len += sprintf(buf+len, " HW");
+	if ((tts->iflag & (IXON|IXOFF|IXANY)) != 0)
+		len += sprintf(buf+len, " SW");
+
+	len += sprintf(buf+len, "\n");
+	return len;
+}
+
+static ssize_t tty_slave_show_modem_lines(struct device *dev,
+		struct device_attribute *attr, char *buf)
+{
+	struct tty_slave *tts = to_tty_slave(dev);
+	int len = 0;
+
+	/* endian */
+	if (tts->mctrl & TIOCM_LE)
+		len += sprintf(buf+len, "LE:");
+	else
+		len += sprintf(buf+len, "BE:");
+
+	/* terminal lines */
+	if (tts->mctrl & TIOCM_DTR)
+		len += sprintf(buf+len, "DTR,");
+	if (tts->mctrl & TIOCM_RTS)
+		len += sprintf(buf+len, "RTS,");
+
+	/* modem lines */
+	if (tts->mctrl & TIOCM_CTS)
+		len += sprintf(buf+len, "CTS,");
+	if (tts->mctrl & TIOCM_CAR)
+		len += sprintf(buf+len, "CAR,");
+	if (tts->mctrl & TIOCM_RNG)
+		len += sprintf(buf+len, "RNG,");
+	if (tts->mctrl & TIOCM_DSR)
+		len += sprintf(buf+len, "DSR,");
+
+	len += sprintf(buf+len, "\n");
+	return len;
+}
+
+static DEVICE_ATTR(name, S_IRUGO, tty_slave_show_name, NULL);
+static DEVICE_ATTR(modalias, S_IRUGO, tty_slave_show_modalias, NULL);
+static DEVICE_ATTR(tty_attrs, S_IRUGO, tty_slave_show_tty_attrs, NULL);
+static DEVICE_ATTR(modem_lines, S_IRUGO, tty_slave_show_modem_lines, NULL);
+
+static struct attribute *tty_slave_attrs[] = {
+	&dev_attr_name.attr,
+	/* coldplug: modprobe $(cat .../modalias) */
+	&dev_attr_modalias.attr,
+	&dev_attr_tty_attrs.attr,
+	&dev_attr_modem_lines.attr,
+	NULL,
+};
+
+static struct attribute_group tty_slave_group = {
+	.attrs	= tty_slave_attrs,
+};
+
+static const struct attribute_group *tty_slave_groups[] = {
+	&tty_slave_group,
+	NULL,
+};
+
+#ifdef CONFIG_HOTPLUG
+static int tty_slave_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+	struct tty_slave *tts = to_tty_slave(dev);
+	int len;
+
+	if (!tts->nr_ids)
+		return 0;
+
+	if (add_uevent_var(env, "MODALIAS="))
+		return -ENOMEM;
+	len = create_modalias(tts, &env->buf[env->buflen - 1],
+			      sizeof(env->buf) - env->buflen);
+	if (len >= (sizeof(env->buf) - env->buflen))
+		return -ENOMEM;
+	env->buflen += len;
+
+	dev_dbg(dev, "uevent\n");
+	return 0;
+}
+#else
+#define tty_slave_uevent	NULL
+#endif
+
+static void tty_slave_release(struct device *dev)
+{
+	struct tty_slave *tts = to_tty_slave(dev);
+
+	kfree(tts);
+	/* Test code to see if slave device get released */
+	BUG();
+}
+
+struct device_type tty_slave_type = {
+	.name		= "tty_slave",
+	.groups		= tty_slave_groups,
+	.uevent		= tty_slave_uevent,
+	.release	= tty_slave_release,
+};
+EXPORT_SYMBOL_GPL(tty_slave_type);
+
+/**
+ * tty_bus_register_slave - register a physical tty slave device
+ * @port: the tty port
+ * @info: the tty slave device description
+ *
+ * Initialize and add a physical tty slave device.
+ *
+ * This returns the new physical tty slave device, which may be saved for
+ * later use with device_unregister; or NULL to indicate an error.
+ */
+struct tty_slave *tty_bus_register_slave(struct tty_port *port,
+		struct tty_board_info const *info)
+{
+	struct tty_slave *tts;
+	struct tty_bus_id *bus_id, *new_bus_id;
+	struct device *dev = NULL;
+	int i;
+	int status;
+	int found = 0;
+
+	/* Drivers having not called tty_port_register_device, should not
+	 * call this API.
+	 */
+	BUG_ON(!info || !port || !port->dev);
+	dev = port->dev;
+
+	tts = kzalloc(sizeof(struct tty_slave) + info->nr_ids * TTY_NAME_SIZE,
+		      GFP_KERNEL);
+	if (!tts) {
+		dev_err(dev, "Failed to alloc tty_slave %s.\n", info->type);
+		return NULL;
+	}
+
+	new_bus_id = kzalloc(sizeof(struct tty_bus_id), GFP_KERNEL);
+	if (!new_bus_id) {
+		dev_err(dev, "Failed to alloc tty_bus_id for %s.\n",
+			info->type);
+		goto fail;
+	}
+
+	tts->dev.parent = dev;
+	tts->dev.bus = &tty_enum_bus;
+	tts->dev.type = &tty_slave_type;
+
+	tts->dev.platform_data = info->platform_data;
+	if (info->archdata)
+		tts->dev.archdata = *info->archdata;
+
+	tts->baud = info->baud;
+	tts->cflag = info->cflag;
+	tts->iflag = info->iflag;
+	tts->mctrl = info->mctrl;
+	tts->irq = info->irq;
+
+	strlcpy(tts->name, info->type, TTY_NAME_SIZE);
+	for (i = 0; i < info->nr_ids; i++)
+		strlcpy(tts->ids[i], info->ids[i], TTY_NAME_SIZE);
+	tts->nr_ids = info->nr_ids;
+
+	mutex_lock(&tty_bus_lock);
+	list_for_each_entry(bus_id, &tty_bus_id_list, node) {
+		if (!strcmp(bus_id->bus_id, info->type)) {
+			bus_id->instance_no++;
+			found = 1;
+			kfree(new_bus_id);
+			break;
+		}
+	}
+	if (!found) {
+		bus_id = new_bus_id;
+		strcpy(bus_id->bus_id, info->type);
+		bus_id->instance_no = 0;
+		list_add_tail(&bus_id->node, &tty_bus_id_list);
+	}
+	dev_set_name(&tts->dev, "%s:%02x", bus_id->bus_id, bus_id->instance_no);
+	mutex_unlock(&tty_bus_lock);
+
+	status = device_register(&tts->dev);
+	if (status) {
+		dev_err(dev, "Failed to register slave device %s.\n",
+			info->type);
+		goto fail;
+	}
+	dev_info(dev, "Registered slave device %s.\n", info->type);
+
+	return tts;
+
+fail:
+	kfree(tts);
+	return NULL;
+}
+EXPORT_SYMBOL_GPL(tty_bus_register_slave);
+
+static int __unregister(struct device *dev, void *null)
+{
+	if (is_tty_slave(dev))
+		device_unregister(dev);
+	return 0;
+}
+
+void tty_bus_unregister_slaves(struct tty_port *port)
+{
+	if (port->dev)
+		(void)device_for_each_child(port->dev, NULL, __unregister);
+}
+EXPORT_SYMBOL_GPL(tty_bus_unregister_slaves);
+
+void tty_bus_register_master(struct tty_port *port, struct device *dev)
+{
+	BUG_ON(!port);
+	if (dev)
+		port->dev = get_device(dev);
+}
+EXPORT_SYMBOL_GPL(tty_bus_register_master);
+
+void tty_bus_unregister_master(struct tty_port *port)
+{
+	BUG_ON(!port);
+	if (port->dev) {
+		tty_bus_unregister_slaves(port);
+		put_device(port->dev);
+		port->dev = NULL;
+	}
+}
+EXPORT_SYMBOL_GPL(tty_bus_unregister_master);
+
+struct bus_type tty_enum_bus = {
+	.name		= "tty_enum",
+};
+EXPORT_SYMBOL_GPL(tty_enum_bus);
+
+int __init tty_bus_init(void)
+{
+	return bus_register(&tty_enum_bus);
+}
+
+postcore_initcall(tty_bus_init);
diff --git a/drivers/tty/tty_port.c b/drivers/tty/tty_port.c
index b7ff59d..8864124 100644
--- a/drivers/tty/tty_port.c
+++ b/drivers/tty/tty_port.c
@@ -69,8 +69,13 @@ struct device *tty_port_register_device(struct tty_port *port,
 		struct tty_driver *driver, unsigned index,
 		struct device *device)
 {
+	struct device *dev;
+
 	tty_port_link_device(port, driver, index);
-	return tty_register_device(driver, index, device);
+	dev = tty_register_device(driver, index, device);
+	tty_bus_register_master(port, dev);
+
+	return dev;
 }
 EXPORT_SYMBOL_GPL(tty_port_register_device);
 
@@ -92,12 +97,33 @@ struct device *tty_port_register_device_attr(struct tty_port *port,
 		struct device *device, void *drvdata,
 		const struct attribute_group **attr_grp)
 {
+	struct device *dev;
+
 	tty_port_link_device(port, driver, index);
-	return tty_register_device_attr(driver, index, device, drvdata,
-			attr_grp);
+	dev = tty_register_device_attr(driver, index, device, drvdata,
+				       attr_grp);
+	tty_bus_register_master(port, dev);
+
+	return dev;
 }
 EXPORT_SYMBOL_GPL(tty_port_register_device_attr);
 
+/**
+ * tty_port_unregister_device_attr - unregister tty device
+ * @port: tty_port of the device
+ * @driver: tty_driver for this device
+ * @index: index of the tty
+ *
+ * The reverse call for tty_register_device.
+ */
+void tty_port_unregister_device(struct tty_port *port,
+		struct tty_driver *driver, unsigned index)
+{
+	tty_bus_unregister_master(port);
+	tty_unregister_device(driver, index);
+}
+EXPORT_SYMBOL_GPL(tty_port_unregister_device);
+
 int tty_port_alloc_xmit_buf(struct tty_port *port)
 {
 	/* We may sleep in get_zeroed_page() */
@@ -132,6 +158,16 @@ EXPORT_SYMBOL(tty_port_free_xmit_buf);
  */
 void tty_port_destroy(struct tty_port *port)
 {
+	/*
+	 * XXX: TTY Driver Cleanup
+	 *
+	 * If you got panic here, it means you have enabled the TTY bus
+	 * enumeration support for your TTY driver but you haven't updated
+	 * your TTY driver code to call the tty_port_unregister_device
+	 * instead of the tty_unregister_device as a destruction
+	 * corresponding to the tty_port_register_device.
+	 */
+	BUG_ON(port->dev);
 	tty_buffer_free_all(port);
 }
 EXPORT_SYMBOL(tty_port_destroy);
diff --git a/include/linux/mod_devicetable.h b/include/linux/mod_devicetable.h
index fed3def..f1c4875 100644
--- a/include/linux/mod_devicetable.h
+++ b/include/linux/mod_devicetable.h
@@ -433,6 +433,12 @@ struct rpmsg_device_id {
 	char name[RPMSG_NAME_SIZE];
 };
 
+/* tty */
+
+/* TTY slave name size */
+#define TTY_NAME_SIZE	32
+#define TTY_MODULE_PREFIX "tty:"
+
 /* i2c */
 
 #define I2C_NAME_SIZE	20
diff --git a/include/linux/tty.h b/include/linux/tty.h
index 8db1b56..36eb9d1 100644
--- a/include/linux/tty.h
+++ b/include/linux/tty.h
@@ -9,6 +9,7 @@
 #include <linux/tty_ldisc.h>
 #include <linux/mutex.h>
 #include <linux/tty_flags.h>
+#include <linux/mod_devicetable.h>
 #include <uapi/linux/tty.h>
 
 
@@ -154,6 +155,105 @@ struct tty_bufhead {
 
 struct device;
 struct signal_struct;
+struct tty_slave;
+struct tty_board_info;
+
+/**
+ * struct tty_board_info - template for slave device creation
+ * @type: chip type, to initialize tty_slave.name
+ * @cflag: termio cflag, preferred termio cflag to be used to communicate
+ *         with this slave device
+ * @iflag: termio iflag, preferred termio iflag to be used to communicate
+ *         with this slave device
+ * @mctrl: termio mctrl, preferred termio mctrl to be used to communicate
+ *         with this slave device
+ * @baud: termio baud, preferred termio baud rate to be used to communicate
+ *        with this slave device
+ * @irq: stored in tty_slave.irq
+ * @platform_data: stored in tty_slave.dev.platform_data
+ * @archdata: copied into tty_slave.dev.archdata
+ * @nr_ids: number of IDs
+ * @ids: ID strings
+ *
+ * tty_board_info is used to build tables of information listing TTY
+ * devices that are present. This information is used to grow the driver
+ * model tree. For add-on boards, tty_bus_register_slave() does this
+ * dynamically with the host side physical device already known.
+ */
+struct tty_board_info {
+	char type[TTY_NAME_SIZE];
+	unsigned int cflag;	/* termio cflag */
+	unsigned int iflag;	/* termio iflag */
+	unsigned int mctrl;	/* modem ctrl settings */
+	unsigned int baud;
+	int irq;
+	void *platform_data;
+	struct dev_archdata *archdata;
+
+	int nr_ids;
+	/* This must be the last member of tty_board_info */
+	char ids[0][TTY_NAME_SIZE];
+};
+
+/**
+ * struct tty_slave - represent a TTY slave device
+ * @name: Indicates the type of the device, usually a chip name that's
+ *        generic enough to hide second-sourcing and compatible revisions
+ * @cflag: preferred termio cflag used to communicate with this slave
+ *         device
+ * @iflag: preferred termio iflag used to communicate with this slave
+ *         device
+ * @mctrl: preferred termio mctrl used to communicate with this slave
+ *         device
+ * @baud: preferred termio baud rate used to communicate with this slave
+ *        device
+ * @irq: indicates the IRQ generated by this slave device (if any)
+ * @dev: driver model device node for the slave device
+ * @nr_ids: number of IDs
+ * @ids: ID strings
+ *
+ * A tty_slave identifies a single device (i.e. chip) connected to a tty
+ * port.
+ */
+struct tty_slave {
+	char			name[TTY_NAME_SIZE];
+	unsigned int		cflag;	/* termio cflag */
+	unsigned int		iflag;	/* termio iflag */
+	unsigned int		mctrl;	/* modem ctrl settings */
+	unsigned int		baud;
+	int			irq;	/* irq issued by device */
+	struct device		dev;
+	int			nr_ids;
+	char			ids[0][TTY_NAME_SIZE];
+};
+
+extern struct device_type tty_slave_type;
+
+#define is_tty_slave(d) ((d) && (d)->type == &tty_slave_type)
+#define to_tty_slave(d) container_of(d, struct tty_slave, dev)
+
+static inline struct tty_slave *tty_verify_slave(struct device *dev)
+{
+	return is_tty_slave(dev) ? to_tty_slave(dev) : NULL;
+}
+
+#ifdef CONFIG_TTY_ENUM
+struct tty_slave *tty_bus_register_slave(struct tty_port *port,
+		struct tty_board_info const *info);
+void tty_bus_unregister_slaves(struct tty_port *port);
+void tty_bus_register_master(struct tty_port *port, struct device *dev);
+void tty_bus_unregister_master(struct tty_port *port);
+#else
+static inline struct tty_slave *tty_bus_register_slave(struct tty_port *port,
+		struct tty_board_info const *info)
+{
+	return NULL;
+}
+static inline void tty_bus_unregister_slaves(struct tty_port *port) {}
+static inline void tty_bus_register_master(struct tty_port *port,
+		struct device *dev) {}
+static inline void tty_bus_unregister_master(struct tty_port *port) {}
+#endif
 
 /*
  * Port level information. Each device keeps its own port level information
@@ -189,6 +289,7 @@ struct tty_port_operations {
 	
 struct tty_port {
 	struct tty_bufhead	buf;		/* Locked internally */
+	struct device		*dev;		/* Registered tty device */
 	struct tty_struct	*tty;		/* Back pointer */
 	struct tty_struct	*itty;		/* internal back ptr */
 	const struct tty_port_operations *ops;	/* Port operations */
@@ -325,6 +426,7 @@ extern void console_init(void);
 extern int vcs_init(void);
 
 extern struct class *tty_class;
+extern struct bus_type tty_enum_bus;
 
 /**
  *	tty_kref_get		-	get a tty reference
@@ -453,6 +555,8 @@ extern struct device *tty_port_register_device_attr(struct tty_port *port,
 		struct tty_driver *driver, unsigned index,
 		struct device *device, void *drvdata,
 		const struct attribute_group **attr_grp);
+extern void tty_port_unregister_device(struct tty_port *port,
+		struct tty_driver *driver, unsigned index);
 extern int tty_port_alloc_xmit_buf(struct tty_port *port);
 extern void tty_port_free_xmit_buf(struct tty_port *port);
 extern void tty_port_destroy(struct tty_port *port);
-- 
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