Alsa-Devel Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: Takashi Iwai <tiwai@suse.de>
To: alsa-devel@alsa-project.org
Cc: linux-kernel@vger.kernel.org
Subject: [PATCH v2 13/37] ALSA: ump: Redirect rawmidi substream access via own helpers
Date: Tue, 23 May 2023 09:53:34 +0200	[thread overview]
Message-ID: <20230523075358.9672-14-tiwai@suse.de> (raw)
In-Reply-To: <20230523075358.9672-1-tiwai@suse.de>

This is a code refactoring for abstracting the rawmidi access to the
UMP's own helpers.  It's a preliminary work for the later code
refactoring of the UMP layer.

Until now, we access to the rawmidi substream directly from the
driver via rawmidi access helpers, but after this change, the driver
is supposed to access via the newly introduced snd_ump_ops and
receive/transmit via snd_ump_receive() and snd_ump_transmit() helpers.
As of this commit, those are merely wrappers for the rawmidi
substream, and no much function change is seen here.

Reviewed-by: Jaroslav Kysela <perex@perex.cz>
Signed-off-by: Takashi Iwai <tiwai@suse.de>
---
 include/sound/ump.h |  14 ++++++
 sound/core/ump.c    | 111 ++++++++++++++++++++++++++++++++++++++++++++
 sound/usb/midi2.c   |  71 ++++++++++------------------
 3 files changed, 149 insertions(+), 47 deletions(-)

diff --git a/include/sound/ump.h b/include/sound/ump.h
index 8a3ac97cd1d3..6f786b462f16 100644
--- a/include/sound/ump.h
+++ b/include/sound/ump.h
@@ -9,18 +9,30 @@
 
 struct snd_ump_endpoint;
 struct snd_ump_block;
+struct snd_ump_ops;
 
 struct snd_ump_endpoint {
 	struct snd_rawmidi core;	/* raw UMP access */
 
 	struct snd_ump_endpoint_info info;
 
+	const struct snd_ump_ops *ops;	/* UMP ops set by the driver */
+	struct snd_rawmidi_substream *substreams[2];	/* opened substreams */
+
 	void *private_data;
 	void (*private_free)(struct snd_ump_endpoint *ump);
 
 	struct list_head block_list;	/* list of snd_ump_block objects */
 };
 
+/* ops filled by UMP drivers */
+struct snd_ump_ops {
+	int (*open)(struct snd_ump_endpoint *ump, int dir);
+	void (*close)(struct snd_ump_endpoint *ump, int dir);
+	void (*trigger)(struct snd_ump_endpoint *ump, int dir, int up);
+	void (*drain)(struct snd_ump_endpoint *ump, int dir);
+};
+
 struct snd_ump_block {
 	struct snd_ump_block_info info;
 	struct snd_ump_endpoint *ump;
@@ -39,6 +51,8 @@ int snd_ump_endpoint_new(struct snd_card *card, char *id, int device,
 int snd_ump_block_new(struct snd_ump_endpoint *ump, unsigned int blk,
 		      unsigned int direction, unsigned int first_group,
 		      unsigned int num_groups, struct snd_ump_block **blk_ret);
+int snd_ump_receive(struct snd_ump_endpoint *ump, const u32 *buffer, int count);
+int snd_ump_transmit(struct snd_ump_endpoint *ump, u32 *buffer, int count);
 
 /*
  * Some definitions for UMP
diff --git a/sound/core/ump.c b/sound/core/ump.c
index 651cd3752719..46ec297a786c 100644
--- a/sound/core/ump.c
+++ b/sound/core/ump.c
@@ -23,6 +23,11 @@ static long snd_ump_ioctl(struct snd_rawmidi *rmidi, unsigned int cmd,
 			  void __user *argp);
 static void snd_ump_proc_read(struct snd_info_entry *entry,
 			      struct snd_info_buffer *buffer);
+static int snd_ump_rawmidi_open(struct snd_rawmidi_substream *substream);
+static int snd_ump_rawmidi_close(struct snd_rawmidi_substream *substream);
+static void snd_ump_rawmidi_trigger(struct snd_rawmidi_substream *substream,
+				    int up);
+static void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream);
 
 static const struct snd_rawmidi_global_ops snd_ump_rawmidi_ops = {
 	.dev_register = snd_ump_dev_register,
@@ -31,6 +36,19 @@ static const struct snd_rawmidi_global_ops snd_ump_rawmidi_ops = {
 	.proc_read = snd_ump_proc_read,
 };
 
+static const struct snd_rawmidi_ops snd_ump_rawmidi_input_ops = {
+	.open = snd_ump_rawmidi_open,
+	.close = snd_ump_rawmidi_close,
+	.trigger = snd_ump_rawmidi_trigger,
+};
+
+static const struct snd_rawmidi_ops snd_ump_rawmidi_output_ops = {
+	.open = snd_ump_rawmidi_open,
+	.close = snd_ump_rawmidi_close,
+	.trigger = snd_ump_rawmidi_trigger,
+	.drain = snd_ump_rawmidi_drain,
+};
+
 static void snd_ump_endpoint_free(struct snd_rawmidi *rmidi)
 {
 	struct snd_ump_endpoint *ump = rawmidi_to_ump(rmidi);
@@ -104,6 +122,12 @@ int snd_ump_endpoint_new(struct snd_card *card, char *id, int device,
 
 	ump->core.private_free = snd_ump_endpoint_free;
 	ump->core.ops = &snd_ump_rawmidi_ops;
+	if (input)
+		snd_rawmidi_set_ops(&ump->core, SNDRV_RAWMIDI_STREAM_INPUT,
+				    &snd_ump_rawmidi_input_ops);
+	if (output)
+		snd_rawmidi_set_ops(&ump->core, SNDRV_RAWMIDI_STREAM_OUTPUT,
+				    &snd_ump_rawmidi_output_ops);
 
 	ump_dbg(ump, "Created a UMP EP #%d (%s)\n", device, id);
 	*ump_ret = ump;
@@ -137,6 +161,93 @@ snd_ump_get_block(struct snd_ump_endpoint *ump, unsigned char id)
 	return NULL;
 }
 
+/*
+ * rawmidi ops for UMP endpoint
+ */
+static int snd_ump_rawmidi_open(struct snd_rawmidi_substream *substream)
+{
+	struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi);
+	int dir = substream->stream;
+	int err;
+
+	if (ump->substreams[dir])
+		return -EBUSY;
+	err = ump->ops->open(ump, dir);
+	if (err < 0)
+		return err;
+	ump->substreams[dir] = substream;
+	return 0;
+}
+
+static int snd_ump_rawmidi_close(struct snd_rawmidi_substream *substream)
+{
+	struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi);
+	int dir = substream->stream;
+
+	ump->substreams[dir] = NULL;
+	ump->ops->close(ump, dir);
+	return 0;
+}
+
+static void snd_ump_rawmidi_trigger(struct snd_rawmidi_substream *substream,
+				    int up)
+{
+	struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi);
+	int dir = substream->stream;
+
+	ump->ops->trigger(ump, dir, up);
+}
+
+static void snd_ump_rawmidi_drain(struct snd_rawmidi_substream *substream)
+{
+	struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi);
+
+	if (ump->ops->drain)
+		ump->ops->drain(ump, SNDRV_RAWMIDI_STREAM_OUTPUT);
+}
+
+/**
+ * snd_ump_receive - transfer UMP packets from the device
+ * @ump: the UMP endpoint
+ * @buffer: the buffer pointer to transfer
+ * @count: byte size to transfer
+ *
+ * Called from the driver to submit the received UMP packets from the device
+ * to user-space.  It's essentially a wrapper of rawmidi_receive().
+ * The data to receive is in CPU-native endianness.
+ */
+int snd_ump_receive(struct snd_ump_endpoint *ump, const u32 *buffer, int count)
+{
+	struct snd_rawmidi_substream *substream =
+		ump->substreams[SNDRV_RAWMIDI_STREAM_INPUT];
+
+	if (!substream)
+		return 0;
+	return snd_rawmidi_receive(substream, (const char *)buffer, count);
+}
+EXPORT_SYMBOL_GPL(snd_ump_receive);
+
+/**
+ * snd_ump_transmit - transmit UMP packets
+ * @ump: the UMP endpoint
+ * @buffer: the buffer pointer to transfer
+ * @count: byte size to transfer
+ *
+ * Called from the driver to obtain the UMP packets from user-space to the
+ * device.  It's essentially a wrapper of rawmidi_transmit().
+ * The data to transmit is in CPU-native endianness.
+ */
+int snd_ump_transmit(struct snd_ump_endpoint *ump, u32 *buffer, int count)
+{
+	struct snd_rawmidi_substream *substream =
+		ump->substreams[SNDRV_RAWMIDI_STREAM_OUTPUT];
+
+	if (!substream)
+		return -ENODEV;
+	return snd_rawmidi_transmit(substream, (char *)buffer, count);
+}
+EXPORT_SYMBOL_GPL(snd_ump_transmit);
+
 /**
  * snd_ump_block_new - Create a UMP block
  * @ump: UMP object
diff --git a/sound/usb/midi2.c b/sound/usb/midi2.c
index 5ffee06ac746..7e849b2384ee 100644
--- a/sound/usb/midi2.c
+++ b/sound/usb/midi2.c
@@ -52,7 +52,8 @@ struct snd_usb_midi2_endpoint {
 	struct usb_device *dev;
 	const struct usb_ms20_endpoint_descriptor *ms_ep; /* reference to EP descriptor */
 	struct snd_usb_midi2_endpoint *pair;	/* bidirectional pair EP */
-	struct snd_usb_midi2_ump *rmidi;	/* assigned UMP EP */
+	struct snd_usb_midi2_ump *rmidi;	/* assigned UMP EP pair */
+	struct snd_ump_endpoint *ump;		/* assigned UMP EP */
 	int direction;			/* direction (STR_IN/OUT) */
 	unsigned int endpoint;		/* EP number */
 	unsigned int pipe;		/* URB pipe */
@@ -133,12 +134,8 @@ static int prepare_output_urb(struct snd_usb_midi2_endpoint *ep,
 {
 	int count;
 
-	if (ep->substream)
-		count = snd_rawmidi_transmit(ep->substream,
-					     urb->transfer_buffer,
-					     ep->packets);
-	else
-		count = -ENODEV;
+	count = snd_ump_transmit(ep->ump, urb->transfer_buffer,
+				 ep->packets);
 	if (count < 0) {
 		dev_dbg(&ep->dev->dev, "rawmidi transmit error %d\n", count);
 		return count;
@@ -197,9 +194,9 @@ static void input_urb_complete(struct urb *urb)
 	len &= ~3; /* align UMP */
 	if (len > ep->packets)
 		len = ep->packets;
-	if (len > 0 && ep->substream) {
+	if (len > 0) {
 		le32_to_cpu_array((u32 *)urb->transfer_buffer, len >> 2);
-		snd_rawmidi_receive(ep->substream, urb->transfer_buffer, len);
+		snd_ump_receive(ep->ump, (u32 *)urb->transfer_buffer, len);
 	}
  dequeue:
 	set_bit(ctx->index, &ep->urb_free);
@@ -330,68 +327,58 @@ static int alloc_midi_urbs(struct snd_usb_midi2_endpoint *ep)
 }
 
 static struct snd_usb_midi2_endpoint *
-substream_to_endpoint(struct snd_rawmidi_substream *substream)
+ump_to_endpoint(struct snd_ump_endpoint *ump, int dir)
 {
-	struct snd_ump_endpoint *ump = rawmidi_to_ump(substream->rmidi);
 	struct snd_usb_midi2_ump *rmidi = ump->private_data;
 
-	return rmidi->eps[substream->stream];
+	return rmidi->eps[dir];
 }
 
-/* rawmidi open callback */
-static int snd_usb_midi_v2_open(struct snd_rawmidi_substream *substream)
+/* ump open callback */
+static int snd_usb_midi_v2_open(struct snd_ump_endpoint *ump, int dir)
 {
-	struct snd_usb_midi2_endpoint *ep = substream_to_endpoint(substream);
+	struct snd_usb_midi2_endpoint *ep = ump_to_endpoint(ump, dir);
 	int err = 0;
 
 	if (!ep || !ep->endpoint)
 		return -ENODEV;
 	if (ep->disconnected)
 		return -EIO;
-	if (ep->substream)
-		return -EBUSY;
 	if (ep->direction == STR_OUT) {
 		err = alloc_midi_urbs(ep);
 		if (err)
 			return err;
 	}
-	spin_lock_irq(&ep->lock);
-	ep->substream = substream;
-	spin_unlock_irq(&ep->lock);
 	return 0;
 }
 
-/* rawmidi close callback */
-static int snd_usb_midi_v2_close(struct snd_rawmidi_substream *substream)
+/* ump close callback */
+static void snd_usb_midi_v2_close(struct snd_ump_endpoint *ump, int dir)
 {
-	struct snd_usb_midi2_endpoint *ep = substream_to_endpoint(substream);
+	struct snd_usb_midi2_endpoint *ep = ump_to_endpoint(ump, dir);
 
-	spin_lock_irq(&ep->lock);
-	ep->substream = NULL;
-	spin_unlock_irq(&ep->lock);
 	if (ep->direction == STR_OUT) {
 		kill_midi_urbs(ep, false);
 		drain_urb_queue(ep);
 		free_midi_urbs(ep);
 	}
-	return 0;
 }
 
-/* rawmidi trigger callback */
-static void snd_usb_midi_v2_trigger(struct snd_rawmidi_substream *substream,
+/* ump trigger callback */
+static void snd_usb_midi_v2_trigger(struct snd_ump_endpoint *ump, int dir,
 				    int up)
 {
-	struct snd_usb_midi2_endpoint *ep = substream_to_endpoint(substream);
+	struct snd_usb_midi2_endpoint *ep = ump_to_endpoint(ump, dir);
 
 	atomic_set(&ep->running, up);
 	if (up && ep->direction == STR_OUT && !ep->disconnected)
 		submit_io_urbs(ep);
 }
 
-/* rawmidi drain callback */
-static void snd_usb_midi_v2_drain(struct snd_rawmidi_substream *substream)
+/* ump drain callback */
+static void snd_usb_midi_v2_drain(struct snd_ump_endpoint *ump, int dir)
 {
-	struct snd_usb_midi2_endpoint *ep = substream_to_endpoint(substream);
+	struct snd_usb_midi2_endpoint *ep = ump_to_endpoint(ump, dir);
 
 	drain_urb_queue(ep);
 }
@@ -426,19 +413,13 @@ static int start_input_streams(struct snd_usb_midi2_interface *umidi)
 	return err;
 }
 
-static const struct snd_rawmidi_ops output_ops = {
+static const struct snd_ump_ops snd_usb_midi_v2_ump_ops = {
 	.open = snd_usb_midi_v2_open,
 	.close = snd_usb_midi_v2_close,
 	.trigger = snd_usb_midi_v2_trigger,
 	.drain = snd_usb_midi_v2_drain,
 };
 
-static const struct snd_rawmidi_ops input_ops = {
-	.open = snd_usb_midi_v2_open,
-	.close = snd_usb_midi_v2_close,
-	.trigger = snd_usb_midi_v2_trigger,
-};
-
 /* create a USB MIDI 2.0 endpoint object */
 static int create_midi2_endpoint(struct snd_usb_midi2_interface *umidi,
 				 struct usb_host_endpoint *hostep,
@@ -729,23 +710,19 @@ static int create_midi2_ump(struct snd_usb_midi2_interface *umidi,
 	umidi->chip->num_rawmidis++;
 
 	ump->private_data = rmidi;
-
-	if (input)
-		snd_rawmidi_set_ops(&ump->core, SNDRV_RAWMIDI_STREAM_INPUT,
-				    &input_ops);
-	if (output)
-		snd_rawmidi_set_ops(&ump->core, SNDRV_RAWMIDI_STREAM_OUTPUT,
-				    &output_ops);
+	ump->ops = &snd_usb_midi_v2_ump_ops;
 
 	rmidi->eps[STR_IN] = ep_in;
 	rmidi->eps[STR_OUT] = ep_out;
 	if (ep_in) {
 		ep_in->pair = ep_out;
 		ep_in->rmidi = rmidi;
+		ep_in->ump = ump;
 	}
 	if (ep_out) {
 		ep_out->pair = ep_in;
 		ep_out->rmidi = rmidi;
+		ep_out->ump = ump;
 	}
 
 	list_add_tail(&rmidi->list, &umidi->rawmidi_list);
-- 
2.35.3


  parent reply	other threads:[~2023-05-23  7:58 UTC|newest]

Thread overview: 69+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-05-23  7:53 [PATCH v2 00/37] ALSA: Add MIDI 2.0 support Takashi Iwai
2023-05-23  7:53 ` [PATCH v2 01/37] ALSA: rawmidi: Pass rawmidi directly to snd_rawmidi_kernel_open() Takashi Iwai
2023-05-23  7:53 ` [PATCH v2 02/37] ALSA: rawmidi: Add ioctl callback to snd_rawmidi_global_ops Takashi Iwai
2023-05-23  7:53 ` [PATCH v2 03/37] ALSA: rawmidi: UMP support Takashi Iwai
2023-05-23  7:53 ` [PATCH v2 04/37] ALSA: rawmidi: Skip UMP devices at SNDRV_CTL_IOCTL_RAWMIDI_NEXT_DEVICE Takashi Iwai
2023-05-23  7:53 ` [PATCH v2 05/37] ALSA: ump: Add ioctls to inquiry UMP EP and Block info via control API Takashi Iwai
2023-05-23  7:53 ` [PATCH v2 06/37] ALSA: ump: Additional proc output Takashi Iwai
2023-05-23  7:53 ` [PATCH v2 07/37] ALSA: usb-audio: Manage number of rawmidis globally Takashi Iwai
2023-05-23  7:53 ` [PATCH v2 08/37] ALSA: usb-audio: Define USB MIDI 2.0 specs Takashi Iwai
2023-05-23  7:53 ` [PATCH v2 09/37] ALSA: usb-audio: USB MIDI 2.0 UMP support Takashi Iwai
2023-05-23  7:53 ` [PATCH v2 10/37] ALSA: usb-audio: Get UMP EP name string from USB interface Takashi Iwai
2023-05-23  7:53 ` [PATCH v2 11/37] ALSA: usb-audio: Trim superfluous "MIDI" suffix from UMP EP name Takashi Iwai
2023-05-23  7:53 ` [PATCH v2 12/37] ALSA: usb-audio: Create UMP blocks from USB MIDI GTBs Takashi Iwai
2023-05-23  7:53 ` Takashi Iwai [this message]
2023-05-23  7:53 ` [PATCH v2 14/37] ALSA: ump: Add legacy raw MIDI support Takashi Iwai
2023-05-23  7:53 ` [PATCH v2 15/37] ALSA: usb-audio: Enable the " Takashi Iwai
2023-05-23  7:53 ` [PATCH v2 16/37] ALSA: usb-audio: Inform inconsistent protocols in GTBs Takashi Iwai
2023-05-23  7:53 ` [PATCH v2 17/37] ALSA: seq: Clear padded bytes at expanding events Takashi Iwai
2023-05-23  7:53 ` [PATCH v2 18/37] ALSA: seq: Add snd_seq_expand_var_event_at() helper Takashi Iwai
2023-05-23  7:53 ` [PATCH v2 19/37] ALSA: seq: Treat snd_seq_client object directly in client drivers Takashi Iwai
2023-05-23  7:53 ` [PATCH v2 20/37] ALSA: seq: Drop dead code for the old broadcast support Takashi Iwai
2023-05-23  7:53 ` [PATCH v2 21/37] ALSA: seq: Check the conflicting port at port creation Takashi Iwai
2023-05-23  7:53 ` [PATCH v2 22/37] ALSA: seq: Check validity before creating a port object Takashi Iwai
2023-05-23  7:53 ` [PATCH v2 23/37] ALSA: seq: Prohibit creating ports with special numbers Takashi Iwai
2023-05-23  7:53 ` [PATCH v2 24/37] ALSA: seq: Introduce SNDRV_SEQ_IOCTL_USER_PVERSION ioctl Takashi Iwai
2023-05-23  7:53 ` [PATCH v2 25/37] ALSA: seq: Add UMP support Takashi Iwai
2023-05-23  7:53 ` [PATCH v2 26/37] ALSA: seq: Add port inactive flag Takashi Iwai
2023-05-23  7:53 ` [PATCH v2 27/37] ALSA: seq: Support MIDI 2.0 UMP Endpoint port Takashi Iwai
2023-05-23  7:53 ` [PATCH v2 28/37] ALSA: seq: Add port direction to snd_seq_port_info Takashi Iwai
2023-05-23  7:53 ` [PATCH v2 29/37] ALSA: seq: Add UMP group number " Takashi Iwai
2023-05-23  7:53 ` [PATCH v2 30/37] ALSA: seq: Automatic conversion of UMP events Takashi Iwai
2023-05-23  7:53 ` [PATCH v2 31/37] ALSA: seq: Allow suppressing UMP conversions Takashi Iwai
2023-05-23  7:53 ` [PATCH v2 32/37] ALSA: seq: Bind UMP device Takashi Iwai
2023-05-23  7:53 ` [PATCH v2 33/37] ALSA: seq: ump: Create UMP Endpoint port for broadcast Takashi Iwai
2023-05-23  7:53 ` [PATCH v2 34/37] ALSA: seq: Add ioctls for client UMP info query and setup Takashi Iwai
2023-05-23  7:53 ` [PATCH v2 35/37] ALSA: seq: Print UMP Endpoint and Block information in proc outputs Takashi Iwai
2023-05-23  7:53 ` [PATCH v2 36/37] ALSA: seq: Add UMP group filter Takashi Iwai
2023-05-23  7:53 ` [PATCH v2 37/37] ALSA: docs: Add MIDI 2.0 documentation Takashi Iwai
2023-06-13 12:41 ` [PATCH v2 00/37] ALSA: Add MIDI 2.0 support happy.debugging
2023-06-13 12:53   ` Takashi Iwai
2023-06-13 13:24     ` Happy Debugging
2023-06-13 13:31       ` Takashi Iwai
2023-06-13 15:23         ` Symbolic Debugger
2023-06-14  6:01           ` Symbolic Debugger
2023-06-14  6:09             ` Takashi Iwai
2023-06-14  6:53               ` Takashi Iwai
2023-06-15 11:39                 ` Symbolic Debugger
2023-06-16 12:23                   ` Symbolic Debugger
2023-06-16 12:27                     ` Takashi Iwai
2023-06-16 14:17                       ` Symbolic Debugger
2023-06-17 15:07                         ` Symbolic Debugger
2023-06-18  8:54                           ` Takashi Iwai
2023-06-20 13:29                             ` Symbolic Debugger
2023-06-28  6:52                               ` Symbolic Debugger
2023-06-28 11:12                                 ` Takashi Iwai
2023-06-28 11:32                                   ` Symbolic Debugger
2023-06-28 14:20                                     ` Takashi Iwai
2023-06-29  2:03                                       ` Symbolic Debugger
2023-06-29  6:40                                         ` Takashi Iwai
2023-06-29  8:38                                           ` Symbolic Debugger
2023-07-03 10:15                                             ` Symbolic Debugger
2023-07-03 10:19                                               ` Takashi Iwai
2023-07-03 11:25                                                 ` Symbolic Debugger
2023-07-03 12:09                                                   ` Takashi Iwai
2023-07-03 12:54                                                     ` Symbolic Debugger
2023-07-03 13:01                                                       ` Takashi Iwai
2023-07-03 13:53                                                         ` Symbolic Debugger
2023-07-12 13:26                                                           ` Symbolic Debugger
2023-07-12 13:58                                                             ` Takashi Iwai

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=20230523075358.9672-14-tiwai@suse.de \
    --to=tiwai@suse.de \
    --cc=alsa-devel@alsa-project.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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox