Date: Fri, 07 Aug 2026 12:45:48 +0000 From: Kevin Bowling <kbowling@FreeBSD.org> To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org Subject: git: 33fdcdb18eb6 - main - ixgbe: complete PF cleanup after VF FLR Message-ID: <6a75d37c.38b6f.2e1eec7d@gitrepo.freebsd.org>
index | next in thread | raw e-mail
The branch main has been updated by kbowling: URL: https://cgit.FreeBSD.org/src/commit/?id=33fdcdb18eb61f089b9a4786ef7aa9224ad0722c commit 33fdcdb18eb61f089b9a4786ef7aa9224ad0722c Author: Kevin Bowling <kbowling@FreeBSD.org> AuthorDate: 2026-08-01 06:03:17 +0000 Commit: Kevin Bowling <kbowling@FreeBSD.org> CommitDate: 2026-08-07 12:45:43 +0000 ixgbe: complete PF cleanup after VF FLR The 82599, X540, and X550 documentation identifies VF registers which retain state across VFLR and must be reconfigured before a VF is reused. The VF reset path already initializes its queue-owned registers, but the PF only cleared VF mailbox memory and transmit head write-back addresses after a cooperative mailbox reset. A bare hardware VFLR therefore left both behind on affected devices. Move TDWBA cleanup into the common reset path. Clear CTS when VFLR invalidates the mailbox session, and accept only VF_RESET during the reset pass before restoring VF traffic. Clear VFMBMEM through the PFU/VFU semaphore. Recheck VFREQ while holding PFU so a reset event cannot erase a request posted between the initial mailbox check and the clear. Dispatch an already-read message even if the residual clear fails, but keep cleanup pending until a synchronized clear succeeds. Retry cleanup in the same admin pass after a failed message read or clear. The 82599 also retains VFMAILBOX.VFU across VFLR. Leave a VF-owned mailbox intact initially so a live post-reset writer can finish. Retry cleanup from the admin timer and, after a two-second grace period, use PFMAILBOX.RVFU only when VFU remains set and no request has been posted. Clear the mailbox under PFU afterward. This recovers an abandoned pre-reset owner without sleeping under the iflib context lock or immediately stealing from a new reset request. Suppress mailbox dispatch once iflib has cleared IFF_DRV_RUNNING so a pending reset request cannot re-enable VF traffic inside the PF stop path. Periodically sample aggregate VFREQ, VFACK, and VFLR registers, masked to active VFs, so work suppressed across a stop/restart and a bare 82599 VFLR without EICR_MAILBOX are both discovered without another interrupt edge. MFC after: 2 weeks --- sys/dev/ixgbe/if_ix.c | 9 ++- sys/dev/ixgbe/if_sriov.c | 190 +++++++++++++++++++++++++++++++++++++------- sys/dev/ixgbe/ixgbe.h | 2 + sys/dev/ixgbe/ixgbe_mbx.c | 66 +++++++++++++-- sys/dev/ixgbe/ixgbe_mbx.h | 1 + sys/dev/ixgbe/ixgbe_sriov.h | 2 + sys/dev/ixgbe/ixgbe_vf.c | 4 +- 7 files changed, 237 insertions(+), 37 deletions(-) diff --git a/sys/dev/ixgbe/if_ix.c b/sys/dev/ixgbe/if_ix.c index f800be109fc3..d18f60984b8f 100644 --- a/sys/dev/ixgbe/if_ix.c +++ b/sys/dev/ixgbe/if_ix.c @@ -4831,7 +4831,14 @@ ixgbe_if_update_admin_status(if_ctx_t ctx) ixgbe_handle_mod(ctx); if (sc->task_requests & IXGBE_REQUEST_TASK_MSF) ixgbe_handle_msf(ctx); - if (sc->task_requests & IXGBE_REQUEST_TASK_MBX) + /* + * A reset request re-enables VF traffic, so do not service mailboxes + * while the PF is stopped. VFREQ, VFACK, and VFLR are hardware-latched + * and ixgbe_mbx_pending() resamples them after the PF is running again. + */ + if ((if_getdrvflags(iflib_get_ifp(ctx)) & IFF_DRV_RUNNING) != 0 && + ((sc->task_requests & IXGBE_REQUEST_TASK_MBX) != 0 || + sc->iov_mbx_cleanup_pending || ixgbe_mbx_pending(sc))) ixgbe_handle_mbx(ctx); if (sc->task_requests & IXGBE_REQUEST_TASK_FDIR) ixgbe_reinit_fdir(ctx); diff --git a/sys/dev/ixgbe/if_sriov.c b/sys/dev/ixgbe/if_sriov.c index b2325046d5e8..848a53df290c 100644 --- a/sys/dev/ixgbe/if_sriov.c +++ b/sys/dev/ixgbe/if_sriov.c @@ -41,6 +41,8 @@ MALLOC_DEFINE(M_IXGBE_SRIOV, "ix_sriov", "ix SR-IOV allocations"); +#define IXGBE_VF_MBX_CLEANUP_GRACE (2 * SBT_1S) + /************************************************************************ * ixgbe_define_iov_schemas ************************************************************************/ @@ -521,9 +523,13 @@ ixgbe_vf_frame_size_compatible(struct ixgbe_softc *sc, struct ixgbe_vf *vf) static void ixgbe_process_vf_reset(struct ixgbe_softc *sc, struct ixgbe_vf *vf) { + struct ixgbe_hw *hw; bool rebuild_mta; s32 error; + int i, queue_count; + hw = &sc->hw; + vf->flags &= ~IXGBE_VF_CTS; rebuild_mta = vf->num_mc_hashes != 0; vf->xcast_mode = IXGBEVF_XCAST_MODE_NONE; vf->num_mc_hashes = 0; @@ -537,9 +543,18 @@ ixgbe_process_vf_reset(struct ixgbe_softc *sc, struct ixgbe_vf *vf) ixgbe_iov_rebuild_mta(sc); ixgbe_vf_clear_mac_filters(sc, vf, true); - ixgbe_clear_rar(&sc->hw, vf->rar_index); + ixgbe_clear_rar(hw, vf->rar_index); ixgbe_vf_set_anti_spoof(sc, vf); - ixgbe_toggle_txdctl(&sc->hw, vf->pool); + ixgbe_toggle_txdctl(hw, vf->pool); + + /* VFLR does not clear transmit head write-back state. */ + queue_count = ixgbe_vf_queues(sc->iov_mode); + for (i = 0; i < queue_count; i++) { + IXGBE_WRITE_REG(hw, + IXGBE_PVFTDWBAHn(queue_count, vf->pool, i), 0); + IXGBE_WRITE_REG(hw, + IXGBE_PVFTDWBALn(queue_count, vf->pool, i), 0); + } vf->api_ver = IXGBE_API_VER_UNKNOWN; } /* ixgbe_process_vf_reset */ @@ -584,28 +599,10 @@ ixgbe_vf_reset_msg(struct ixgbe_softc *sc, struct ixgbe_vf *vf, uint32_t *msg) struct ixgbe_hw *hw; uint32_t ack; uint32_t resp[IXGBE_VF_PERMADDR_MSG_LEN]; - int i, queue_count; hw = &sc->hw; ixgbe_process_vf_reset(sc, vf); - /* - * The reset request was consumed by ixgbe_process_vf_msg(), so it is - * now safe to clear this VF's mailbox. - */ - ixgbe_clear_mbx(hw, vf->pool); - - /* - * VF reset does not clear the transmit head write-back addresses. - * The queues were disabled by ixgbe_process_vf_reset(). - */ - queue_count = ixgbe_vf_queues(sc->iov_mode); - for (i = 0; i < queue_count; i++) { - IXGBE_WRITE_REG(hw, - IXGBE_PVFTDWBAHn(queue_count, vf->pool, i), 0); - IXGBE_WRITE_REG(hw, - IXGBE_PVFTDWBALn(queue_count, vf->pool, i), 0); - } if (ixgbe_validate_mac_addr(vf->ether_addr) == 0) { ixgbe_set_rar(&sc->hw, vf->rar_index, vf->ether_addr, @@ -936,8 +933,8 @@ ixgbe_vf_get_queues(struct ixgbe_softc *sc, struct ixgbe_vf *vf, } /* ixgbe_vf_get_queues */ -static void -ixgbe_process_vf_msg(if_ctx_t ctx, struct ixgbe_vf *vf) +static bool +ixgbe_process_vf_msg(if_ctx_t ctx, struct ixgbe_vf *vf, bool reset_pending) { struct ixgbe_softc *sc = iflib_get_softc(ctx); #ifdef KTR @@ -945,25 +942,39 @@ ixgbe_process_vf_msg(if_ctx_t ctx, struct ixgbe_vf *vf) #endif struct ixgbe_hw *hw; uint32_t msg[IXGBE_VFMAILBOX_SIZE]; + bool cleanup_complete; int error; hw = &sc->hw; + cleanup_complete = true; error = ixgbe_read_mbx(hw, msg, IXGBE_VFMAILBOX_SIZE, vf->pool); if (error != 0) - return; + return (false); + /* + * Some devices do not clear VFMBMEM on VFLR. Copy a pending request + * first because the VF posts its mailbox reset request after raising + * the reset event. + */ + if (reset_pending || msg[0] == IXGBE_VF_RESET) + cleanup_complete = + ixgbe_clear_mbx(hw, vf->pool) == IXGBE_SUCCESS; + /* The successful read ACKed the request; dispatch it even if not clear. */ CTR3(KTR_MALLOC, "%s: received msg %x from %d", if_name(ifp), msg[0], vf->pool); if (msg[0] == IXGBE_VF_RESET) { ixgbe_vf_reset_msg(sc, vf, msg); - return; + return (cleanup_complete); } + /* Discard requests from the mailbox session invalidated by VFLR. */ + if (reset_pending) + return (cleanup_complete); if (!(vf->flags & IXGBE_VF_CTS)) { ixgbe_send_vf_failure(sc, vf, msg[0]); - return; + return (true); } switch (msg[0] & IXGBE_VT_MSG_MASK) { @@ -994,8 +1005,39 @@ ixgbe_process_vf_msg(if_ctx_t ctx, struct ixgbe_vf *vf) default: ixgbe_send_vf_failure(sc, vf, msg[0]); } + return (true); } /* ixgbe_process_vf_msg */ +static void +ixgbe_cleanup_vf_mbx(struct ixgbe_softc *sc, struct ixgbe_vf *vf) +{ + struct ixgbe_hw *hw; + sbintime_t now; + + hw = &sc->hw; + if (ixgbe_clear_mbx(hw, vf->pool) == IXGBE_SUCCESS) { + vf->flags &= ~IXGBE_VF_MBX_CLEANUP; + return; + } + + now = getsbinuptime(); + if (now < vf->mbx_cleanup_deadline) + return; + + /* + * A functioning VF posts VFREQ within the grace interval. Ownership + * still held after that interval is residue from the old reset epoch. + * The force-clear helper rechecks VFREQ before asserting RVFU. + */ + if (ixgbe_force_clear_mbx_pf(hw, vf->pool) == IXGBE_SUCCESS) { + vf->flags &= ~IXGBE_VF_MBX_CLEANUP; + return; + } + + /* A request raced the force-clear attempt; give it another interval. */ + vf->mbx_cleanup_deadline = now + IXGBE_VF_MBX_CLEANUP_GRACE; +} + /* Tasklet for handling VF -> PF mailbox messages */ void ixgbe_handle_mbx(void *context) @@ -1004,29 +1046,109 @@ ixgbe_handle_mbx(void *context) struct ixgbe_softc *sc = iflib_get_softc(ctx); struct ixgbe_hw *hw; struct ixgbe_vf *vf; + bool cleanup_pending, reset_pending, reset_seen; int i; hw = &sc->hw; + cleanup_pending = false; for (i = 0; i < sc->num_vfs; i++) { vf = &sc->vfs[i]; if (vf->flags & IXGBE_VF_ACTIVE) { - if (hw->mbx.ops[vf->pool].check_for_rst(hw, - vf->pool) == 0) + reset_seen = hw->mbx.ops[vf->pool].check_for_rst(hw, + vf->pool) == 0; + if (reset_seen) { + vf->flags |= IXGBE_VF_MBX_CLEANUP; + vf->mbx_cleanup_deadline = getsbinuptime() + + IXGBE_VF_MBX_CLEANUP_GRACE; ixgbe_process_vf_reset(sc, vf); + } + reset_pending = + (vf->flags & IXGBE_VF_MBX_CLEANUP) != 0; if (hw->mbx.ops[vf->pool].check_for_msg(hw, - vf->pool) == 0) - ixgbe_process_vf_msg(ctx, vf); + vf->pool) == 0) { + if (ixgbe_process_vf_msg(ctx, vf, reset_pending)) + vf->flags &= ~IXGBE_VF_MBX_CLEANUP; + } + if (reset_pending && + (vf->flags & IXGBE_VF_MBX_CLEANUP) != 0) + ixgbe_cleanup_vf_mbx(sc, vf); if (hw->mbx.ops[vf->pool].check_for_ack(hw, vf->pool) == 0) ixgbe_process_vf_ack(sc, vf); + + if (vf->flags & IXGBE_VF_MBX_CLEANUP) + cleanup_pending = true; } } + sc->iov_mbx_cleanup_pending = cleanup_pending; } /* ixgbe_handle_mbx */ +/* + * VFREQ, VFACK, and a bare VFLR can remain latched without a usable shared + * mailbox interrupt across a PF stop/restart. Sample their aggregate + * registers from the periodic admin pass so work does not depend on another + * edge. + */ +bool +ixgbe_mbx_pending(struct ixgbe_softc *sc) +{ + struct ixgbe_hw *hw; + uint32_t active_mbx[4], active_rst[2], events; + int i, index; + + if (sc->num_vfs == 0) + return (false); + + bzero(active_mbx, sizeof(active_mbx)); + bzero(active_rst, sizeof(active_rst)); + for (i = 0; i < sc->num_vfs; i++) { + if ((sc->vfs[i].flags & IXGBE_VF_ACTIVE) == 0) + continue; + index = IXGBE_PFMBICR_INDEX(sc->vfs[i].pool); + active_mbx[index] |= + IXGBE_PFMBICR_VFREQ_VF1 << + IXGBE_PFMBICR_SHIFT(sc->vfs[i].pool); + active_mbx[index] |= + IXGBE_PFMBICR_VFACK_VF1 << + IXGBE_PFMBICR_SHIFT(sc->vfs[i].pool); + index = IXGBE_PFVFLRE_INDEX(sc->vfs[i].pool); + active_rst[index] |= + 1U << IXGBE_PFVFLRE_SHIFT(sc->vfs[i].pool); + } + + hw = &sc->hw; + for (index = 0; index < nitems(active_mbx); index++) { + if (active_mbx[index] != 0 && + (IXGBE_READ_REG(hw, IXGBE_PFMBICR(index)) & + active_mbx[index]) != 0) + return (true); + } + for (index = 0; index < nitems(active_rst); index++) { + if (active_rst[index] == 0) + continue; + switch (hw->mac.type) { + case ixgbe_mac_82599EB: + events = IXGBE_READ_REG(hw, IXGBE_PFVFLRE(index)); + break; + case ixgbe_mac_X540: + case ixgbe_mac_X550: + case ixgbe_mac_X550EM_x: + case ixgbe_mac_X550EM_a: + events = IXGBE_READ_REG(hw, IXGBE_PFVFLREC(index)); + break; + default: + return (false); + } + if ((events & active_rst[index]) != 0) + return (true); + } + return (false); +} + int ixgbe_iov_validate(struct ixgbe_softc *sc, u16 num_vfs) { @@ -1114,6 +1236,7 @@ ixgbe_if_iov_init(if_ctx_t ctx, u16 num_vfs, const nvlist_t *config) } sc->num_vfs = num_vfs; + sc->iov_mbx_cleanup_pending = false; ixgbe_init_mbx_params_pf(&sc->hw); sc->feat_en |= IXGBE_FEATURE_SRIOV; @@ -1129,6 +1252,7 @@ err_init_iov: sc->num_vfs = 0; sc->pool = 0; sc->iov_mode = IXGBE_NO_VM; + sc->iov_mbx_cleanup_pending = false; return (retval); } /* ixgbe_if_iov_init */ @@ -1204,6 +1328,7 @@ ixgbe_if_iov_uninit(if_ctx_t ctx) ixgbe_align_all_queue_indices(sc); sc->iov_vfta_valid = false; sc->iov_vlan_promisc = false; + sc->iov_mbx_cleanup_pending = false; (void)ixgbe_clear_vfta(hw); ixgbe_setup_vlan_hw_support(ctx); } /* ixgbe_if_iov_uninit */ @@ -1405,4 +1530,11 @@ ixgbe_handle_mbx(void *context) UNREFERENCED_PARAMETER(context); } /* ixgbe_handle_mbx */ +bool +ixgbe_mbx_pending(struct ixgbe_softc *sc) +{ + UNREFERENCED_PARAMETER(sc); + return (false); +} + #endif diff --git a/sys/dev/ixgbe/ixgbe.h b/sys/dev/ixgbe/ixgbe.h index 003021e928a9..20f8fce8f04b 100644 --- a/sys/dev/ixgbe/ixgbe.h +++ b/sys/dev/ixgbe/ixgbe.h @@ -366,6 +366,7 @@ struct ixgbe_vf { uint16_t default_vlan; uint16_t api_ver; uint8_t xcast_mode; + sbintime_t mbx_cleanup_deadline; }; /* Our softc structure */ @@ -461,6 +462,7 @@ struct ixgbe_softc { bool iov_mta_valid; bool iov_vfta_valid; bool iov_vlan_promisc; + bool iov_mbx_cleanup_pending; /* Bypass */ struct ixgbe_bp_data bypass; diff --git a/sys/dev/ixgbe/ixgbe_mbx.c b/sys/dev/ixgbe/ixgbe_mbx.c index 810b282bbd0e..eefe4543f3fe 100644 --- a/sys/dev/ixgbe/ixgbe_mbx.c +++ b/sys/dev/ixgbe/ixgbe_mbx.c @@ -207,7 +207,8 @@ s32 ixgbe_check_for_rst(struct ixgbe_hw *hw, u16 mbx_id) * @hw: pointer to the HW structure * @mbx_id: id of mailbox to write * - * Set VFMBMEM of given VF to 0x0. + * Set VFMBMEM of given VF to 0x0. A peer-owned or newly posted mailbox is + * left intact. **/ s32 ixgbe_clear_mbx(struct ixgbe_hw *hw, u16 mbx_id) { @@ -1112,24 +1113,79 @@ static s32 ixgbe_read_mbx_pf(struct ixgbe_hw *hw, u32 *msg, u16 size, } /** - * ixgbe_clear_mbx_pf - Clear Mailbox Memory + * ixgbe_clear_mbx_pf - Clear idle Mailbox Memory * @hw: pointer to the HW structure * @vf_id: the VF index * - * Set VFMBMEM of given VF to 0x0. + * Set VFMBMEM of given VF to 0x0 unless the VF owns it or has posted a new + * request. **/ static s32 ixgbe_clear_mbx_pf(struct ixgbe_hw *hw, u16 vf_id) { - u16 mbx_size = hw->mbx.size; + s32 ret_val; + u16 mbx_size; u16 i; if (vf_id > 63) return IXGBE_ERR_PARAM; + /* + * Serialize against a VF composing a request. After acquiring PFU, + * recheck VFREQ to close the window between the caller's mailbox check + * and this clear. Leave a newly posted request intact for the normal + * mailbox path to consume. + */ + ret_val = ixgbe_obtain_mbx_lock_pf(hw, vf_id); + if (ret_val != IXGBE_SUCCESS) + return ret_val; + if (ixgbe_check_for_msg_pf(hw, vf_id) == IXGBE_SUCCESS) { + ret_val = IXGBE_ERR_MBX; + goto out; + } + + mbx_size = hw->mbx.size; for (i = 0; i < mbx_size; ++i) IXGBE_WRITE_REG_ARRAY(hw, IXGBE_PFMBMEM(vf_id), i, 0x0); + ret_val = IXGBE_SUCCESS; - return IXGBE_SUCCESS; +out: + ixgbe_release_mbx_lock_pf(hw, vf_id); + return ret_val; +} + +/** + * ixgbe_force_clear_mbx_pf - Revoke stale VF ownership and clear its mailbox + * @hw: pointer to the HW structure + * @vf_id: the VF index + * + * VFLR does not clear VFMAILBOX.VFU on some devices. The caller must allow a + * live VF a bounded interval to finish posting its reset request before using + * this function. Refuse to revoke ownership if a request is already posted, + * then use RVFU and the normal synchronized clear path. + **/ +s32 ixgbe_force_clear_mbx_pf(struct ixgbe_hw *hw, u16 vf_id) +{ + u32 pf_mailbox; + + if (vf_id > 63) + return IXGBE_ERR_PARAM; + + pf_mailbox = IXGBE_READ_REG(hw, IXGBE_PFMAILBOX(vf_id)); + if ((pf_mailbox & IXGBE_PFMAILBOX_VFU) == 0) + return ixgbe_clear_mbx_pf(hw, vf_id); + if (ixgbe_check_for_msg_pf(hw, vf_id) == IXGBE_SUCCESS) + return IXGBE_ERR_MBX; + + /* + * Hardware has no atomic revoke-if-idle operation. A VF that starts a + * request after the grace interval can race RVFU; the synchronized clear + * below detects a request that has reached VFREQ, and its failure makes + * the caller retain cleanup state for another interval. + */ + IXGBE_WRITE_REG(hw, IXGBE_PFMAILBOX(vf_id), IXGBE_PFMAILBOX_RVFU); + IXGBE_WRITE_FLUSH(hw); + + return ixgbe_clear_mbx_pf(hw, vf_id); } /** diff --git a/sys/dev/ixgbe/ixgbe_mbx.h b/sys/dev/ixgbe/ixgbe_mbx.h index e6519963242e..fc6831f67b34 100644 --- a/sys/dev/ixgbe/ixgbe_mbx.h +++ b/sys/dev/ixgbe/ixgbe_mbx.h @@ -196,6 +196,7 @@ s32 ixgbe_check_for_msg(struct ixgbe_hw *hw, u16 mbx_id); s32 ixgbe_check_for_ack(struct ixgbe_hw *hw, u16 mbx_id); s32 ixgbe_check_for_rst(struct ixgbe_hw *hw, u16 mbx_id); s32 ixgbe_clear_mbx(struct ixgbe_hw *hw, u16 vf_number); +s32 ixgbe_force_clear_mbx_pf(struct ixgbe_hw *hw, u16 vf_number); void ixgbe_init_mbx_params_vf(struct ixgbe_hw *hw); void ixgbe_upgrade_mbx_params_vf(struct ixgbe_hw *hw); void ixgbe_init_mbx_params_pf(struct ixgbe_hw *hw); diff --git a/sys/dev/ixgbe/ixgbe_sriov.h b/sys/dev/ixgbe/ixgbe_sriov.h index 443bf78e9a1d..1ae54863befc 100644 --- a/sys/dev/ixgbe/ixgbe_sriov.h +++ b/sys/dev/ixgbe/ixgbe_sriov.h @@ -49,6 +49,7 @@ #define IXGBE_VF_ACTIVE (1 << 3) /* VF is active. */ #define IXGBE_VF_ANTI_SPOOF (1 << 4) /* Enforce source identity. */ #define IXGBE_VF_ALLOW_PROMISC (1 << 5) /* VF may request promiscuity. */ +#define IXGBE_VF_MBX_CLEANUP (1 << 6) /* Reset mailbox cleanup pending. */ #define IXGBE_VF_INDEX(vmdq) ((vmdq) / 32) #define IXGBE_VF_BIT(vmdq) (1 << ((vmdq) % 32)) @@ -106,5 +107,6 @@ u32 ixgbe_get_mrqc(int); void ixgbe_if_init(if_ctx_t ctx); void ixgbe_handle_mbx(void *); +bool ixgbe_mbx_pending(struct ixgbe_softc *); #endif diff --git a/sys/dev/ixgbe/ixgbe_vf.c b/sys/dev/ixgbe/ixgbe_vf.c index 5f65ee038753..00f0cb131cbf 100644 --- a/sys/dev/ixgbe/ixgbe_vf.c +++ b/sys/dev/ixgbe/ixgbe_vf.c @@ -210,8 +210,8 @@ s32 ixgbe_reset_hw_vf(struct ixgbe_hw *hw) ixgbe_virt_clr_reg(hw); /* - * VFLR does not clear VFMAILBOX.VFU. Drop stale ownership and - * cached read-to-clear status. + * Some devices do not clear VFMAILBOX.VFU on VFLR. Drop stale + * ownership and cached read-to-clear status. */ IXGBE_WRITE_REG(hw, IXGBE_VFMAILBOX, 0); hw->mbx.vf_mailbox = 0;home | help
Want to link to this message? Use this
URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?6a75d37c.38b6f.2e1eec7d>
