lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite for Android: free password hash cracker in your pocket
[<prev] [next>] [day] [month] [year] [list]
Message-Id: <1335245716-3869-1-git-send-email-tom_lin@emc.com.tw>
Date:	Tue, 24 Apr 2012 13:35:16 +0800
From:	Tom Lin <tom_lin@....com.tw>
To:	dmitry.torokhov@...il.com
Cc:	tom_lin@....com.tw, linux-input@...r.kernel.org,
	linux-kernel@...r.kernel.org, Daniel Kurtz <djkurtz@...omium.org>
Subject: [PATCH v1] Input:Elan I2C touchpad driver for HID over I2C

This patch adds driver for Elan I2C touchpad. These protocol of HID over I2C
was defined by Microsoft. The kernel driver would use the multi-toucpad
protrocol format B.

Cc: Daniel Kurtz <djkurtz@...omium.org>
Signed-off-by: Tom Lin(Lin Yen Yu) <tom_lin@....com.tw>
---
 drivers/input/mouse/Kconfig        |   13 +-
 drivers/input/mouse/Makefile       |    1 +
 drivers/input/mouse/elantech_i2c.c |  488 ++++++++++++++++++++++++++++++++++++
 3 files changed, 501 insertions(+), 1 deletion(-)
 create mode 100644 drivers/input/mouse/elantech_i2c.c

diff --git a/drivers/input/mouse/Kconfig b/drivers/input/mouse/Kconfig
index 9b8db82..be931be 100644
--- a/drivers/input/mouse/Kconfig
+++ b/drivers/input/mouse/Kconfig
@@ -304,6 +304,18 @@ config MOUSE_MAPLE
 	  To compile this driver as a module choose M here: the module will be
 	  called maplemouse.
 
+config MOUSE_ELANTECH_I2C
+	tristate "Elan I2C Touchpad support"
+	depends on I2C
+	help
+	 This driver supports Elan I2C touchpad controller and follows the HID over
+	 I2C data format.
+
+	 Say Y here if you want to use a Elan HID-I2C touchpad.
+
+	 To compile this driver as a module, choose M here: the
+	 module will be called elantech_i2c.
+
 config MOUSE_SYNAPTICS_I2C
 	tristate "Synaptics I2C Touchpad support"
 	depends on I2C
@@ -338,5 +350,4 @@ config MOUSE_SYNAPTICS_USB
 
 	  To compile this driver as a module, choose M here: the
 	  module will be called synaptics_usb.
-
 endif
diff --git a/drivers/input/mouse/Makefile b/drivers/input/mouse/Makefile
index 4718eff..ba7cc88 100644
--- a/drivers/input/mouse/Makefile
+++ b/drivers/input/mouse/Makefile
@@ -8,6 +8,7 @@ obj-$(CONFIG_MOUSE_AMIGA)		+= amimouse.o
 obj-$(CONFIG_MOUSE_APPLETOUCH)		+= appletouch.o
 obj-$(CONFIG_MOUSE_ATARI)		+= atarimouse.o
 obj-$(CONFIG_MOUSE_BCM5974)		+= bcm5974.o
+obj-$(CONFIG_MOUSE_ELANTECH_I2C)	+= elantech_i2c.o
 obj-$(CONFIG_MOUSE_GPIO)		+= gpio_mouse.o
 obj-$(CONFIG_MOUSE_INPORT)		+= inport.o
 obj-$(CONFIG_MOUSE_LOGIBM)		+= logibm.o
diff --git a/drivers/input/mouse/elantech_i2c.c b/drivers/input/mouse/elantech_i2c.c
new file mode 100644
index 0000000..5cf2eb8
--- /dev/null
+++ b/drivers/input/mouse/elantech_i2c.c
@@ -0,0 +1,488 @@
+/*
+ * Elantech I2C Touchpad diver
+ *
+ * Copyright (c) 2011-2012 Tom Lin (Lin Yen Yu) <tom_lin@....com.tw>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ *
+ * Trademarks are the property of their respective owners.
+ */
+
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/input.h>
+#include <linux/input/mt.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#define DRIVER_NAME		"elantech_i2c"
+#define ETP_FINGER_DATA_OFFSET	4
+#define ETP_FINGER_DATA_LEN	5
+#define ETP_I2C_COMMAND_TRIES	3
+#define ETP_I2C_COMMAND_DELAY	500
+/* Length of Elan Touchpad information */
+#define ETP_INF_LENGTH		2
+#define ETP_MAX_FINGERS		5
+#define ETP_PACKET_LENGTH	30
+#define ETP_REPORT_ID		0X5d
+#define ETP_REPORT_MAX_LENGTH	79
+#define ETP_PRESSURE_MAX	0xff
+#define HID_DES_LENGTH_OFFSET	1
+
+static bool elan_i2c_debug;
+module_param_named(debug, elan_i2c_debug, bool, 0600);
+MODULE_PARM_DESC(debug, "Turn Elan i2c TP  debugging mode on and off");
+
+enum etd_i2c_command {
+	ETP_HID_DESCR_LENGTH_CMD,
+	ETP_HID_DESCR_CMD,
+	ETP_HID_WAKE_UP_CMD,
+	ETP_HID_RESET_CMD,
+	ETP_HID_REPORT_DESCR_CMD,
+	ETP_TRACE_NUM_CMD,
+	ETP_X_AXIS_MAX_CMD,
+	ETP_Y_AXIS_MAX_CMD,
+	ETP_DPI_VALUE_CMD,
+	ETP_ENABLE_ABS_CMD
+};
+
+static const u8 etp_hid_descriptor_cmd[] = {0x01, 0x00};
+static const u8 etp_hid_wake_up_cmd[] = {0x05, 0x00, 0x00, 0x08};
+static const u8 etp_hid_reset_cmd[] = {0x05, 0x00, 0x00, 0x01};
+static const u8 etp_hid_report_descriptor_cmd[] = {0x02, 0x00};
+static const u8 etp_trace_num_xy_cmd[] = {0x05, 0x01};
+static const u8 etp_x_axis_max_cmd[] = {0x06, 0x01};
+static const u8 etp_y_axis_max_cmd[] = {0x07, 0x01};
+static const u8 etp_dpi_value_cmd[] = {0x08, 0x01};
+static const u8 etp_enable_abs_cmd[] = {0x00, 0x03, 0x01, 0x00};
+
+/* The main device structure */
+struct elantech_i2c {
+	struct i2c_client	*client;
+	struct input_dev	*input;
+	struct work_struct	work;
+	unsigned int		gpio;
+	unsigned int		max_x;
+	unsigned int		max_y;
+	unsigned int		width_x;
+	unsigned int		width_y;
+	int			irq;
+	u8			pre_recv[28];
+};
+
+static int elantech_i2c_command(struct i2c_client *client, int  command,
+				unsigned char *buf_recv, int data_len)
+{
+	const u8 *cmd = NULL;
+	unsigned char *rec_buf = buf_recv;
+	int ret;
+	int tries = ETP_I2C_COMMAND_TRIES;
+	int length = 0;
+	struct i2c_msg msg[2];
+	int msg_num = 0;
+
+	switch (command) {
+	case ETP_HID_DESCR_LENGTH_CMD:
+	case ETP_HID_DESCR_CMD:
+		cmd = etp_hid_descriptor_cmd;
+		length = sizeof(etp_hid_descriptor_cmd);
+		msg[1].len = data_len;
+		msg_num = 2;
+		break;
+	case ETP_HID_WAKE_UP_CMD:
+		cmd = etp_hid_wake_up_cmd;
+		length = sizeof(etp_hid_wake_up_cmd);
+		msg_num = 1;
+		break;
+	case ETP_HID_RESET_CMD:
+		cmd = etp_hid_reset_cmd;
+		length = sizeof(etp_hid_reset_cmd);
+		msg_num = 1;
+		break;
+	case ETP_HID_REPORT_DESCR_CMD:
+		cmd = etp_hid_report_descriptor_cmd;
+		length = sizeof(etp_hid_report_descriptor_cmd);
+		msg[1].len = data_len;
+		msg_num = 2;
+		break;
+	case ETP_TRACE_NUM_CMD:
+		cmd = etp_trace_num_xy_cmd;
+		length = sizeof(etp_trace_num_xy_cmd);
+		msg[1].len = data_len;
+		msg_num = 2;
+		break;
+	case ETP_X_AXIS_MAX_CMD:
+		cmd = etp_x_axis_max_cmd;
+		length = sizeof(etp_x_axis_max_cmd);
+		msg[1].len = data_len;
+		msg_num = 2;
+		break;
+	case ETP_Y_AXIS_MAX_CMD:
+		cmd = etp_y_axis_max_cmd;
+		length = sizeof(etp_y_axis_max_cmd);
+		msg[1].len = data_len;
+		msg_num = 2;
+		break;
+	case ETP_DPI_VALUE_CMD:
+		cmd = etp_dpi_value_cmd;
+		length = sizeof(etp_dpi_value_cmd);
+		msg[1].len = data_len;
+		msg_num = 2;
+		break;
+	case ETP_ENABLE_ABS_CMD:
+		cmd = etp_enable_abs_cmd;
+		length = sizeof(etp_enable_abs_cmd);
+		msg_num = 1;
+		break;
+	default:
+		dev_dbg(&client->dev, "%s command=%d unknow.\n",
+			 __func__, command);
+		return -1;
+	}
+
+	msg[0].addr = client->addr;
+	msg[0].flags = client->flags & I2C_M_TEN;
+	msg[0].len = length;
+	msg[0].buf = (char *) cmd;
+	msg[1].addr = client->addr;
+	msg[1].flags = client->flags & I2C_M_TEN;
+	msg[1].flags |= I2C_M_RD;
+	msg[1].buf = rec_buf;
+
+	do {
+		ret = i2c_transfer(client->adapter, msg, msg_num);
+		if (ret != msg_num && elan_i2c_debug)
+			print_hex_dump_bytes("Elan I2C Touch data :",
+				DUMP_PREFIX_NONE, rec_buf, msg[1].len);
+		if (ret > 0)
+			break;
+		tries--;
+		dev_dbg(&client->dev, "retrying elantech_i2c_command:%d (%d)\n",
+			command, tries);
+	} while (tries > 0);
+
+	return ret;
+}
+
+static bool elantech_i2c_hw_init(struct i2c_client *client)
+{
+	int rc;
+	uint8_t buf_recv[ETP_REPORT_MAX_LENGTH];
+	int hid_descr_len;
+	int hid_report_len;
+
+	/* Fetch the length of HID description */
+	rc = elantech_i2c_command(client, ETP_HID_DESCR_LENGTH_CMD, buf_recv,
+			HID_DES_LENGTH_OFFSET);
+	if (rc != 2)
+		return false;
+
+	hid_descr_len = buf_recv[0];
+	/* Fetch the lenght of HID report description */
+	rc = elantech_i2c_command(client, ETP_HID_DESCR_CMD, buf_recv, hid_descr_len);
+	if (rc != 2)
+		return false;
+
+	hid_report_len = buf_recv[4];
+	rc = elantech_i2c_command(client, ETP_HID_WAKE_UP_CMD, buf_recv, 0);
+	if (rc != 1)
+		return false;
+
+	rc = elantech_i2c_command(client, ETP_HID_RESET_CMD, buf_recv, 0);
+	if (rc != 1)
+		return false;
+
+	msleep(ETP_I2C_COMMAND_DELAY);
+	rc = i2c_master_recv(client, buf_recv, 2);
+	if (rc != 2 || (buf_recv[0] != 0 && buf_recv[1] != 0))
+		return false;
+
+	rc = elantech_i2c_command(client, ETP_HID_REPORT_DESCR_CMD, buf_recv,
+		hid_report_len);
+	if (rc != 2)
+		return false;
+
+	return true;
+}
+
+static void elantech_i2c_report_absolute(struct elantech_i2c *touch,
+				 uint8_t *packet)
+{
+	uint8_t *finger_data = &packet[ETP_FINGER_DATA_OFFSET];
+	struct input_dev *input = touch->input;
+	bool finger_status;
+	int i;
+	int position_x, position_y;
+	int mky_value, mkx_value, h_value;
+
+	for (i = 0 ; i < ETP_MAX_FINGERS ; i++) {
+		finger_status = (packet[3] >> (3+i)) & 0x01;
+		if (finger_status) {
+			position_x = ((finger_data[0] & 0xf0) << 4) | finger_data[1];
+			position_y = touch->max_y -
+				(((finger_data[0] & 0x0f) << 8) | finger_data[2]);
+			mkx_value = (finger_data[3] & 0x0f) * touch->width_x;
+			mky_value = (finger_data[3] >> 4) * touch->width_y;
+			h_value = finger_data[4];
+
+			finger_data += ETP_FINGER_DATA_LEN;
+
+			input_mt_slot(input, i);
+			input_mt_report_slot_state(input, MT_TOOL_FINGER, true);
+
+			input_report_abs(input, ABS_MT_POSITION_X, position_x);
+			input_report_abs(input, ABS_MT_POSITION_Y, position_y);
+			input_report_abs(input, ABS_MT_PRESSURE, h_value);
+			input_report_abs(input, ABS_MT_TOUCH_MAJOR, mkx_value);
+			input_report_abs(input, ABS_MT_TOUCH_MINOR, mky_value);
+		} else {
+			input_mt_slot(input, i);
+			input_mt_report_slot_state(input, MT_TOOL_FINGER, false);
+		}
+	}
+	input_report_key(input, BTN_LEFT, ((packet[3] & 0x01) == 1));
+	input_report_key(input, BTN_RIGHT, ((packet[3] & 0x02) == 2));
+	input_report_key(input, BTN_MIDDLE, ((packet[3] & 0x04) == 4));
+	input_mt_report_pointer_emulation(input, true);
+	input_sync(input);
+}
+
+static irqreturn_t elantech_i2c_isr(int irq, void *dev_id)
+{
+	struct elantech_i2c *elantech_i2c_priv = dev_id;
+	unsigned char buf_recv[ETP_PACKET_LENGTH];
+	int rc;
+
+	rc = i2c_master_recv(elantech_i2c_priv->client, buf_recv, ETP_PACKET_LENGTH);
+	if (rc != ETP_PACKET_LENGTH)
+		goto elantech_isr_end;
+
+	memcpy(elantech_i2c_priv->pre_recv, buf_recv, ETP_PACKET_LENGTH);
+	/* Packet check */
+	if (buf_recv[2] == ETP_REPORT_ID && buf_recv[ETP_PACKET_LENGTH - 1] == 0x00)
+		elantech_i2c_report_absolute(elantech_i2c_priv, buf_recv);
+
+
+elantech_isr_end:
+	if (elan_i2c_debug && rc > 0)
+		print_hex_dump_bytes("Elan I2C Touch data :",
+			DUMP_PREFIX_NONE, buf_recv, rc);
+
+	return IRQ_HANDLED;
+}
+
+static struct elantech_i2c *elantech_i2c_priv_create(struct i2c_client *client)
+{
+	struct elantech_i2c *elantech_i2c_priv;
+	struct input_dev *dev;
+	unsigned int gpio;
+	const char *label = "elantech_i2c";
+	int rc = 0;
+	unsigned char buf_recv[2];
+
+	elantech_i2c_priv = kzalloc(sizeof(struct elantech_i2c), GFP_KERNEL);
+	if (!elantech_i2c_priv)
+		return NULL;
+
+	elantech_i2c_priv->client = client;
+	elantech_i2c_priv->irq = client->irq;
+	elantech_i2c_priv->input = input_allocate_device();
+	if (elantech_i2c_priv->input == NULL)
+		goto err_input_allocate_device;
+
+	elantech_i2c_priv->input->name = "ELANTECH_I2C";
+	elantech_i2c_priv->input->phys = client->adapter->name;
+	elantech_i2c_priv->input->id.bustype = BUS_I2C;
+
+	dev = elantech_i2c_priv->input;
+	__set_bit(EV_KEY, dev->evbit);
+	__set_bit(EV_ABS, dev->evbit);
+
+	__set_bit(BTN_LEFT, dev->keybit);
+	__set_bit(BTN_RIGHT, dev->keybit);
+	__set_bit(BTN_MIDDLE, dev->keybit);
+
+	__set_bit(BTN_TOUCH, dev->keybit);
+	__set_bit(BTN_TOOL_FINGER, dev->keybit);
+	__set_bit(BTN_TOOL_DOUBLETAP, dev->keybit);
+	__set_bit(BTN_TOOL_TRIPLETAP, dev->keybit);
+	__set_bit(BTN_TOOL_QUADTAP, dev->keybit);
+
+	elantech_i2c_command(client, ETP_X_AXIS_MAX_CMD, buf_recv, ETP_INF_LENGTH);
+	elantech_i2c_priv->max_x = (0x0f & buf_recv[1]) << 8 | buf_recv[0];
+	elantech_i2c_command(client, ETP_Y_AXIS_MAX_CMD, buf_recv, ETP_INF_LENGTH);
+	elantech_i2c_priv->max_y = (0x0f & buf_recv[1]) << 8 | buf_recv[0];
+	dev_info(&client->dev, "%s max_x = %d\n", __func__, elantech_i2c_priv->max_x);
+	dev_info(&client->dev, "%s max_y = %d\n", __func__, elantech_i2c_priv->max_y);
+	elantech_i2c_command(client, ETP_TRACE_NUM_CMD, buf_recv, ETP_INF_LENGTH);
+	elantech_i2c_priv->width_x = elantech_i2c_priv->max_x / (buf_recv[0] - 1);
+	elantech_i2c_priv->width_y = elantech_i2c_priv->max_y / (buf_recv[1] - 1);
+
+	/* X Y Value*/
+	input_set_abs_params(dev, ABS_X, 0, elantech_i2c_priv->max_x, 0, 0);
+	input_set_abs_params(dev, ABS_Y, 0, elantech_i2c_priv->max_y, 0, 0);
+	/* Palme Value */
+	input_set_abs_params(dev, ABS_PRESSURE, 0, 255, 0, 0);
+
+	/* Finger Pressure value */
+	input_set_abs_params(dev, ABS_TOOL_WIDTH, 0, ETP_PRESSURE_MAX, 0, 0);
+
+	/* Finger Pressure value */
+	input_mt_init_slots(dev, ETP_MAX_FINGERS);
+	input_set_abs_params(dev, ABS_MT_POSITION_X, 0, elantech_i2c_priv->max_x, 0, 0);
+	input_set_abs_params(dev, ABS_MT_POSITION_Y, 0, elantech_i2c_priv->max_y, 0, 0);
+	input_set_abs_params(dev, ABS_MT_PRESSURE, 0, ETP_PRESSURE_MAX, 0, 0);
+	input_set_abs_params(dev, ABS_MT_TOUCH_MAJOR, 0, ETP_PRESSURE_MAX, 0, 0);
+	input_set_abs_params(dev, ABS_MT_TOUCH_MINOR, 0, ETP_PRESSURE_MAX, 0, 0);
+
+	/* Enable ABS mode*/
+	elantech_i2c_command(client, ETP_ENABLE_ABS_CMD, buf_recv, ETP_INF_LENGTH);
+	gpio = irq_to_gpio(client->irq);
+	rc = gpio_request(gpio, label);
+	if (rc < 0) {
+		dev_dbg(&client->dev, "gpio_request failed for input %d rc = %d\n", gpio, rc);
+		goto	err_gpio_request;
+	}
+	rc = gpio_direction_input(gpio);
+	if (rc < 0) {
+		dev_dbg(&client->dev, "gpio_direction_input failed for input %d\n", gpio);
+		goto	err_gpio_request;
+	}
+	elantech_i2c_priv->gpio = gpio;
+	rc = request_threaded_irq(client->irq, NULL, elantech_i2c_isr,
+			IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+			client->name, elantech_i2c_priv);
+	 if (rc < 0) {
+		dev_dbg(&client->dev, "Could not register for %s interrupt, irq = %d, rc = %d\n",
+		label, client->irq, rc);
+		goto	err_gpio_request_irq_fail;
+	}
+
+	return elantech_i2c_priv;
+
+err_gpio_request_irq_fail:
+	gpio_free(gpio);
+err_gpio_request:
+	input_free_device(elantech_i2c_priv->input);
+err_input_allocate_device:
+	kfree(elantech_i2c_priv);
+	return NULL;
+}
+
+static int elantech_i2c_probe(struct i2c_client *client,
+			      const struct i2c_device_id *dev_id)
+
+{
+	int ret = 0;
+	struct elantech_i2c *elantech_i2c_priv;
+	unsigned int gpio;
+
+	if (!elantech_i2c_hw_init(client)) {
+		ret = -EINVAL;
+		goto err_hw_init;
+	}
+	elantech_i2c_priv = elantech_i2c_priv_create(client);
+	if (!elantech_i2c_priv) {
+		ret = -EINVAL;
+		goto err_hw_init;
+	}
+
+	ret = input_register_device(elantech_i2c_priv->input);
+	if (ret < 0)
+		goto err_input_register_device;
+
+	i2c_set_clientdata(client, elantech_i2c_priv);
+	device_init_wakeup(&client->dev, 1);
+
+	return 0;
+
+err_input_register_device:
+	gpio = elantech_i2c_priv->gpio;
+	gpio_free(gpio);
+	free_irq(elantech_i2c_priv->irq, elantech_i2c_priv);
+	input_free_device(elantech_i2c_priv->input);
+	kfree(elantech_i2c_priv);
+err_hw_init:
+	return ret;
+}
+
+static int elantech_i2c_remove(struct i2c_client *client)
+{
+	struct elantech_i2c *elantech_i2c_priv = i2c_get_clientdata(client);
+
+	if (elantech_i2c_priv) {
+		if (elantech_i2c_priv->gpio > 0)
+			gpio_free(elantech_i2c_priv->gpio);
+		free_irq(elantech_i2c_priv->irq, elantech_i2c_priv);
+		input_unregister_device(elantech_i2c_priv->input);
+		kfree(elantech_i2c_priv);
+	}
+
+	return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int elantech_i2c_suspend(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+
+	if (device_may_wakeup(&client->dev))
+		enable_irq_wake(client->irq);
+
+	return 0;
+}
+
+static int elantech_i2c_resume(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+
+	if (device_may_wakeup(&client->dev))
+		enable_irq_wake(client->irq);
+
+	return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(elan_i2c_touchpad_pm_ops,
+		elantech_i2c_suspend, elantech_i2c_resume);
+
+static const struct i2c_device_id elantech_i2c_id_table[] = {
+	{ "elantech_i2c", 0 },
+	{ },
+};
+MODULE_DEVICE_TABLE(i2c, elantech_i2c_id_table);
+
+static struct i2c_driver elantech_i2c_driver = {
+	.driver = {
+		.name	= DRIVER_NAME,
+		.owner	= THIS_MODULE,
+		.pm	= &elan_i2c_touchpad_pm_ops,
+	},
+	.probe		= elantech_i2c_probe,
+	.remove		= __devexit_p(elantech_i2c_remove),
+	.id_table	= elantech_i2c_id_table,
+};
+
+static int __init elantech_i2c_init(void)
+{
+	return i2c_add_driver(&elantech_i2c_driver);
+}
+
+static void __exit elantech_i2c_exit(void)
+{
+	i2c_del_driver(&elantech_i2c_driver);
+}
+
+module_init(elantech_i2c_init);
+module_exit(elantech_i2c_exit);
+
+MODULE_AUTHOR("Tom Lin (Lin Yen Yu) <tom_lin@....com.tw>");
+MODULE_DESCRIPTION("Elan I2C Touch Pad driver");
+MODULE_LICENSE("GPL");
-- 
1.7.9.2

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ