From: Bin Gao <bin.gao@linux.intel.com>
To: Arnd Bergmann <arnd@arndb.de>,
Greg Kroah-Hartman <gregkh@linuxfoundation.org>,
linux-kernel@vger.kernel.org
Subject: [PATCH 1/4] drivers/misc: add rawio framework driver
Date: Mon, 21 Oct 2013 17:03:20 -0700 [thread overview]
Message-ID: <5265C0C8.10609@linux.intel.com> (raw)
Rawio provides a framework to read/write registers from a bus, including
pci, i2c, I/O device(memory mapped), etc. based on debug fs.
Rawio bus drivers implement the read/write operation on a specific bus
on top of the rawio framework driver.
They are designed to help device driver and kernel debugging on
embedded systems.
Signed-off-by: Bin Gao <bin.gao@intel.com>
---
drivers/misc/Kconfig | 1 +
drivers/misc/Makefile | 1 +
drivers/misc/rawio/Kconfig | 21 ++
drivers/misc/rawio/Makefile | 1 +
drivers/misc/rawio/rawio.c | 514
++++++++++++++++++++++++++++++++++++++++++++
include/linux/rawio.h | 78 +++++++
6 files changed, 616 insertions(+)
create mode 100644 drivers/misc/rawio/Kconfig
create mode 100644 drivers/misc/rawio/Makefile
create mode 100644 drivers/misc/rawio/rawio.c
create mode 100644 include/linux/rawio.h
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig
index 8dacd4c..1afbe4e 100644
--- a/drivers/misc/Kconfig
+++ b/drivers/misc/Kconfig
@@ -537,4 +537,5 @@ source "drivers/misc/carma/Kconfig"
source "drivers/misc/altera-stapl/Kconfig"
source "drivers/misc/mei/Kconfig"
source "drivers/misc/vmw_vmci/Kconfig"
+source "drivers/misc/rawio/Kconfig"
endmenu
diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile
index c235d5b..3bc116b 100644
--- a/drivers/misc/Makefile
+++ b/drivers/misc/Makefile
@@ -53,3 +53,4 @@ obj-$(CONFIG_INTEL_MEI) += mei/
obj-$(CONFIG_VMWARE_VMCI) += vmw_vmci/
obj-$(CONFIG_LATTICE_ECP3_CONFIG) += lattice-ecp3-config.o
obj-$(CONFIG_SRAM) += sram.o
+obj-$(CONFIG_RAWIO) += rawio/
diff --git a/drivers/misc/rawio/Kconfig b/drivers/misc/rawio/Kconfig
new file mode 100644
index 0000000..fd4272e
--- /dev/null
+++ b/drivers/misc/rawio/Kconfig
@@ -0,0 +1,21 @@
+#
+# rawio utility drivers
+#
+
+menuconfig RAWIO
+ tristate "Debug fs based raw io device read/write framework "
+ depends on DEBUG_FS
+ default no
+ help
+ This option enables support for reading or writing registers/memory
+ region in a io device via debug fs.
+ With this option and related rawio driver options enabled, you could
+ read configuration space of a PCI device, registers of a memory
+ mapped or port mapped device, registers of a i2c device, etc.
+ This is the just the framework driver. You need enable more
+ options to support specific device types.
+
+ To compile this driver as a module, choose M: the module will
+ be called rawio.
+
+ If you are not sure, say N here.
diff --git a/drivers/misc/rawio/Makefile b/drivers/misc/rawio/Makefile
new file mode 100644
index 0000000..c21453c
--- /dev/null
+++ b/drivers/misc/rawio/Makefile
@@ -0,0 +1 @@
+obj-$(CONFIG_RAWIO) += rawio.o
diff --git a/drivers/misc/rawio/rawio.c b/drivers/misc/rawio/rawio.c
new file mode 100644
index 0000000..a05b493
--- /dev/null
+++ b/drivers/misc/rawio/rawio.c
@@ -0,0 +1,514 @@
+/*
+ * rawio.c - a debugfs based framework for reading/writing registers
+ * from a I/O device.
+ * With pluggable rawio drivers, it can support PCI devices, I2C devices,
+ * memory mapped I/O devices, etc.
+ * It's designed for helping debug Linux device drivers on embedded
system or
+ * SoC platforms.
+ *
+ * Copyright (c) 2013 Bin Gao <bin.gao@intel.com>
+ *
+ * This file is released under the GPLv2
+ *
+ *
+ * Two files are created in debugfs root folder: rawio_cmd and
rawio_output.
+ * To read or write via the rawio debugfs interface, first echo a rawio
+ * command to the file rawio_cmd, then cat the file rawio_output:
+ * $ echo "<rawio command>" > /sys/kernel/debug/rawio_cmd
+ * $ cat /sys/kernel/debug/rawio_output
+ * The cat command is required for both read and write operations.
+ * For details of rawio command format, see specific rawio drivers.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/debugfs.h>
+#include <linux/err.h>
+#include <linux/seq_file.h>
+#include <linux/uaccess.h>
+#include <linux/rawio.h>
+
+#define SHOW_NUM_PER_LINE (32 / active_width)
+#define LINE_WIDTH 32
+#define IS_WHITESPACE(c) ((c) == ' ' || (c) == '\t' || (c) == '\n')
+
+static struct dentry *rawio_cmd_dentry, *rawio_output_dentry;
+static char rawio_cmd_buf[RAWIO_CMD_LEN], rawio_err_buf[RAWIO_ERR_LEN + 1];
+static DEFINE_MUTEX(rawio_lock);
+static LIST_HEAD(rawio_driver_head);
+static struct rawio_driver *active_driver;
+static enum width active_width;
+static enum ops active_ops;
+static u64 args_val[RAWIO_ARGS_MAX];
+static u8 args_postfix[RAWIO_ARGS_MAX];
+static int num_args_val;
+
+static void store_value(u64 *where, void *value, enum type type)
+{
+ switch (type) {
+ case TYPE_U8:
+ *(u8 *)where = *(u8 *)value;
+ break;
+ case TYPE_U16:
+ *(u16 *)where = *(u16 *)value;
+ break;
+ case TYPE_U32:
+ *(u32 *)where = *(u32 *)value;
+ break;
+ case TYPE_U64:
+ *where = *(u64 *)value;
+ break;
+ case TYPE_S8:
+ *(s8 *)where = *(s8 *)value;
+ break;
+ case TYPE_S16:
+ *(s16 *)where = *(s16 *)value;
+ break;
+ case TYPE_S32:
+ *(s32 *)where = *(s32 *)value;
+ break;
+ case TYPE_S64:
+ *(s64 *)where = *(s64 *)value;
+ break;
+ default:
+ break;
+ }
+}
+
+int rawio_register_driver(struct rawio_driver *driver)
+{
+ mutex_lock(&rawio_lock);
+ list_add_tail(&driver->list, &rawio_driver_head);
+ mutex_unlock(&rawio_lock);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(rawio_register_driver);
+
+int rawio_unregister_driver(struct rawio_driver *driver)
+{
+ mutex_lock(&rawio_lock);
+ list_del(&driver->list);
+ mutex_unlock(&rawio_lock);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(rawio_unregister_driver);
+
+void rawio_err(const char *fmt, ...)
+{
+ va_list args;
+ va_start(args, fmt);
+ vsnprintf(rawio_err_buf, RAWIO_ERR_LEN, fmt, args);
+ va_end(args);
+}
+EXPORT_SYMBOL_GPL(rawio_err);
+
+static int parse_arguments(char *input, char **args)
+{
+ int count, located;
+ char *p = input;
+ int input_len = strlen(input);
+
+ count = 0;
+ located = 0;
+ while (*p != 0) {
+ if (p - input >= input_len)
+ break;
+
+ /* Locate the first character of a argument */
+ if (!IS_WHITESPACE(*p)) {
+ if (!located) {
+ located = 1;
+ args[count++] = p;
+ if (count > RAWIO_ARGS_MAX)
+ break;
+ }
+ } else {
+ if (located) {
+ *p = 0;
+ located = 0;
+ }
+ }
+ p++;
+ }
+
+ return count;
+}
+
+static int parse_driver_args(struct rawio_driver *driver, char **arg_list,
+ int num_args, enum ops ops, u64 *arg_val, u8 *postfix)
+{
+ int i;
+ size_t str_len;
+ enum type type;
+ u64 value;
+ char *str;
+ char *args_postfix;
+
+ for (i = 0; i < num_args; i++) {
+ switch (ops) {
+ case OPS_RD:
+ type = driver->args_rd_types[i];
+ args_postfix = driver->args_rd_postfix;
+ break;
+ case OPS_WR:
+ type = driver->args_wr_types[i];
+ args_postfix = driver->args_wr_postfix;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (args_postfix[i]) {
+ str = (char *) arg_list[i];
+ str_len = strlen(str);
+ if (str[str_len - 1] == args_postfix[i]) {
+ postfix[i] = 1;
+ str[str_len - 1] = 0;
+ } else {
+ postfix[i] = 0;
+ }
+ }
+
+ if (kstrtou64(arg_list[i], 0, &value))
+ goto failed;
+ store_value(arg_val + i, &value, type);
+ }
+
+ return 0;
+
+failed:
+ snprintf(rawio_err_buf, RAWIO_ERR_LEN,
+ "invalid argument %s, usage:\n", arg_list[i]);
+ strncat(rawio_err_buf, driver->help, RAWIO_ERR_LEN -
+ strlen(rawio_err_buf));
+ return -EINVAL;
+}
+
+static struct rawio_driver *find_driver(const char *name)
+{
+ struct rawio_driver *driver;
+
+ mutex_lock(&rawio_lock);
+ list_for_each_entry(driver, &rawio_driver_head, list) {
+ if (!strncmp(driver->name, name, strlen(name))) {
+ mutex_unlock(&rawio_lock);
+ return driver;
+ }
+ }
+ mutex_unlock(&rawio_lock);
+
+ return NULL;
+}
+
+static ssize_t rawio_cmd_write(struct file *file, const char __user *buf,
+ size_t len, loff_t *offset)
+{
+ char cmd[RAWIO_CMD_LEN];
+ char *arg_list[RAWIO_ARGS_MAX];
+ int num_args;
+ enum ops ops;
+ enum width width;
+ struct rawio_driver *driver;
+
+ rawio_err_buf[0] = 0;
+
+ if (len >= RAWIO_CMD_LEN) {
+ snprintf(rawio_err_buf, RAWIO_ERR_LEN, "command is too long.\n"
+ "max allowed command length is %d\n",
+ RAWIO_CMD_LEN);
+ goto done;
+ }
+
+ if (copy_from_user(cmd, buf, len)) {
+ snprintf(rawio_err_buf, RAWIO_ERR_LEN,
+ "copy_from_user() failed.\n");
+ goto done;
+ }
+ cmd[len] = 0;
+
+ rawio_cmd_buf[0] = 0;
+ strncpy(rawio_cmd_buf, cmd, len);
+ rawio_cmd_buf[len] = 0;
+
+ num_args = parse_arguments(cmd, arg_list);
+ if (num_args < RAWIO_ARGS_MIN) {
+ snprintf(rawio_err_buf, RAWIO_ERR_LEN,
+ "invalid command(too few arguments)\n");
+ goto done;
+ }
+ if (num_args > RAWIO_ARGS_MAX) {
+ snprintf(rawio_err_buf, RAWIO_ERR_LEN,
+ "invalid command(too many arguments)\n");
+ goto done;
+ }
+
+ /* arg 0: ops(read/write) and width (8/16/32/64 bit) */
+ if (arg_list[0][0] == 'r')
+ ops = OPS_RD;
+ else if (arg_list[0][0] == 'w')
+ ops = OPS_WR;
+ else {
+ snprintf(rawio_err_buf, RAWIO_ERR_LEN,
+ "invalid operation: %c, only r and w are supported\n",
+ arg_list[0][0]);
+ goto done;
+ }
+
+ if (strlen(arg_list[0]) >= 3) {
+ snprintf(rawio_err_buf, RAWIO_ERR_LEN,
+ "invalid bus width: %s, only 1 2 4 8 are supported\n",
+ arg_list[0] + 1);
+ goto done;
+ }
+
+ if (strlen(arg_list[0]) == 1)
+ width = WIDTH_DEFAULT;
+ else {
+ switch (arg_list[0][1]) {
+ case '1':
+ width = WIDTH_1;
+ break;
+ case '2':
+ width = WIDTH_2;
+ break;
+ case '4':
+ width = WIDTH_4;
+ break;
+ case '8':
+ width = WIDTH_8;
+ break;
+ default:
+ snprintf(rawio_err_buf, RAWIO_ERR_LEN,
+ "invalid bus width: %c, only 1 2 4 8 are supported\n",
+ arg_list[0][1]);
+ goto done;
+ }
+ }
+
+ /* arg1: driver name */
+ driver = find_driver(arg_list[1]);
+ if (!driver) {
+ snprintf(rawio_err_buf, RAWIO_ERR_LEN,
+ "unsupported driver type: %s\n", arg_list[1]);
+ goto done;
+ }
+
+ if (width == WIDTH_DEFAULT)
+ width = driver->default_width;
+
+ if (!(width & driver->supported_width)) {
+ snprintf(rawio_err_buf, RAWIO_ERR_LEN,
+ "unsupported driver width: %s\n", arg_list[0]);
+ goto done;
+ }
+
+ /* arg2, ..., argn: driver specific arguments */
+ num_args = num_args - 2;
+ if (((ops == OPS_RD) && (num_args > driver->args_rd_max_num)) ||
+ ((ops == OPS_WR) && (num_args > driver->args_wr_max_num))) {
+ snprintf(rawio_err_buf, RAWIO_ERR_LEN,
+ "too many arguments, usage:\n");
+ strncat(rawio_err_buf, driver->help, RAWIO_ERR_LEN -
+ strlen(rawio_err_buf));
+ goto done;
+ }
+ if (((ops == OPS_RD) && (num_args < driver->args_rd_min_num)) ||
+ ((ops == OPS_WR) && (num_args < driver->args_wr_min_num))) {
+ snprintf(rawio_err_buf, RAWIO_ERR_LEN,
+ "too few arguments, usage:\n");
+ strncat(rawio_err_buf, driver->help, RAWIO_ERR_LEN -
+ strlen(rawio_err_buf));
+ goto done;
+ }
+
+ if (parse_driver_args(driver, arg_list + 2, num_args, ops,
+ args_val, args_postfix))
+ goto done;
+
+ active_driver = driver;
+ active_width = width;
+ active_ops = ops;
+ num_args_val = num_args;
+done:
+ return len;
+}
+
+static int rawio_output_show(struct seq_file *s, void *unused)
+{
+ u32 start, end, start_nature, end_nature;
+ int ret, i, comp1, comp2, output_len;
+ void *output;
+ char seq_buf[16];
+
+ mutex_lock(&rawio_lock);
+
+ if (strlen(rawio_err_buf) > 0) {
+ seq_puts(s, rawio_err_buf);
+ mutex_unlock(&rawio_lock);
+ return 0;
+ }
+
+ active_driver->s = s;
+
+ if (active_ops == OPS_WR) {
+ ret = active_driver->ops->write(active_driver, active_width,
+ args_val, args_postfix, num_args_val);
+ if (ret)
+ seq_puts(s, rawio_err_buf);
+ else
+ seq_puts(s, "write succeeded.\n");
+
+ mutex_unlock(&rawio_lock);
+ return 0;
+ }
+
+ if (active_driver->ops->read_and_show) {
+ ret = active_driver->ops->read_and_show(active_driver,
+ active_width, args_val, args_postfix, num_args_val);
+ if (ret)
+ seq_puts(s, rawio_err_buf);
+ mutex_unlock(&rawio_lock);
+ return 0;
+ }
+
+ ret = active_driver->ops->read(active_driver, active_width, args_val,
+ args_postfix, num_args_val, &output, &output_len);
+ if (ret) {
+ seq_puts(s, rawio_err_buf);
+ mutex_unlock(&rawio_lock);
+ return 0;
+ }
+
+ start_nature = (u32)args_val[active_driver->addr_pos];
+ start = (start_nature / LINE_WIDTH) * LINE_WIDTH;
+ end_nature = start_nature + (output_len - 1) * active_width;
+ end = (end_nature / LINE_WIDTH + 1) * LINE_WIDTH - active_width;
+ comp1 = (start_nature - start) / active_width;
+ comp2 = (end - end_nature) / active_width;
+
+ mutex_unlock(&rawio_lock);
+
+ for (i = 0; i < comp1 + comp2 + output_len; i++) {
+ if ((i % SHOW_NUM_PER_LINE) == 0) {
+ snprintf(seq_buf, sizeof(seq_buf), "[%08x]",
+ (u32)start + i * 4);
+ seq_puts(s, seq_buf);
+ }
+ if (i < comp1 || i >= output_len + comp1) {
+ switch (active_width) {
+ case WIDTH_8:
+ seq_puts(s, " ****************");
+ break;
+ case WIDTH_4:
+ seq_puts(s, " ********");
+ break;
+ case WIDTH_2:
+ seq_puts(s, " ****");
+ break;
+ case WIDTH_1:
+ seq_puts(s, " **");
+ break;
+ default:
+ break;
+ }
+ } else {
+ switch (active_width) {
+ case WIDTH_8:
+ snprintf(seq_buf, sizeof(seq_buf), "[%016llx]",
+ *((u64 *)output + i - comp1));
+ seq_puts(s, seq_buf);
+ break;
+ case WIDTH_4:
+ snprintf(seq_buf, sizeof(seq_buf), " %08x",
+ *((u32 *)output + i - comp1));
+ seq_puts(s, seq_buf);
+ break;
+ case WIDTH_2:
+ snprintf(seq_buf, sizeof(seq_buf), " %04x",
+ *((u16 *)output + i - comp1));
+ seq_puts(s, seq_buf);
+ break;
+ case WIDTH_1:
+ snprintf(seq_buf, sizeof(seq_buf), " %02x",
+ *((u8 *)output + i - comp1));
+ seq_puts(s, seq_buf);
+ break;
+ default:
+ break;
+ }
+ }
+
+ if ((i + 1) % SHOW_NUM_PER_LINE == 0)
+ seq_puts(s, "\n");
+ }
+
+ kfree(output);
+ return 0;
+}
+
+static int rawio_cmd_show(struct seq_file *s, void *unused)
+{
+ seq_puts(s, rawio_cmd_buf);
+ return 0;
+}
+
+static int rawio_cmd_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, rawio_cmd_show, NULL);
+}
+
+static const struct file_operations rawio_cmd_fops = {
+ .owner = THIS_MODULE,
+ .open = rawio_cmd_open,
+ .read = seq_read,
+ .write = rawio_cmd_write,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int rawio_output_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, rawio_output_show, NULL);
+}
+
+static const struct file_operations rawio_output_fops = {
+ .owner = THIS_MODULE,
+ .open = rawio_output_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int __init rawio_init(void)
+{
+ rawio_cmd_dentry = debugfs_create_file("rawio_cmd",
+ S_IFREG | S_IRUGO | S_IWUSR, NULL, NULL, &rawio_cmd_fops);
+ rawio_output_dentry = debugfs_create_file("rawio_output",
+ S_IFREG | S_IRUGO, NULL, NULL, &rawio_output_fops);
+ if (!rawio_cmd_dentry || !rawio_output_dentry) {
+ pr_err("rawio: can't create debugfs node\n");
+ return -EFAULT;
+ }
+
+ return 0;
+}
+module_init(rawio_init);
+
+static void __exit rawio_exit(void)
+{
+ debugfs_remove(rawio_cmd_dentry);
+ debugfs_remove(rawio_output_dentry);
+}
+module_exit(rawio_exit);
+
+MODULE_DESCRIPTION("Raw IO read/write utility framework driver");
+MODULE_VERSION("1.0");
+MODULE_AUTHOR("Bin Gao <bin.gao@intel.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/include/linux/rawio.h b/include/linux/rawio.h
new file mode 100644
index 0000000..8f62851
--- /dev/null
+++ b/include/linux/rawio.h
@@ -0,0 +1,78 @@
+#ifndef RAWIO_H
+#define RAWIO_H
+
+#define RAWIO_DRVNAME_LEN 8
+#define RAWIO_ERR_LEN 256
+#define RAWIO_CMD_LEN 96
+#define RAWIO_HELP_LEN 128
+#define RAWIO_ARGS_MIN 3
+#define RAWIO_ARGS_MAX 10
+
+enum type {
+ TYPE_STR = 0,
+ TYPE_U8,
+ TYPE_U16,
+ TYPE_U32,
+ TYPE_U64,
+ TYPE_S8,
+ TYPE_S16,
+ TYPE_S32,
+ TYPE_S64,
+};
+
+/* read/write width: 1, 2, 4 or 8 bytes */
+enum width {
+ WIDTH_DEFAULT = 0,
+ WIDTH_1 = 1,
+ WIDTH_2 = 2,
+ WIDTH_4 = 4,
+ WIDTH_8 = 8,
+};
+
+enum ops {
+ OPS_RD = 1, /* read */
+ OPS_WR, /* write */
+};
+
+struct rawio_driver {
+ struct list_head list;
+ char name[RAWIO_DRVNAME_LEN];
+
+ int args_rd_max_num; /* max args for read(including optional args) */
+ enum type args_rd_types[RAWIO_ARGS_MAX]; /* type of each arg */
+ int args_rd_min_num; /* min args for read */
+ char args_rd_postfix[RAWIO_ARGS_MAX]; /* read args postfix */
+
+ int args_wr_max_num; /* max args for write(including optional args) */
+ enum type args_wr_types[RAWIO_ARGS_MAX]; /* type of each arg */
+ int args_wr_min_num; /* min args for write */
+ char args_wr_postfix[RAWIO_ARGS_MAX]; /* write args postfix */
+
+ /* index of argument that specifies the register or memory address */
+ int addr_pos;
+
+ unsigned int supported_width;
+ enum width default_width;
+ char help[RAWIO_HELP_LEN];
+ struct rawio_ops *ops;
+ struct seq_file *s;
+};
+
+struct rawio_ops {
+ /* driver reads io device and returns the data to framework */
+ int (*read) (struct rawio_driver *drv, int width,
+ u64 *input, u8 *postfix, int input_num,
+ void **output, int *output_num);
+ /* driver reads io device and shows the data */
+ int (*read_and_show) (struct rawio_driver *drv, int width,
+ u64 *input, u8 *postfix, int input_num);
+ /* driver writes data passed from framework to io device */
+ int (*write) (struct rawio_driver *driver, int width,
+ u64 *input, u8 *postfix, int input_num);
+};
+
+int rawio_register_driver(struct rawio_driver *drv);
+int rawio_unregister_driver(struct rawio_driver *drv);
+void rawio_err(const char *fmt, ...);
+
+#endif
--
1.8.1.2
next reply other threads:[~2013-10-22 0:01 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2013-10-22 0:03 Bin Gao [this message]
2013-10-22 5:45 ` [PATCH 1/4] drivers/misc: add rawio framework driver Greg Kroah-Hartman
2013-10-22 18:32 ` Bin Gao
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=5265C0C8.10609@linux.intel.com \
--to=bin.gao@linux.intel.com \
--cc=arnd@arndb.de \
--cc=gregkh@linuxfoundation.org \
--cc=linux-kernel@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.