From: "David Härdeman" <david@hardeman.nu>
To: linux-media@vger.kernel.org
Cc: m.chehab@samsung.com
Subject: [PATCH 17/49] rc-core: add chardev
Date: Fri, 04 Apr 2014 01:32:41 +0200 [thread overview]
Message-ID: <20140403233241.27099.78648.stgit@zeus.muc.hardeman.nu> (raw)
In-Reply-To: <20140403232420.27099.94872.stgit@zeus.muc.hardeman.nu>
This patch lays the groundwork for adding a rc-core chardev.
Signed-off-by: David Härdeman <david@hardeman.nu>
---
drivers/media/rc/ir-raw.c | 2
drivers/media/rc/rc-main.c | 284 +++++++++++++++++++++++++++++++++++---------
include/media/rc-core.h | 11 +-
3 files changed, 238 insertions(+), 59 deletions(-)
diff --git a/drivers/media/rc/ir-raw.c b/drivers/media/rc/ir-raw.c
index aed2997..7eb347a 100644
--- a/drivers/media/rc/ir-raw.c
+++ b/drivers/media/rc/ir-raw.c
@@ -271,7 +271,7 @@ int ir_raw_event_register(struct rc_dev *dev)
spin_lock_init(&dev->raw->lock);
dev->raw->thread = kthread_run(ir_raw_event_thread, dev->raw,
- "rc%u", dev->minor);
+ dev_name(&dev->dev));
if (IS_ERR(dev->raw->thread)) {
rc = PTR_ERR(dev->raw->thread);
diff --git a/drivers/media/rc/rc-main.c b/drivers/media/rc/rc-main.c
index 42268f3..9c7bdb8 100644
--- a/drivers/media/rc/rc-main.c
+++ b/drivers/media/rc/rc-main.c
@@ -18,6 +18,7 @@
#include <linux/input.h>
#include <linux/leds.h>
#include <linux/slab.h>
+#include <linux/sched.h>
#include <linux/idr.h>
#include <linux/device.h>
#include <linux/module.h>
@@ -38,6 +39,20 @@ static struct led_trigger *led_feedback;
/* Used to keep track of rc devices */
static DEFINE_IDA(rc_ida);
+static dev_t rc_devt;
+
+/**
+ * struct rc_client - keeps track of processes which have opened a rc chardev
+ * @dev: the &struct rc_dev which is being controlled
+ * @fasync: keeps track of the fasync queue
+ * @node: list of current clients for the rc device (protected by client_lock
+ * in &struct rc_dev)
+ */
+struct rc_client {
+ struct rc_dev *dev;
+ struct fasync_struct *fasync;
+ struct list_head node;
+};
static struct rc_map_list *seek_rc_map(const char *name)
{
@@ -844,47 +859,45 @@ void rc_keydown_notimeout(struct rc_dev *dev, enum rc_type protocol,
}
EXPORT_SYMBOL_GPL(rc_keydown_notimeout);
-int rc_open(struct rc_dev *rdev)
+int rc_open(struct rc_dev *dev)
{
- int rval = 0;
-
- if (!rdev)
- return -EINVAL;
+ int err = 0;
- mutex_lock(&rdev->lock);
- if (!rdev->users++ && rdev->open != NULL)
- rval = rdev->open(rdev);
+ mutex_lock(&dev->lock);
- if (rval)
- rdev->users--;
+ if (dev->dead)
+ err = -ENODEV;
+ else if (!dev->users++ && dev->open) {
+ err = dev->open(dev);
+ if (err)
+ dev->users--;
+ }
- mutex_unlock(&rdev->lock);
+ mutex_unlock(&dev->lock);
- return rval;
+ return err;
}
EXPORT_SYMBOL_GPL(rc_open);
-static int ir_open(struct input_dev *idev)
+static int rc_input_open(struct input_dev *idev)
{
struct rc_dev *rdev = input_get_drvdata(idev);
return rc_open(rdev);
}
-void rc_close(struct rc_dev *rdev)
+void rc_close(struct rc_dev *dev)
{
- if (rdev) {
- mutex_lock(&rdev->lock);
+ mutex_lock(&dev->lock);
- if (!--rdev->users && rdev->close != NULL)
- rdev->close(rdev);
+ if (!dev->dead && !--dev->users && dev->close)
+ dev->close(dev);
- mutex_unlock(&rdev->lock);
- }
+ mutex_unlock(&dev->lock);
}
EXPORT_SYMBOL_GPL(rc_close);
-static void ir_close(struct input_dev *idev)
+static void rc_input_close(struct input_dev *idev)
{
struct rc_dev *rdev = input_get_drvdata(idev);
rc_close(rdev);
@@ -1306,8 +1319,136 @@ unlock:
return (ret < 0) ? ret : len;
}
+/**
+ * rc_attach_client() - attaches a new userspace client to a rc device
+ * @dev: the rc device
+ * @client: the client to attach
+ */
+static void rc_attach_client(struct rc_dev *dev, struct rc_client *client)
+{
+ spin_lock(&dev->client_lock);
+ list_add_tail_rcu(&client->node, &dev->client_list);
+ spin_unlock(&dev->client_lock);
+}
+
+/**
+ * rc_detach_client() - detaches a userspace client from a rc device
+ * @dev: the rc device
+ * @client: the client to detach
+ */
+static void rc_detach_client(struct rc_dev *dev, struct rc_client *client)
+{
+ spin_lock(&dev->client_lock);
+ list_del_rcu(&client->node);
+ spin_unlock(&dev->client_lock);
+ synchronize_rcu();
+}
+
+/**
+ * rc_dev_open() - allows userspace to open() a rc device file
+ * @inode: the &struct inode corresponding to the device file
+ * @file: the &struct file corresponding to the open() attempt
+ * @return: zero on success, or a negative error code
+ *
+ * This function (which implements open in &struct file_operations)
+ * allows userspace to open() a rc device file.
+ */
+static int rc_dev_open(struct inode *inode, struct file *file)
+{
+ struct rc_dev *dev = container_of(inode->i_cdev, struct rc_dev, cdev);
+ struct rc_client *client;
+ int err;
+
+ IR_dprintk(2, "Open attempt on %u\n", iminor(inode));
+
+ client = kzalloc(sizeof(*client), GFP_KERNEL);
+ if (!client)
+ return -ENOMEM;
+
+ client->dev = dev;
+
+ rc_attach_client(dev, client);
+
+ err = rc_open(dev);
+ if (err)
+ goto out;
+
+ file->private_data = client;
+ nonseekable_open(inode, file);
+
+ IR_dprintk(2, "Device %u opened\n", iminor(inode));
+ return 0;
+
+out:
+ rc_detach_client(dev, client);
+ kfree(client);
+ return err;
+}
+
+/**
+ * rc_release() - allows userspace to close() a rc device file
+ * @inode: the &struct inode corresponding to the device file
+ * @file: the &struct file corresponding to the previous open()
+ * @return: zero on success, or a negative error code
+ *
+ * This function (which implements release in &struct file_operations)
+ * allows userspace to close() a rc device file.
+ */
+static int rc_release(struct inode *inode, struct file *file)
+{
+ struct rc_client *client = file->private_data;
+ struct rc_dev *dev = client->dev;
+
+ rc_detach_client(dev, client);
+ kfree(client);
+ rc_close(dev);
+
+ IR_dprintk(2, "Device %u closed\n", iminor(inode));
+ return 0;
+}
+
+/**
+ * rc_fasync() - allows userspace to recieve asynchronous notifications
+ * @fd: the file descriptor corresponding to the opened rc device
+ * @file: the &struct file corresponding to the previous open()
+ * @on: whether notifications should be enabled or disabled
+ * @return: zero on success, or a negative error code
+ *
+ * This function (which implements fasync in &struct file_operations)
+ * allows userspace to receive asynchronous signal notifications
+ * when the state of a rc device file changes.
+ */
+static int rc_fasync(int fd, struct file *file, int on)
+{
+ struct rc_client *client = file->private_data;
+
+ return fasync_helper(fd, file, on, &client->fasync);
+}
+
+static const struct file_operations rc_fops = {
+ .owner = THIS_MODULE,
+ .open = rc_dev_open,
+ .release = rc_release,
+ .fasync = rc_fasync,
+ .llseek = no_llseek,
+};
+
+/**
+ * rc_dev_release() - release a &struct rc_dev
+ * @device: the &struct device which corresponds to the &struct rc_dev
+ *
+ * This function is called by the driver core when the refcount
+ * for the &device reaches zero.
+ */
static void rc_dev_release(struct device *device)
{
+ struct rc_dev *dev = to_rc_dev(device);
+
+ if (dev->input_dev)
+ input_free_device(dev->input_dev);
+
+ kfree(dev);
+ module_put(THIS_MODULE);
}
#define ADD_HOTPLUG_VAR(fmt, val...) \
@@ -1409,6 +1550,9 @@ struct rc_dev *rc_allocate_device(void)
dev->input_dev->setkeycode = ir_setkeycode;
input_set_drvdata(dev->input_dev, dev);
+ INIT_LIST_HEAD(&dev->client_list);
+ spin_lock_init(&dev->client_lock);
+
spin_lock_init(&dev->rc_map.lock);
spin_lock_init(&dev->keylock);
mutex_init(&dev->lock);
@@ -1418,23 +1562,27 @@ struct rc_dev *rc_allocate_device(void)
dev->dev.class = &rc_class;
device_initialize(&dev->dev);
+ cdev_init(&dev->cdev, &rc_fops);
+ dev->cdev.kobj.parent = &dev->dev.kobj;
+ dev->cdev.owner = THIS_MODULE;
+
__module_get(THIS_MODULE);
return dev;
}
EXPORT_SYMBOL_GPL(rc_allocate_device);
+/**
+ * rc_free_device() - free an allocated struct rc_dev
+ * @dev: the &struct rc_dev to free
+ *
+ * This function is used by drivers to free a &struct rc_dev which has
+ * been allocated with rc_allocate_device(). It must not be used
+ * after rc_register_device() has been successfully called.
+ */
void rc_free_device(struct rc_dev *dev)
{
- if (!dev)
- return;
-
- if (dev->input_dev)
- input_free_device(dev->input_dev);
-
- put_device(&dev->dev);
-
- kfree(dev);
- module_put(THIS_MODULE);
+ if (dev)
+ put_device(&dev->dev);
}
EXPORT_SYMBOL_GPL(rc_free_device);
@@ -1460,17 +1608,15 @@ int rc_register_device(struct rc_dev *dev)
set_bit(EV_REP, dev->input_dev->evbit);
set_bit(EV_MSC, dev->input_dev->evbit);
set_bit(MSC_SCAN, dev->input_dev->mscbit);
- if (dev->open)
- dev->input_dev->open = ir_open;
- if (dev->close)
- dev->input_dev->close = ir_close;
+ dev->input_dev->open = rc_input_open;
+ dev->input_dev->close = rc_input_close;
minor = ida_simple_get(&rc_ida, 0, RC_DEV_MAX, GFP_KERNEL);
if (minor < 0)
return minor;
- dev->minor = minor;
- dev_set_name(&dev->dev, "rc%u", dev->minor);
+ dev->dev.devt = MKDEV(MAJOR(rc_devt), minor);
+ dev_set_name(&dev->dev, "rc%u", minor);
dev_set_drvdata(&dev->dev, dev);
dev->dev.groups = dev->sysfs_groups;
@@ -1491,10 +1637,14 @@ int rc_register_device(struct rc_dev *dev)
*/
mutex_lock(&dev->lock);
- rc = device_add(&dev->dev);
+ rc = cdev_add(&dev->cdev, dev->dev.devt, 1);
if (rc)
goto out_unlock;
+ rc = device_add(&dev->dev);
+ if (rc)
+ goto out_cdev;
+
rc = ir_setkeytable(dev, rc_map);
if (rc)
goto out_dev;
@@ -1558,8 +1708,8 @@ int rc_register_device(struct rc_dev *dev)
mutex_unlock(&dev->lock);
- IR_dprintk(1, "Registered rc%u (driver: %s, remote: %s, mode %s)\n",
- dev->minor,
+ IR_dprintk(1, "Registered %s (driver: %s, remote: %s, mode %s)\n",
+ dev_name(&dev->dev),
dev->driver_name ? dev->driver_name : "unknown",
rc_map->name ? rc_map->name : "unknown",
dev->driver_type == RC_DRIVER_IR_RAW ? "raw" : "cooked");
@@ -1576,6 +1726,8 @@ out_table:
ir_free_table(&dev->rc_map);
out_dev:
device_del(&dev->dev);
+out_cdev:
+ cdev_del(&dev->cdev);
out_unlock:
mutex_unlock(&dev->lock);
ida_simple_remove(&rc_ida, minor);
@@ -1585,42 +1737,61 @@ EXPORT_SYMBOL_GPL(rc_register_device);
void rc_unregister_device(struct rc_dev *dev)
{
+ struct rc_client *client;
+
if (!dev)
return;
+ mutex_lock(&dev->lock);
+ dev->dead = true;
+ mutex_unlock(&dev->lock);
+
+ spin_lock(&dev->client_lock);
+ list_for_each_entry(client, &dev->client_list, node)
+ kill_fasync(&client->fasync, SIGIO, POLL_HUP);
+ spin_unlock(&dev->client_lock);
+
+ cdev_del(&dev->cdev);
+
del_timer_sync(&dev->timer_keyup);
if (dev->driver_type == RC_DRIVER_IR_RAW)
ir_raw_event_unregister(dev);
- /* Freeing the table should also call the stop callback */
ir_free_table(&dev->rc_map);
- IR_dprintk(1, "Freed keycode table\n");
input_unregister_device(dev->input_dev);
dev->input_dev = NULL;
- device_del(&dev->dev);
-
- ida_simple_remove(&rc_ida, dev->minor);
+ /* dev is marked as dead so no one changes dev->users */
+ if (dev->users && dev->close)
+ dev->close(dev);
- rc_free_device(dev);
+ device_del(&dev->dev);
+ ida_simple_remove(&rc_ida, MINOR(dev->dev.devt));
+ put_device(&dev->dev);
}
EXPORT_SYMBOL_GPL(rc_unregister_device);
-/*
- * Init/exit code for the module. Basically, creates/removes /sys/class/rc
- */
-
static int __init rc_core_init(void)
{
- int rc = class_register(&rc_class);
- if (rc) {
- printk(KERN_ERR "rc_core: unable to register rc class\n");
- return rc;
+ int err;
+
+ err = class_register(&rc_class);
+ if (err) {
+ printk(KERN_ERR "rc_core: unable to create class\n");
+ return err;
}
+ err = alloc_chrdev_region(&rc_devt, 0, RC_DEV_MAX, "rc-core");
+ if (err) {
+ printk(KERN_ERR "rc_core: unable to allocate chardev region\n");
+ class_unregister(&rc_class);
+ return err;
+ }
+
+ IR_dprintk(1, "Allocated char dev: %u\n", MAJOR(rc_devt));
led_trigger_register_simple("rc-feedback", &led_feedback);
rc_map_register(&empty_map);
@@ -1629,9 +1800,10 @@ static int __init rc_core_init(void)
static void __exit rc_core_exit(void)
{
- class_unregister(&rc_class);
- led_trigger_unregister_simple(led_feedback);
rc_map_unregister(&empty_map);
+ led_trigger_unregister_simple(led_feedback);
+ unregister_chrdev_region(rc_devt, RC_DEV_MAX);
+ class_unregister(&rc_class);
}
subsys_initcall(rc_core_init);
diff --git a/include/media/rc-core.h b/include/media/rc-core.h
index ca3d836..e2615e7 100644
--- a/include/media/rc-core.h
+++ b/include/media/rc-core.h
@@ -20,6 +20,7 @@
#include <linux/kfifo.h>
#include <linux/time.h>
#include <linux/timer.h>
+#include <linux/cdev.h>
#include <media/rc-map.h>
extern int rc_core_debug;
@@ -78,6 +79,7 @@ enum rc_filter_type {
/**
* struct rc_dev - represents a remote control device
* @dev: driver model's view of this device
+ * @cdev: character device structure
* @sysfs_groups: sysfs attribute groups
* @input_name: name of the input child device
* @input_phys: physical path to the input child device
@@ -87,7 +89,9 @@ enum rc_filter_type {
* @rc_map: current scan/key table
* @lock: used to ensure we've filled in all protocol details before
* anyone can call show_protocols or store_protocols
- * @minor: unique minor remote control device number
+ * @dead: used to determine if the device is still alive
+ * @client_list: list of clients (processes which have opened the rc chardev)
+ * @client_lock: protects client_list
* @raw: additional data for raw pulse/space devices
* @input_dev: the input child device used to communicate events to userspace
* @driver_type: specifies if protocol decoding is done in hardware or software
@@ -139,6 +143,7 @@ enum rc_filter_type {
*/
struct rc_dev {
struct device dev;
+ struct cdev cdev;
const struct attribute_group *sysfs_groups[5];
const char *input_name;
const char *input_phys;
@@ -147,7 +152,9 @@ struct rc_dev {
const char *map_name;
struct rc_map rc_map;
struct mutex lock;
- unsigned int minor;
+ bool dead;
+ struct list_head client_list;
+ spinlock_t client_lock;
struct ir_raw_event_ctrl *raw;
struct input_dev *input_dev;
enum rc_driver_type driver_type;
next prev parent reply other threads:[~2014-04-03 23:32 UTC|newest]
Thread overview: 68+ messages / expand[flat|nested] mbox.gz Atom feed top
2014-04-03 23:31 [PATCH 00/49] rc-core: my current patch queue David Härdeman
2014-04-03 23:31 ` [PATCH 01/49] bt8xx: fixup RC5 decoding David Härdeman
2014-04-03 23:31 ` [PATCH 02/49] rc-core: improve ir-kbd-i2c get_key functions David Härdeman
2014-04-03 23:31 ` [PATCH 03/49] rc-core: document the protocol type David Härdeman
2014-04-03 23:31 ` [PATCH 04/49] rc-core: do not change 32bit NEC scancode format for now David Härdeman
2014-04-04 13:18 ` James Hogan
2014-04-03 23:31 ` [PATCH 05/49] rc-core: split dev->s_filter David Härdeman
2014-04-04 13:08 ` James Hogan
2014-04-03 23:31 ` [PATCH 06/49] rc-core: remove generic scancode filter David Härdeman
2014-04-04 13:30 ` James Hogan
2014-04-03 23:31 ` [PATCH 07/49] dib0700: NEC scancode cleanup David Härdeman
2014-04-03 23:31 ` [PATCH 08/49] lmedm04: " David Härdeman
2014-04-03 23:32 ` [PATCH 09/49] saa7134: NEC scancode fix David Härdeman
2014-04-03 23:32 ` [PATCH 10/49] [RFC] rc-core: use the full 32 bits for NEC scancodes David Härdeman
2014-04-03 23:32 ` [PATCH 11/49] [RFC] rc-core: don't throw away protocol information David Härdeman
2014-04-03 23:32 ` [PATCH 12/49] rc-core: simplify sysfs code David Härdeman
2014-04-03 23:32 ` [PATCH 13/49] rc-core: remove protocol arrays David Härdeman
2014-04-03 23:32 ` [PATCH 14/49] rc-core: rename dev->scanmask to dev->scancode_mask David Härdeman
2014-04-03 23:32 ` [PATCH 15/49] rc-core: merge rc5 and streamzap decoders David Härdeman
2014-04-03 23:32 ` [PATCH 16/49] rc-core: use an IDA rather than a bitmap David Härdeman
2014-07-25 22:39 ` Mauro Carvalho Chehab
2014-04-03 23:32 ` David Härdeman [this message]
2014-04-03 23:32 ` [PATCH 18/49] rc-core: allow chardev to be read David Härdeman
2014-04-03 23:32 ` [PATCH 19/49] rc-core: use a kfifo for TX data David Härdeman
2014-04-03 23:32 ` [PATCH 20/49] rc-core: allow chardev to be written David Härdeman
2014-04-03 23:33 ` [PATCH 21/49] rc-core: add ioctl support to the rc chardev David Härdeman
2014-04-03 23:33 ` [PATCH 22/49] rc-core: add an ioctl for getting IR RX settings David Härdeman
2014-04-03 23:33 ` [PATCH 23/49] rc-loopback: add RCIOCGIRRX ioctl support David Härdeman
2014-04-03 23:33 ` [PATCH 24/49] rc-core: add an ioctl for setting IR RX settings David Härdeman
2014-04-03 23:33 ` [PATCH 25/49] rc-loopback: add RCIOCSIRRX ioctl support David Härdeman
2014-04-03 23:33 ` [PATCH 26/49] rc-core: add an ioctl for getting IR TX settings David Härdeman
2014-04-03 23:33 ` [PATCH 27/49] rc-loopback: add RCIOCGIRTX ioctl support David Härdeman
2014-04-03 23:33 ` [PATCH 28/49] rc-core: add an ioctl for setting IR TX settings David Härdeman
2014-04-03 23:33 ` [PATCH 29/49] rc-loopback: add RCIOCSIRTX ioctl support David Härdeman
2014-04-03 23:33 ` [PATCH 30/49] rc-core: leave the internals of rc_dev alone David Härdeman
2014-07-24 1:50 ` Mauro Carvalho Chehab
2014-04-03 23:33 ` [PATCH 31/49] rc-core: split rc-main.c into rc-main.c and rc-keytable.c David Härdeman
2014-07-25 22:44 ` Mauro Carvalho Chehab
2014-04-03 23:33 ` [PATCH 32/49] rc-core: prepare for multiple keytables David Härdeman
2014-07-25 22:52 ` Mauro Carvalho Chehab
2014-04-03 23:34 ` [PATCH 33/49] rc-core: make the keytable of rc_dev an array David Härdeman
2014-04-03 23:34 ` [PATCH 34/49] rc-core: add ioctls for adding/removing keytables from userspace David Härdeman
2014-04-03 23:34 ` [PATCH 35/49] rc-core: remove redundant spinlock David Härdeman
2014-04-03 23:34 ` [PATCH 36/49] rc-core: make keytable RCU-friendly David Härdeman
2014-04-03 23:34 ` [PATCH 37/49] rc-core: allow empty keymaps David Härdeman
2014-07-25 22:58 ` Mauro Carvalho Chehab
2014-04-03 23:34 ` [PATCH 38/49] rc-core: rename ir-raw.c David Härdeman
2014-04-03 23:34 ` [PATCH 39/49] rc-core: make IR raw handling a separate module David Härdeman
2014-07-25 23:04 ` Mauro Carvalho Chehab
2014-04-03 23:34 ` [PATCH 40/49] rc-ir-raw: simplify locking David Härdeman
2014-07-25 23:08 ` Mauro Carvalho Chehab
2014-04-03 23:34 ` [PATCH 41/49] rc-core: rename mutex David Härdeman
2014-04-10 21:28 ` James Hogan
2014-07-25 23:12 ` Mauro Carvalho Chehab
2014-04-03 23:34 ` [PATCH 42/49] rc-ir-raw: atomic reads of protocols David Härdeman
2014-07-25 23:13 ` Mauro Carvalho Chehab
2014-04-03 23:34 ` [PATCH 43/49] rc-core: fix various sparse warnings David Härdeman
2014-04-03 23:34 ` [PATCH 44/49] rc-core: don't report scancodes via input devices David Härdeman
2014-07-25 23:16 ` Mauro Carvalho Chehab
2014-04-03 23:35 ` [PATCH 45/49] rc-ir-raw: add various rc_events David Härdeman
2014-07-25 23:16 ` Mauro Carvalho Chehab
2014-04-03 23:35 ` [PATCH 46/49] rc-core: use struct rc_event for all rc communication David Härdeman
2014-07-25 23:19 ` Mauro Carvalho Chehab
2014-04-03 23:35 ` [PATCH 47/49] rc-core: add keytable events David Härdeman
2014-04-03 23:35 ` [PATCH 48/49] rc-core: move remaining keytable functions David Härdeman
2014-04-03 23:35 ` [PATCH 49/49] rc-core: make rc-core.h userspace friendly David Härdeman
2014-04-04 2:05 ` [PATCH 00/49] rc-core: my current patch queue Mauro Carvalho Chehab
2014-06-26 20:07 ` David Härdeman
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=20140403233241.27099.78648.stgit@zeus.muc.hardeman.nu \
--to=david@hardeman.nu \
--cc=linux-media@vger.kernel.org \
--cc=m.chehab@samsung.com \
/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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox