All of lore.kernel.org
 help / color / mirror / Atom feed
From: Chris Wilson <chris@chris-wilson.co.uk>
To: intel-gfx@lists.freedesktop.org
Subject: [PATCH 15/34] drm/i915: Move vma lookup to its own lock
Date: Mon, 21 Jan 2019 22:20:58 +0000	[thread overview]
Message-ID: <20190121222117.23305-16-chris@chris-wilson.co.uk> (raw)
In-Reply-To: <20190121222117.23305-1-chris@chris-wilson.co.uk>

Remove the struct_mutex requirement for looking up the vma for an
object.

v2: Highlight how the race for duplicate vma creation is resolved on
reacquiring the lock with a short comment.

Signed-off-by: Chris Wilson <chris@chris-wilson.co.uk>
Reviewed-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
---
 drivers/gpu/drm/i915/i915_debugfs.c       |  6 +--
 drivers/gpu/drm/i915/i915_gem.c           | 33 +++++++-----
 drivers/gpu/drm/i915/i915_gem_object.h    | 45 +++++++++-------
 drivers/gpu/drm/i915/i915_vma.c           | 66 ++++++++++++++++-------
 drivers/gpu/drm/i915/i915_vma.h           |  2 +-
 drivers/gpu/drm/i915/selftests/i915_vma.c |  4 +-
 6 files changed, 98 insertions(+), 58 deletions(-)

diff --git a/drivers/gpu/drm/i915/i915_debugfs.c b/drivers/gpu/drm/i915/i915_debugfs.c
index 3ec369980d40..2a6e4044f25b 100644
--- a/drivers/gpu/drm/i915/i915_debugfs.c
+++ b/drivers/gpu/drm/i915/i915_debugfs.c
@@ -159,14 +159,14 @@ describe_obj(struct seq_file *m, struct drm_i915_gem_object *obj)
 		   obj->mm.madv == I915_MADV_DONTNEED ? " purgeable" : "");
 	if (obj->base.name)
 		seq_printf(m, " (name: %d)", obj->base.name);
-	list_for_each_entry(vma, &obj->vma_list, obj_link) {
+	list_for_each_entry(vma, &obj->vma.list, obj_link) {
 		if (i915_vma_is_pinned(vma))
 			pin_count++;
 	}
 	seq_printf(m, " (pinned x %d)", pin_count);
 	if (obj->pin_global)
 		seq_printf(m, " (global)");
-	list_for_each_entry(vma, &obj->vma_list, obj_link) {
+	list_for_each_entry(vma, &obj->vma.list, obj_link) {
 		if (!drm_mm_node_allocated(&vma->node))
 			continue;
 
@@ -322,7 +322,7 @@ static int per_file_stats(int id, void *ptr, void *data)
 	if (obj->base.name || obj->base.dma_buf)
 		stats->shared += obj->base.size;
 
-	list_for_each_entry(vma, &obj->vma_list, obj_link) {
+	list_for_each_entry(vma, &obj->vma.list, obj_link) {
 		if (!drm_mm_node_allocated(&vma->node))
 			continue;
 
diff --git a/drivers/gpu/drm/i915/i915_gem.c b/drivers/gpu/drm/i915/i915_gem.c
index 538fa5404603..15acd052da46 100644
--- a/drivers/gpu/drm/i915/i915_gem.c
+++ b/drivers/gpu/drm/i915/i915_gem.c
@@ -437,15 +437,19 @@ int i915_gem_object_unbind(struct drm_i915_gem_object *obj)
 	if (ret)
 		return ret;
 
-	while ((vma = list_first_entry_or_null(&obj->vma_list,
-					       struct i915_vma,
-					       obj_link))) {
+	spin_lock(&obj->vma.lock);
+	while (!ret && (vma = list_first_entry_or_null(&obj->vma.list,
+						       struct i915_vma,
+						       obj_link))) {
 		list_move_tail(&vma->obj_link, &still_in_list);
+		spin_unlock(&obj->vma.lock);
+
 		ret = i915_vma_unbind(vma);
-		if (ret)
-			break;
+
+		spin_lock(&obj->vma.lock);
 	}
-	list_splice(&still_in_list, &obj->vma_list);
+	list_splice(&still_in_list, &obj->vma.list);
+	spin_unlock(&obj->vma.lock);
 
 	return ret;
 }
@@ -3489,7 +3493,7 @@ int i915_gem_object_set_cache_level(struct drm_i915_gem_object *obj,
 	 * reading an invalid PTE on older architectures.
 	 */
 restart:
-	list_for_each_entry(vma, &obj->vma_list, obj_link) {
+	list_for_each_entry(vma, &obj->vma.list, obj_link) {
 		if (!drm_mm_node_allocated(&vma->node))
 			continue;
 
@@ -3567,7 +3571,7 @@ int i915_gem_object_set_cache_level(struct drm_i915_gem_object *obj,
 			 */
 		}
 
-		list_for_each_entry(vma, &obj->vma_list, obj_link) {
+		list_for_each_entry(vma, &obj->vma.list, obj_link) {
 			if (!drm_mm_node_allocated(&vma->node))
 				continue;
 
@@ -3577,7 +3581,7 @@ int i915_gem_object_set_cache_level(struct drm_i915_gem_object *obj,
 		}
 	}
 
-	list_for_each_entry(vma, &obj->vma_list, obj_link)
+	list_for_each_entry(vma, &obj->vma.list, obj_link)
 		vma->node.color = cache_level;
 	i915_gem_object_set_cache_coherency(obj, cache_level);
 	obj->cache_dirty = true; /* Always invalidate stale cachelines */
@@ -4153,7 +4157,9 @@ void i915_gem_object_init(struct drm_i915_gem_object *obj,
 {
 	mutex_init(&obj->mm.lock);
 
-	INIT_LIST_HEAD(&obj->vma_list);
+	spin_lock_init(&obj->vma.lock);
+	INIT_LIST_HEAD(&obj->vma.list);
+
 	INIT_LIST_HEAD(&obj->lut_list);
 	INIT_LIST_HEAD(&obj->batch_pool_link);
 
@@ -4319,14 +4325,13 @@ static void __i915_gem_free_objects(struct drm_i915_private *i915,
 		mutex_lock(&i915->drm.struct_mutex);
 
 		GEM_BUG_ON(i915_gem_object_is_active(obj));
-		list_for_each_entry_safe(vma, vn,
-					 &obj->vma_list, obj_link) {
+		list_for_each_entry_safe(vma, vn, &obj->vma.list, obj_link) {
 			GEM_BUG_ON(i915_vma_is_active(vma));
 			vma->flags &= ~I915_VMA_PIN_MASK;
 			i915_vma_destroy(vma);
 		}
-		GEM_BUG_ON(!list_empty(&obj->vma_list));
-		GEM_BUG_ON(!RB_EMPTY_ROOT(&obj->vma_tree));
+		GEM_BUG_ON(!list_empty(&obj->vma.list));
+		GEM_BUG_ON(!RB_EMPTY_ROOT(&obj->vma.tree));
 
 		/* This serializes freeing with the shrinker. Since the free
 		 * is delayed, first by RCU then by the workqueue, we want the
diff --git a/drivers/gpu/drm/i915/i915_gem_object.h b/drivers/gpu/drm/i915/i915_gem_object.h
index cb1b0144d274..5a33b6d9f942 100644
--- a/drivers/gpu/drm/i915/i915_gem_object.h
+++ b/drivers/gpu/drm/i915/i915_gem_object.h
@@ -87,24 +87,33 @@ struct drm_i915_gem_object {
 
 	const struct drm_i915_gem_object_ops *ops;
 
-	/**
-	 * @vma_list: List of VMAs backed by this object
-	 *
-	 * The VMA on this list are ordered by type, all GGTT vma are placed
-	 * at the head and all ppGTT vma are placed at the tail. The different
-	 * types of GGTT vma are unordered between themselves, use the
-	 * @vma_tree (which has a defined order between all VMA) to find an
-	 * exact match.
-	 */
-	struct list_head vma_list;
-	/**
-	 * @vma_tree: Ordered tree of VMAs backed by this object
-	 *
-	 * All VMA created for this object are placed in the @vma_tree for
-	 * fast retrieval via a binary search in i915_vma_instance().
-	 * They are also added to @vma_list for easy iteration.
-	 */
-	struct rb_root vma_tree;
+	struct {
+		/**
+		 * @vma.lock: protect the list/tree of vmas
+		 */
+		struct spinlock lock;
+
+		/**
+		 * @vma.list: List of VMAs backed by this object
+		 *
+		 * The VMA on this list are ordered by type, all GGTT vma are
+		 * placed at the head and all ppGTT vma are placed at the tail.
+		 * The different types of GGTT vma are unordered between
+		 * themselves, use the @vma.tree (which has a defined order
+		 * between all VMA) to quickly find an exact match.
+		 */
+		struct list_head list;
+
+		/**
+		 * @vma.tree: Ordered tree of VMAs backed by this object
+		 *
+		 * All VMA created for this object are placed in the @vma.tree
+		 * for fast retrieval via a binary search in
+		 * i915_vma_instance(). They are also added to @vma.list for
+		 * easy iteration.
+		 */
+		struct rb_root tree;
+	} vma;
 
 	/**
 	 * @lut_list: List of vma lookup entries in use for this object.
diff --git a/drivers/gpu/drm/i915/i915_vma.c b/drivers/gpu/drm/i915/i915_vma.c
index dcbd0d345c72..d83b8ad5f859 100644
--- a/drivers/gpu/drm/i915/i915_vma.c
+++ b/drivers/gpu/drm/i915/i915_vma.c
@@ -187,32 +187,52 @@ vma_create(struct drm_i915_gem_object *obj,
 								i915_gem_object_get_stride(obj));
 		GEM_BUG_ON(!is_power_of_2(vma->fence_alignment));
 
-		/*
-		 * We put the GGTT vma at the start of the vma-list, followed
-		 * by the ppGGTT vma. This allows us to break early when
-		 * iterating over only the GGTT vma for an object, see
-		 * for_each_ggtt_vma()
-		 */
 		vma->flags |= I915_VMA_GGTT;
-		list_add(&vma->obj_link, &obj->vma_list);
-	} else {
-		list_add_tail(&vma->obj_link, &obj->vma_list);
 	}
 
+	spin_lock(&obj->vma.lock);
+
 	rb = NULL;
-	p = &obj->vma_tree.rb_node;
+	p = &obj->vma.tree.rb_node;
 	while (*p) {
 		struct i915_vma *pos;
+		long cmp;
 
 		rb = *p;
 		pos = rb_entry(rb, struct i915_vma, obj_node);
-		if (i915_vma_compare(pos, vm, view) < 0)
+
+		/*
+		 * If the view already exists in the tree, another thread
+		 * already created a matching vma, so return the older instance
+		 * and dispose of ours.
+		 */
+		cmp = i915_vma_compare(pos, vm, view);
+		if (cmp == 0) {
+			spin_unlock(&obj->vma.lock);
+			kmem_cache_free(vm->i915->vmas, vma);
+			return pos;
+		}
+
+		if (cmp < 0)
 			p = &rb->rb_right;
 		else
 			p = &rb->rb_left;
 	}
 	rb_link_node(&vma->obj_node, rb, p);
-	rb_insert_color(&vma->obj_node, &obj->vma_tree);
+	rb_insert_color(&vma->obj_node, &obj->vma.tree);
+
+	if (i915_vma_is_ggtt(vma))
+		/*
+		 * We put the GGTT vma at the start of the vma-list, followed
+		 * by the ppGGTT vma. This allows us to break early when
+		 * iterating over only the GGTT vma for an object, see
+		 * for_each_ggtt_vma()
+		 */
+		list_add(&vma->obj_link, &obj->vma.list);
+	else
+		list_add_tail(&vma->obj_link, &obj->vma.list);
+
+	spin_unlock(&obj->vma.lock);
 
 	mutex_lock(&vm->mutex);
 	list_add(&vma->vm_link, &vm->unbound_list);
@@ -232,7 +252,7 @@ vma_lookup(struct drm_i915_gem_object *obj,
 {
 	struct rb_node *rb;
 
-	rb = obj->vma_tree.rb_node;
+	rb = obj->vma.tree.rb_node;
 	while (rb) {
 		struct i915_vma *vma = rb_entry(rb, struct i915_vma, obj_node);
 		long cmp;
@@ -272,16 +292,18 @@ i915_vma_instance(struct drm_i915_gem_object *obj,
 {
 	struct i915_vma *vma;
 
-	lockdep_assert_held(&obj->base.dev->struct_mutex);
 	GEM_BUG_ON(view && !i915_is_ggtt(vm));
 	GEM_BUG_ON(vm->closed);
 
+	spin_lock(&obj->vma.lock);
 	vma = vma_lookup(obj, vm, view);
-	if (!vma)
+	spin_unlock(&obj->vma.lock);
+
+	/* vma_create() will resolve the race if another creates the vma */
+	if (unlikely(!vma))
 		vma = vma_create(obj, vm, view);
 
 	GEM_BUG_ON(!IS_ERR(vma) && i915_vma_compare(vma, vm, view));
-	GEM_BUG_ON(!IS_ERR(vma) && vma_lookup(obj, vm, view) != vma);
 	return vma;
 }
 
@@ -808,14 +830,18 @@ static void __i915_vma_destroy(struct i915_vma *vma)
 
 	GEM_BUG_ON(i915_gem_active_isset(&vma->last_fence));
 
-	list_del(&vma->obj_link);
-
 	mutex_lock(&vma->vm->mutex);
 	list_del(&vma->vm_link);
 	mutex_unlock(&vma->vm->mutex);
 
-	if (vma->obj)
-		rb_erase(&vma->obj_node, &vma->obj->vma_tree);
+	if (vma->obj) {
+		struct drm_i915_gem_object *obj = vma->obj;
+
+		spin_lock(&obj->vma.lock);
+		list_del(&vma->obj_link);
+		rb_erase(&vma->obj_node, &vma->obj->vma.tree);
+		spin_unlock(&obj->vma.lock);
+	}
 
 	rbtree_postorder_for_each_entry_safe(iter, n, &vma->active, node) {
 		GEM_BUG_ON(i915_gem_active_isset(&iter->base));
diff --git a/drivers/gpu/drm/i915/i915_vma.h b/drivers/gpu/drm/i915/i915_vma.h
index 4f7c1c7599f4..7252abc73d3e 100644
--- a/drivers/gpu/drm/i915/i915_vma.h
+++ b/drivers/gpu/drm/i915/i915_vma.h
@@ -425,7 +425,7 @@ void i915_vma_parked(struct drm_i915_private *i915);
  * or the list is empty ofc.
  */
 #define for_each_ggtt_vma(V, OBJ) \
-	list_for_each_entry(V, &(OBJ)->vma_list, obj_link)		\
+	list_for_each_entry(V, &(OBJ)->vma.list, obj_link)		\
 		for_each_until(!i915_vma_is_ggtt(V))
 
 #endif
diff --git a/drivers/gpu/drm/i915/selftests/i915_vma.c b/drivers/gpu/drm/i915/selftests/i915_vma.c
index f0a32edfb9b1..cf1de82741fa 100644
--- a/drivers/gpu/drm/i915/selftests/i915_vma.c
+++ b/drivers/gpu/drm/i915/selftests/i915_vma.c
@@ -672,7 +672,7 @@ static int igt_vma_partial(void *arg)
 		}
 
 		count = 0;
-		list_for_each_entry(vma, &obj->vma_list, obj_link)
+		list_for_each_entry(vma, &obj->vma.list, obj_link)
 			count++;
 		if (count != nvma) {
 			pr_err("(%s) All partial vma were not recorded on the obj->vma_list: found %u, expected %u\n",
@@ -701,7 +701,7 @@ static int igt_vma_partial(void *arg)
 		i915_vma_unpin(vma);
 
 		count = 0;
-		list_for_each_entry(vma, &obj->vma_list, obj_link)
+		list_for_each_entry(vma, &obj->vma.list, obj_link)
 			count++;
 		if (count != nvma) {
 			pr_err("(%s) allocated an extra full vma!\n", p->name);
-- 
2.20.1

_______________________________________________
Intel-gfx mailing list
Intel-gfx@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/intel-gfx

  parent reply	other threads:[~2019-01-21 22:21 UTC|newest]

Thread overview: 89+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-01-21 22:20 HWSP for HW semaphores Chris Wilson
2019-01-21 22:20 ` [PATCH 01/34] drm/i915/execlists: Mark up priority boost on preemption Chris Wilson
2019-01-21 22:20 ` [PATCH 02/34] drm/i915/execlists: Suppress preempting self Chris Wilson
2019-01-22 22:18   ` John Harrison
2019-01-22 22:38     ` Chris Wilson
2019-01-21 22:20 ` [PATCH 03/34] drm/i915: Show all active engines on hangcheck Chris Wilson
2019-01-22 12:33   ` Mika Kuoppala
2019-01-22 12:42     ` Chris Wilson
2019-01-21 22:20 ` [PATCH 04/34] drm/i915/selftests: Refactor common live_test framework Chris Wilson
2019-01-22 12:37   ` Matthew Auld
2019-01-21 22:20 ` [PATCH 05/34] drm/i915/selftests: Track evict objects explicitly Chris Wilson
2019-01-22 11:53   ` Matthew Auld
2019-01-21 22:20 ` [PATCH 06/34] drm/i915/selftests: Create a clean GGTT for vma/gtt selftesting Chris Wilson
2019-01-22 12:07   ` Matthew Auld
2019-01-21 22:20 ` [PATCH 07/34] drm/i915: Refactor out intel_context_init() Chris Wilson
2019-01-22 12:32   ` Matthew Auld
2019-01-22 12:39   ` Mika Kuoppala
2019-01-22 12:48     ` Chris Wilson
2019-01-21 22:20 ` [PATCH 08/34] drm/i915: Make all GPU resets atomic Chris Wilson
2019-01-22 22:19   ` John Harrison
2019-01-22 22:27     ` Chris Wilson
2019-01-23  8:52     ` Mika Kuoppala
2019-01-21 22:20 ` [PATCH 09/34] drm/i915/guc: Disable global reset Chris Wilson
2019-01-22 22:23   ` John Harrison
2019-01-21 22:20 ` [PATCH 10/34] drm/i915: Remove GPU reset dependence on struct_mutex Chris Wilson
2019-01-24 12:06   ` Mika Kuoppala
2019-01-24 12:50     ` Chris Wilson
2019-01-24 13:12       ` Chris Wilson
2019-01-24 14:10       ` Chris Wilson
2019-01-21 22:20 ` [PATCH 11/34] drm/i915/selftests: Trim struct_mutex duration for set-wedged selftest Chris Wilson
2019-01-21 22:20 ` [PATCH 12/34] drm/i915: Issue engine resets onto idle engines Chris Wilson
2019-01-23  1:18   ` John Harrison
2019-01-23  1:31     ` Chris Wilson
2019-01-21 22:20 ` [PATCH 13/34] drm/i915: Stop tracking MRU activity on VMA Chris Wilson
2019-01-21 22:20 ` [PATCH 14/34] drm/i915: Pull VM lists under the VM mutex Chris Wilson
2019-01-22  9:09   ` Tvrtko Ursulin
2019-01-21 22:20 ` Chris Wilson [this message]
2019-01-21 22:20 ` [PATCH 16/34] drm/i915: Always allocate an object/vma for the HWSP Chris Wilson
2019-01-21 22:21 ` [PATCH 17/34] drm/i915: Move list of timelines under its own lock Chris Wilson
2019-01-21 22:21 ` [PATCH 18/34] drm/i915/selftests: Use common mock_engine::advance Chris Wilson
2019-01-22  9:33   ` Tvrtko Ursulin
2019-01-21 22:21 ` [PATCH 19/34] drm/i915: Tidy common test_bit probing of i915_request->fence.flags Chris Wilson
2019-01-22  9:35   ` Tvrtko Ursulin
2019-01-21 22:21 ` [PATCH 20/34] drm/i915: Introduce concept of per-timeline (context) HWSP Chris Wilson
2019-01-23  1:35   ` John Harrison
2019-01-21 22:21 ` [PATCH 21/34] drm/i915: Enlarge vma->pin_count Chris Wilson
2019-01-21 22:21 ` [PATCH 22/34] drm/i915: Allocate a status page for each timeline Chris Wilson
2019-01-21 22:21 ` [PATCH 23/34] drm/i915: Share per-timeline HWSP using a slab suballocator Chris Wilson
2019-01-22 10:47   ` Tvrtko Ursulin
2019-01-22 11:12     ` Chris Wilson
2019-01-22 11:33       ` Tvrtko Ursulin
2019-01-21 22:21 ` [PATCH 24/34] drm/i915: Track the context's seqno in its own timeline HWSP Chris Wilson
2019-01-22 12:24   ` Tvrtko Ursulin
2019-01-21 22:21 ` [PATCH 25/34] drm/i915: Track active timelines Chris Wilson
2019-01-22 14:56   ` Tvrtko Ursulin
2019-01-22 15:17     ` Chris Wilson
2019-01-23 22:32       ` John Harrison
2019-01-23 23:08         ` Chris Wilson
2019-01-21 22:21 ` [PATCH 26/34] drm/i915: Identify active requests Chris Wilson
2019-01-22 15:34   ` Tvrtko Ursulin
2019-01-22 15:45     ` Chris Wilson
2019-01-21 22:21 ` [PATCH 27/34] drm/i915: Remove the intel_engine_notify tracepoint Chris Wilson
2019-01-22 15:50   ` Tvrtko Ursulin
2019-01-23 12:54     ` Chris Wilson
2019-01-23 13:18       ` Tvrtko Ursulin
2019-01-23 13:24         ` Chris Wilson
2019-01-21 22:21 ` [PATCH 28/34] drm/i915: Replace global breadcrumbs with per-context interrupt tracking Chris Wilson
2019-01-23  9:21   ` Tvrtko Ursulin
2019-01-23 10:01     ` Chris Wilson
2019-01-23 16:28       ` Tvrtko Ursulin
2019-01-23 11:41   ` [PATCH] " Chris Wilson
2019-01-21 22:21 ` [PATCH 29/34] drm/i915: Drop fake breadcrumb irq Chris Wilson
2019-01-24 17:55   ` Tvrtko Ursulin
2019-01-24 18:18     ` Chris Wilson
2019-01-21 22:21 ` [PATCH 30/34] drm/i915: Keep timeline HWSP allocated until the system is idle Chris Wilson
2019-01-21 22:37   ` Chris Wilson
2019-01-21 22:48     ` Chris Wilson
2019-01-21 22:21 ` [PATCH 31/34] drm/i915/execlists: Refactor out can_merge_rq() Chris Wilson
2019-01-21 22:21 ` [PATCH 32/34] drm/i915: Use HW semaphores for inter-engine synchronisation on gen8+ Chris Wilson
2019-01-21 22:21 ` [PATCH 33/34] drm/i915: Prioritise non-busywait semaphore workloads Chris Wilson
2019-01-23  0:33   ` Chris Wilson
2019-01-21 22:21 ` [PATCH 34/34] drm/i915: Replace global_seqno with a hangcheck heartbeat seqno Chris Wilson
2019-01-22  0:09 ` ✗ Fi.CI.CHECKPATCH: warning for series starting with [01/34] drm/i915/execlists: Mark up priority boost on preemption Patchwork
2019-01-22  0:22 ` ✗ Fi.CI.SPARSE: " Patchwork
2019-01-22  0:30 ` ✓ Fi.CI.BAT: success " Patchwork
2019-01-22  1:35 ` ✗ Fi.CI.IGT: failure " Patchwork
2019-01-23 12:00 ` ✗ Fi.CI.CHECKPATCH: warning for series starting with [01/34] drm/i915/execlists: Mark up priority boost on preemption (rev2) Patchwork
2019-01-23 12:11 ` ✗ Fi.CI.SPARSE: " Patchwork
2019-01-23 12:48 ` ✗ Fi.CI.BAT: failure " Patchwork

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=20190121222117.23305-16-chris@chris-wilson.co.uk \
    --to=chris@chris-wilson.co.uk \
    --cc=intel-gfx@lists.freedesktop.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.