Netdev List
 help / color / mirror / Atom feed
From: Paolo Abeni <pabeni@redhat.com>
To: sukhdeeps@marvell.com
Cc: netdev@vger.kernel.org, irusskikh@marvell.com,
	epomozov@marvell.com, richardcochran@gmail.com,
	andrew+netdev@lunn.ch, davem@davemloft.net, edumazet@google.com,
	kuba@kernel.org, pabeni@redhat.com, linux-kernel@vger.kernel.org
Subject: Re: [PATCH net-next 6/9] net: atlantic: implement AQC113 L2/L3/L4 RX filter management filter management management
Date: Tue, 12 May 2026 11:54:00 +0200	[thread overview]
Message-ID: <20260512095400.134912-1-pabeni@redhat.com> (raw)
In-Reply-To: <20260506135706.2834-7-sukhdeeps@marvell.com>

From: AI Reviewer <ai@example.com>

This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
Full review at: https://netdev-ai.bots.linux.dev/sashiko/
---
Now I have enough context to produce the report.

commit ba73e7cd3452a0a57ac522b11a156ea2b727e473
Author: Sukhdeep Singh <sukhdeeps@marvell.com>
Subject: net: atlantic: implement AQC113 L2/L3/L4 RX filter management filter management management

This commit wires up L2/L3/L4 RX filter management for AQC113 hardware,
introducing reference-counted tag sharing of L3 and L4 filters, a combined
L3L4 configuration path that drives Action Resolver Table entries, an L2
ethertype set/clear pair, and a MAC-setup routine keyed off a firmware
reported L2 filter base index. It also rewires several entries in
hw_atl2_ops and updates the initialization of the new RX filter path.

> net: atlantic: implement AQC113 L2/L3/L4 RX filter management filter management management

The subject line appears to have a copy/paste slip: "filter management"
is repeated and "management" is tripled.

> diff --git a/drivers/net/ethernet/aquantia/atlantic/aq_hw.h b/drivers/net/ethernet/aquantia/atlantic/aq_hw.h
> index 57ea59026a2c..04fb87d4e56d 100644
> --- a/drivers/net/ethernet/aquantia/atlantic/aq_hw.h
> +++ b/drivers/net/ethernet/aquantia/atlantic/aq_hw.h
> @@ -236,6 +236,8 @@ struct aq_hw_ops {
>  
>  	int (*hw_stop)(struct aq_hw_s *self);
>  
> +	u32 (*hw_get_version)(struct aq_hw_s *self);
> +

Is hw_get_version used anywhere in this series?  A grep across the driver
shows only the ops vtable assignment and this struct field; no ethtool,
debugfs, or regs-dump code calls self->aq_hw_ops->hw_get_version().
Should this op be introduced together with its consumer, or dropped from
this patch?

> diff --git a/drivers/net/ethernet/aquantia/atlantic/hw_atl2/hw_atl2.c b/drivers/net/ethernet/aquantia/atlantic/hw_atl2/hw_atl2.c
> index 0ce9caae8799..e58bfff38670 100644
> --- a/drivers/net/ethernet/aquantia/atlantic/hw_atl2/hw_atl2.c
> +++ b/drivers/net/ethernet/aquantia/atlantic/hw_atl2/hw_atl2.c

[ ... ]

> @@ -380,6 +422,9 @@ static void hw_atl2_hw_init_new_rx_filters(struct aq_hw_s *self)
>  {
>  	u8 *prio_tc_map = self->aq_nic_cfg->prio_tc_map;
>  	struct hw_atl2_priv *priv = self->priv;
> +	u32 art_first_sec, art_last_sec;
> +	u32 art_sections;
> +	u32 art_mask = 0;
>  	u16 action;
>  	u8 index;
>  	int i;
> @@ -394,9 +439,14 @@ static void hw_atl2_hw_init_new_rx_filters(struct aq_hw_s *self)
>  	 * REC entry is used for further processing. If multiple entries match,
>  	 * the lowest REC entry, Action field will be selected.
>  	 */
> -	hw_atl2_rpf_act_rslvr_section_en_set(self, 0xFFFF);
> +	art_last_sec = priv->art_base_index / 8 + priv->art_count / 8;
> +	art_first_sec = priv->art_base_index / 8;
> +	art_mask = (BIT(art_last_sec) - 1) - (BIT(art_first_sec) - 1);

Are art_first_sec and art_last_sec bounded before being passed to BIT()?

priv->art_base_index and priv->art_count come from firmware via
hw_atl2_utils_get_action_resolve_table_caps() (rslv_tbl_base_index and
rslv_tbl_count are 8-bit firmware fields).  BIT(nr) expands to
UL(1) << nr, so values that make art_last_sec >= BITS_PER_LONG are C
undefined behavior.

The consumer register RPF_REC_TAB_EN is 16 bits wide
(HW_ATL2_RPF_REC_TAB_EN_MSK = 0xFFFF), so values beyond 16 also silently
produce a bogus mask even if the shift does not invoke UB.

Could the firmware-reported caps be validated or clamped here?

> +	art_sections = hw_atl2_rpf_act_rslvr_section_en_get(self) | art_mask;
> +	hw_atl2_rpf_act_rslvr_section_en_set(self, art_sections);
> +	hw_atl2_rpf_l3_v6_v4_select_set(self, 1);
>  	hw_atl2_rpfl2_uc_flr_tag_set(self, HW_ATL2_RPF_TAG_BASE_UC,
> -				     HW_ATL2_MAC_UC);
> +				     priv->l2_filters_base_index);
>  	hw_atl2_rpfl2_bc_flr_tag_set(self, HW_ATL2_RPF_TAG_BASE_UC);

[ ... ]

> +static int hw_atl2_hw_mac_addr_set(struct aq_hw_s *self, const u8 *mac_addr)
> +{
> +	struct hw_atl2_priv *priv = self->priv;
> +	u32 location = priv->l2_filters_base_index;
> +	unsigned int h = 0U;
> +	unsigned int l = 0U;
> +	int err = 0;
> +
> +	if (!mac_addr) {
> +		err = -EINVAL;
> +		goto err_exit;
> +	}
> +	h = (mac_addr[0] << 8) | (mac_addr[1]);
> +	l = (mac_addr[2] << 24) | (mac_addr[3] << 16) |
> +		(mac_addr[4] << 8) | mac_addr[5];
> +
> +	hw_atl_rpfl2_uc_flr_en_set(self, 0U, location);
> +	hw_atl_rpfl2unicast_dest_addresslsw_set(self, l, location);
> +	hw_atl_rpfl2unicast_dest_addressmsw_set(self, h, location);
> +	hw_atl_rpfl2unicast_flr_act_set(self, 1U, location);
> +	hw_atl2_rpfl2_uc_flr_tag_set(self, HW_ATL2_RPF_TAG_BASE_UC, location);
> +	hw_atl_rpfl2_uc_flr_en_set(self, 1U, location);
> +
> +	err = aq_hw_err_from_flags(self);
> +
> +err_exit:
> +	return err;
> +}

Is the MAC-address programming path self-consistent at init when
priv->l2_filters_base_index != 0?

hw_atl2_hw_init() continues to call hw_atl_b0_hw_mac_addr_set(self,
mac_addr), which writes slot HW_ATL_B0_MAC (=0) with no UC tag.

hw_atl2_hw_init_new_rx_filters() tags slot priv->l2_filters_base_index
with HW_ATL2_RPF_TAG_BASE_UC but does not write a MAC into it.

The new hw_atl2_hw_mac_addr_set() only runs when userspace issues
ndo_set_mac_address.

Does this leave the device with slot 0 enabled and carrying the initial
MAC but no UC tag, while slot priv->l2_filters_base_index has the UC tag
but no address?  The commit message describes this as "Add MAC address
setup using firmware-provided L2 filter base index" but
hw_atl2_hw_init() still routes to the hardcoded slot.

> +static int hw_atl2_new_fl3l4_configure(struct aq_hw_s *self,
> +				       struct aq_rx_filter_l3l4 *data)
> +{
> +	struct hw_atl2_priv *priv = self->priv;
> +	s8 old_l3_index = priv->l3l4_filters[data->location].l3_index;
> +	s8 old_l4_index = priv->l3l4_filters[data->location].l4_index;
> +	u8 old_ipv6 = priv->l3l4_filters[data->location].ipv6;
> +	struct hw_atl2_l3_filter *l3_filters;

[ ... ]

> +	/* find L3 and L4 filters */
> +	if (l3.cmd & (HW_ATL2_RPF_L3_CMD_EN | HW_ATL2_RPF_L3_V6_CMD_EN)) {
> +		l3_idx = hw_atl2_new_fl3l4_find_l3(self, &l3);
> +		if (l3_idx < 0)
> +			return l3_idx;
> +
> +		if (l3.cmd & HW_ATL2_RPF_L3_V6_CMD_EN)
> +			l3_filters = priv->l3_v6_filters;
> +		else
> +			l3_filters = priv->l3_v4_filters;
> +
> +		if (priv->l3l4_filters[data->location].l3_index != l3_idx)
> +			hw_atl2_rxf_l3_get(self, &l3_filters[l3_idx],
> +					   l3_idx, &l3);
> +	}
> +
> +	if (old_l3_index != -1) {
> +		if (old_ipv6)
> +			l3_filters = priv->l3_v6_filters;
> +		else
> +			l3_filters = priv->l3_v4_filters;
> +
> +		if (!(hw_atl2_rxf_l3_is_equal(&l3,
> +					      &l3_filters[old_l3_index]))) {
> +			hw_atl2_rxf_l3_put(self,
> +					   &l3_filters[old_l3_index],
> +					   old_l3_index);
> +		}
> +	}

Note that l3_filters has been reassigned here based on old_ipv6.

> +	if (l3.cmd & HW_ATL2_RPF_L3_V6_CMD_EN)
> +		priv->l3l4_filters[data->location].ipv6 = 1;
> +	else
> +		priv->l3l4_filters[data->location].ipv6 = 0;
> +	priv->l3l4_filters[data->location].l3_index = l3_idx;
> +
> +	if (l4.cmd & HW_ATL2_RPF_L4_CMD_EN) {
> +		l4_idx = hw_atl2_new_fl3l4_find_l4(self, &l4);
> +		if (l4_idx < 0) {
> +			/* Undo L3 acquisition */
> +			if (l3_idx >= 0) {
> +				hw_atl2_rxf_l3_put(self, &l3_filters[l3_idx], l3_idx);
> +				priv->l3l4_filters[data->location].l3_index = old_l3_index;
> +				priv->l3l4_filters[data->location].ipv6 = old_ipv6;
> +			}
> +			return -EINVAL;
> +		}

Can this rollback corrupt the L3 refcounts?

If the new filter is IPv4 and the old one was IPv6 (or vice versa),
l3_filters points at the old family's array by the time this undo runs,
but l3_idx was allocated from the new family:

    if (l3.cmd & HW_ATL2_RPF_L3_V6_CMD_EN)
        l3_filters = priv->l3_v6_filters;
    else
        l3_filters = priv->l3_v4_filters;
    ...
    /* later overwritten using old_ipv6 */
    if (old_ipv6)
        l3_filters = priv->l3_v6_filters;
    else
        l3_filters = priv->l3_v4_filters;
    ...
    hw_atl2_rxf_l3_put(self, &l3_filters[l3_idx], l3_idx);

This decrements usage in the wrong array and, on reaching zero, clears
the wrong hardware register via hw_atl2_rpf_l3_v{4,6}_cmd_set().

Also, earlier in the function the old L3 may already have been put when
its content differed from the new L3.  The rollback does not re-get() the
old L3, so priv->l3l4_filters[data->location].l3_index is restored to
old_l3_index while the corresponding slot has already been cleared if
its usage was 1.

Finally, the new L3 get() is conditional:

    if (priv->l3l4_filters[data->location].l3_index != l3_idx)
        hw_atl2_rxf_l3_get(self, &l3_filters[l3_idx], l3_idx, &l3);

but the rollback's put() is unconditional on l3_idx >= 0.  Re-submitting
the same filter to the same location would skip the get(); a subsequent
L4 failure then puts a reference the call never took, potentially
underflowing the shared slot's usage count and clearing a filter another
location still relies on.

Could this rollback path track whether get() was actually performed, and
either hold on to the matched l3_filters pointer from the new family or
re-derive it from the new l3.cmd instead of old_ipv6?

> +
> +		if (priv->l3l4_filters[data->location].l4_index != l4_idx)
> +			hw_atl2_rxf_l4_get(self, &priv->l4_filters[l4_idx],
> +					   l4_idx, &l4);
> +	}

[ ... ]

> +static int hw_atl2_new_fl3l4_find_l3(struct aq_hw_s *self,
> +				     struct hw_atl2_l3_filter *l3)
> +{
> +	struct hw_atl2_priv *priv = self->priv;
> +	struct hw_atl2_l3_filter *l3_filters;
> +	int i, first, last;
> +
> +	if (l3->cmd & HW_ATL2_RPF_L3_V6_CMD_EN) {
> +		l3_filters = priv->l3_v6_filters;
> +		first = priv->l3_v6_filter_base_index;
> +		last = priv->l3_v6_filter_base_index +
> +		       priv->l3_v6_filter_count;
> +	} else {
> +		l3_filters = priv->l3_v4_filters;
> +		first = priv->l3_v4_filter_base_index;
> +		last = priv->l3_v4_filter_base_index +
> +		       priv->l3_v4_filter_count;
> +	}
> +	for (i = first; i < last; i++) {
> +		if (hw_atl2_rxf_l3_is_equal(&l3_filters[i], l3))
> +			return i;
> +	}

Can this index the static arrays out of bounds?

The backing arrays are fixed size:

    struct hw_atl2_l3_filter l3_v4_filters[HW_ATL2_RPF_L3L4_FILTERS];
    struct hw_atl2_l3_filter l3_v6_filters[HW_ATL2_RPF_L3L4_FILTERS];
    struct hw_atl2_l4_filter l4_filters[HW_ATL2_RPF_L3L4_FILTERS];

but l3_v4_filter_base_index, l3_v6_filter_base_index, l4_filter_base_index
and the corresponding counts come from firmware.  Looking at
hw_atl2_utils_get_filter_caps() (referenced elsewhere in the series),
l3_v6_filter_count is not clamped at all, unlike l3_v4_filter_count and
l4_filter_count which get reduced by one against
HW_ATL2_RPF_L3V4_FILTERS / HW_ATL2_RPF_L4_FILTERS.  None of the
base_index fields are validated against the fixed-size arrays either.

A firmware value that places first+count past HW_ATL2_RPF_L3L4_FILTERS
yields out-of-bounds reads here and out-of-bounds writes via
hw_atl2_rxf_l3_get() / _put().  The same concern applies to
hw_atl2_new_fl3l4_find_l4().

Could these indices be sanitized at probe time?

[ ... ]

> +static int hw_atl2_hw_fl2_set(struct aq_hw_s *self,
> +			      struct aq_rx_filter_l2 *data)
> +{
> +	struct hw_atl2_priv *priv = self->priv;
> +	u32 mask = HW_ATL2_RPF_TAG_ET_MASK;
> +	u32 req_tag = 0;
> +	u16 action = 0;
> +	u32 location;
> +	u8 index;
> +	int tag;
> +
> +	location = priv->etype_filter_base_index + data->location;
> +	hw_atl_rpf_etht_flr_set(self, data->ethertype, location);
> +	hw_atl_rpf_etht_user_priority_en_set(self,
> +					     !!data->user_priority_en,
> +					     location);
> +	if (data->user_priority_en) {
> +		hw_atl_rpf_etht_user_priority_set(self,
> +						  data->user_priority,
> +						  location);
> +		req_tag |= data->user_priority << HW_ATL2_RPF_TAG_PCP_OFFSET;
> +		mask |= HW_ATL2_RPF_TAG_PCP_MASK;
> +	}
> +
> +	if (data->queue < 0) {
> +		hw_atl_rpf_etht_flr_act_set(self, 0U, location);
> +		hw_atl_rpf_etht_rx_queue_en_set(self, 0U, location);
> +		action = HW_ATL2_ACTION_DROP;
> +	} else {
> +		hw_atl_rpf_etht_flr_act_set(self, 1U, location);
> +		hw_atl_rpf_etht_rx_queue_en_set(self, 1U, location);
> +		hw_atl_rpf_etht_rx_queue_set(self, data->queue, location);
> +		action = HW_ATL2_ACTION_ASSIGN_QUEUE(data->queue);
> +	}
> +
> +	tag = hw_atl2_filter_tag_get(priv->etype_policy,
> +				     priv->etype_filter_tag_top,
> +				     action);
> +
> +	if (tag < 0)
> +		return -ENOSPC;

Two concerns about this path:

First, if location already held an ethertype filter with a previous tag,
is that tag ever released?

aq_add_del_fether() in aq_filters.c calls hw_filter_l2_set() directly on
replacement (without an intervening hw_filter_l2_clear()), so each
overwrite of an existing location invokes hw_atl2_filter_tag_get() for
the new action but never hw_atl2_filter_tag_put() for the old one.  The
etype_policy[] usage counters will diverge from the set of tags actually
live in hardware, and after a small number of changes with distinct
actions the pool (bounded by etype_filter_tag_top) is exhausted and
set() starts returning -ENOSPC.

Second, on the -ENOSPC path above, the per-location ethertype,
user_priority_en/user_priority, flr_act, rx_queue_en and rx_queue
registers have already been written for the new filter while the old
etht_flr_en bit and the old tag are still set in hardware.  Should this
error path leave the hardware state unchanged, or disable the filter
before returning?

> +
> +	req_tag |= tag << HW_ATL2_RPF_TAG_ET_OFFSET;
> +	hw_atl2_rpf_etht_flr_tag_set(self, tag, location);
> +	index = priv->art_base_index + HW_ATL2_RPF_ET_PCP_USER_INDEX +
> +		data->location;
> +	hw_atl2_act_rslvr_table_set(self, index, req_tag, mask, action);
> +
> +	hw_atl_rpf_etht_flr_en_set(self, 1U, location);
> +
> +	return aq_hw_err_from_flags(self);
> +}

[ ... ]

> @@ -825,7 +1394,7 @@ static int hw_atl2_hw_vlan_ctrl(struct aq_hw_s *self, bool enable)
>  const struct aq_hw_ops hw_atl2_ops = {
>  	.hw_soft_reset        = hw_atl2_utils_soft_reset,
>  	.hw_prepare           = hw_atl2_utils_initfw,
> -	.hw_set_mac_address   = hw_atl_b0_hw_mac_addr_set,
> +	.hw_set_mac_address   = hw_atl2_hw_mac_addr_set,
>  	.hw_init              = hw_atl2_hw_init,
>  	.hw_reset             = hw_atl2_hw_reset,
>  	.hw_start             = hw_atl_b0_hw_start,
> @@ -834,6 +1403,7 @@ const struct aq_hw_ops hw_atl2_ops = {
>  	.hw_ring_rx_start     = hw_atl_b0_hw_ring_rx_start,
>  	.hw_ring_rx_stop      = hw_atl_b0_hw_ring_rx_stop,
>  	.hw_stop              = hw_atl2_hw_stop,
> +	.hw_get_version       = hw_atl2_get_hw_version,

See the earlier note; hw_get_version does not appear to have any caller
in this series.
-- 
This is an AI-generated review.


  parent reply	other threads:[~2026-05-12  9:54 UTC|newest]

Thread overview: 35+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-06 13:56 [PATCH net-next 0/9] net: atlantic: add PTP support for AQC113 (Antigua) sukhdeeps
2026-05-06 13:56 ` [PATCH net-next 1/9] net: atlantic: correct L3L4 filter flow_type masking and IPv6 handling masking and IPv6 handling sukhdeeps
2026-05-06 13:56 ` [PATCH net-next 2/9] net: atlantic: move active_ipv4/ipv6 bitmap updates after HW write updates after HW write sukhdeeps
2026-05-12  9:53   ` Paolo Abeni
2026-05-06 13:57 ` [PATCH net-next 3/9] net: atlantic: decouple aq_set_data_fl3l4() from driver internals driver internals sukhdeeps
2026-05-06 13:57 ` [PATCH net-next 4/9] net: atlantic: add AQC113 hardware register definitions and accessors definitions and accessors sukhdeeps
2026-05-06 13:57 ` [PATCH net-next 5/9] net: atlantic: add AQC113 filter data structures and firmware query and firmware query firmware query sukhdeeps
2026-05-12  9:53   ` Paolo Abeni
2026-05-06 13:57 ` [PATCH net-next 6/9] net: atlantic: implement AQC113 L2/L3/L4 RX filter management filter management management sukhdeeps
2026-05-06 22:43   ` Vadim Fedorenko
2026-05-08  6:56     ` [EXTERNAL] " Sukhdeep Soni [C]
2026-05-12  9:54   ` Paolo Abeni [this message]
2026-05-06 13:57 ` [PATCH net-next 7/9] net: atlantic: add AQC113 PTP traffic class and TX path setup TX path setup sukhdeeps
2026-05-12  9:54   ` Paolo Abeni
2026-05-06 13:57 ` [PATCH net-next 8/9] net: atlantic: extend hw_ops and TX descriptor for AQC113 PTP for AQC113 PTP sukhdeeps
2026-05-06 13:57 ` [PATCH net-next 9/9] net: atlantic: add PTP support for AQC113 (Antigua) (Antigua) sukhdeeps
2026-05-12  9:54   ` Paolo Abeni
2026-05-08 12:01 ` [PATCH net-next v2 0/9] net: atlantic: add PTP support for AQC113 (Antigua) sukhdeeps
2026-05-08 12:01   ` [PATCH net-next v2 1/9] net: atlantic: correct L3L4 filter flow_type masking and IPv6 handling masking and IPv6 handling sukhdeeps
2026-05-08 12:01   ` [PATCH net-next v2 2/9] net: atlantic: move active_ipv4/ipv6 bitmap updates after HW write updates after HW write sukhdeeps
2026-05-08 12:01   ` [PATCH net-next v2 3/9] net: atlantic: decouple aq_set_data_fl3l4() from driver internals driver internals sukhdeeps
2026-05-08 12:01   ` [PATCH net-next v2 4/9] net: atlantic: add AQC113 hardware register definitions and accessors definitions and accessors sukhdeeps
2026-05-08 12:01   ` [PATCH net-next v2 5/9] net: atlantic: add AQC113 filter data structures and firmware query and firmware query firmware query sukhdeeps
2026-05-08 12:01   ` [PATCH net-next v2 6/9] net: atlantic: implement AQC113 L2/L3/L4 RX filter management filter management management sukhdeeps
2026-05-12 10:00     ` Paolo Abeni
2026-05-12 10:04     ` Paolo Abeni
2026-05-08 12:01   ` [PATCH net-next v2 7/9] net: atlantic: add AQC113 PTP traffic class and TX path setup TX path setup sukhdeeps
2026-05-08 12:01   ` [PATCH net-next v2 8/9] net: atlantic: extend hw_ops and TX descriptor for AQC113 PTP for AQC113 PTP sukhdeeps
2026-05-08 12:01   ` [PATCH net-next v2 9/9] net: atlantic: add PTP support for AQC113 (Antigua) (Antigua) sukhdeeps
2026-05-12 10:01     ` Paolo Abeni
2026-05-12 10:17     ` Paolo Abeni
2026-05-08 23:06   ` [PATCH net-next v2 0/9] net: atlantic: add PTP support for AQC113 (Antigua) Jakub Kicinski
2026-05-11 12:26     ` [EXTERNAL] " Sukhdeep Soni [C]
2026-05-11 23:50       ` Jakub Kicinski
2026-05-12 11:27   ` Simon Horman

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=20260512095400.134912-1-pabeni@redhat.com \
    --to=pabeni@redhat.com \
    --cc=andrew+netdev@lunn.ch \
    --cc=davem@davemloft.net \
    --cc=edumazet@google.com \
    --cc=epomozov@marvell.com \
    --cc=irusskikh@marvell.com \
    --cc=kuba@kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=netdev@vger.kernel.org \
    --cc=richardcochran@gmail.com \
    --cc=sukhdeeps@marvell.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