Skip site navigation (1)Skip section navigation (2)
Date:      Thu, 4 Nov 2021 16:02:53 GMT
From:      Adrian Chadd <adrian@FreeBSD.org>
To:        src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org
Subject:   git: 960e65d23aaa - main - qcom: add initial SCM legacy API
Message-ID:  <202111041602.1A4G2rEo003703@gitrepo.freebsd.org>

next in thread | raw e-mail | index | archive | help
The branch main has been updated by adrian:

URL: https://cgit.FreeBSD.org/src/commit/?id=960e65d23aaa55dd00255e95f14c2f6256a4fce3

commit 960e65d23aaa55dd00255e95f14c2f6256a4fce3
Author:     Adrian Chadd <adrian@FreeBSD.org>
AuthorDate: 2021-10-30 03:34:08 +0000
Commit:     Adrian Chadd <adrian@FreeBSD.org>
CommitDate: 2021-11-04 16:02:33 +0000

    qcom: add initial SCM legacy API
    
    This is a very simple implementation of Qualcomm's SCM API.
    
    It is just the structure/field definitions and the atomic SCM
    call which doesn't use the structs yet - it uses the field
    definitions inside registers.
    
    I've tested that setting the cold boot address via the atomic
    API is fine - Linux does the same thing.  But not all SCM calls
    can be done via the legacy API.
    
    This is a reimplementation based on the Linux qualcomm SCM legacy
    code and definitions.
    
    Tested:
    
    * Qualcomm IPQ4018 AP, as part of other changes for doing SMP bring-up
    
    Reviewed by: andrew, manu, imp
    Differential Revision: https://reviews.freebsd.org/D32723
---
 sys/arm/conf/std.qca                    |   3 +
 sys/arm/qualcomm/qcom_scm_defs.h        | 122 ++++++++++++++++++++++++++
 sys/arm/qualcomm/qcom_scm_legacy.c      |  88 +++++++++++++++++++
 sys/arm/qualcomm/qcom_scm_legacy.h      |  41 +++++++++
 sys/arm/qualcomm/qcom_scm_legacy_defs.h | 149 ++++++++++++++++++++++++++++++++
 sys/arm/qualcomm/std.ipq4018            |   1 +
 6 files changed, 404 insertions(+)

diff --git a/sys/arm/conf/std.qca b/sys/arm/conf/std.qca
index 091de6178094..00d627b77b7b 100644
--- a/sys/arm/conf/std.qca
+++ b/sys/arm/conf/std.qca
@@ -25,6 +25,9 @@ device 		sdhci
 device 		generic_timer
 device 		mpcore_timer
 
+# PSCI - SMC calls, needed for qualcomm SCM
+device 		psci
+
 options 	FDT
 
 # Disable CP14 work in DDB as TZ won't let us by default
diff --git a/sys/arm/qualcomm/qcom_scm_defs.h b/sys/arm/qualcomm/qcom_scm_defs.h
new file mode 100644
index 000000000000..f4fe0fd76b52
--- /dev/null
+++ b/sys/arm/qualcomm/qcom_scm_defs.h
@@ -0,0 +1,122 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef	__QCOM_SCM_DEFS_H__
+#define	__QCOM_SCM_DEFS_H__
+
+/*
+ * Maximum SCM arguments and return values.
+ */
+#define	MAX_QCOM_SCM_ARGS			10
+#define	MAX_QCOM_SCM_RETS			3
+
+/*
+ * SCM argument type definitions.
+ */
+#define	QCOM_SCM_ARGTYPE_VAL			0x00
+#define	QCOM_SCM_ARGTYPE_RO			0x01
+#define	QCOM_SCM_ARGTYPE_RW			0x02
+#define	QCOM_SCM_ARGTYPE_BUFVAL			0x03
+
+/*
+ * SCM calls + arguments.
+ */
+#define	QCOM_SCM_SVC_BOOT			0x01
+#define		QCOM_SCM_BOOT_SET_ADDR		0x01
+#define		QCOM_SCM_BOOT_TERMINATE_PC	0x02
+#define		QCOM_SCM_BOOT_SET_DLOAD_MODE	0x10
+#define		QCOM_SCM_BOOT_SET_REMOTE_STATE	0x0a
+#define		QCOM_SCM_FLUSH_FLAG_MASK	0x3
+
+/* Flags for QCOM_SCM_BOOT_SET_ADDR argv[0] */
+/* Note: no COLDBOOT for CPU0, it's already booted */
+#define		QCOM_SCM_FLAG_COLDBOOT_CPU1	0x01
+#define		QCOM_SCM_FLAG_WARMBOOT_CPU1	0x02
+#define		QCOM_SCM_FLAG_WARMBOOT_CPU0	0x04
+#define		QCOM_SCM_FLAG_COLDBOOT_CPU2	0x08
+#define		QCOM_SCM_FLAG_WARMBOOT_CPU2	0x10
+#define		QCOM_SCM_FLAG_COLDBOOT_CPU3	0x20
+#define		QCOM_SCM_FLAG_WARMBOOT_CPU3	0x40
+
+#define	QCOM_SCM_SVC_PIL			0x02
+#define		QCOM_SCM_PIL_PAS_INIT_IMAGE	0x01
+#define		QCOM_SCM_PIL_PAS_MEM_SETUP	0x02
+#define		QCOM_SCM_PIL_PAS_AUTH_AND_RESET	0x05
+#define		QCOM_SCM_PIL_PAS_SHUTDOWN	0x06
+#define		QCOM_SCM_PIL_PAS_IS_SUPPORTED	0x07
+#define		QCOM_SCM_PIL_PAS_MSS_RESET	0x0a
+
+#define	QCOM_SCM_SVC_IO				0x05
+#define		QCOM_SCM_IO_READ		0x01
+#define		QCOM_SCM_IO_WRITE		0x02
+
+/*
+ * Fetch SCM call availability information.
+ */
+#define	QCOM_SCM_SVC_INFO			0x06
+#define		QCOM_SCM_INFO_IS_CALL_AVAIL	0x01
+
+#define	QCOM_SCM_SVC_MP				0x0c
+#define		QCOM_SCM_MP_RESTORE_SEC_CFG	0x02
+#define		QCOM_SCM_MP_IOMMU_SECURE_PTBL_SIZE	0x03
+#define		QCOM_SCM_MP_IOMMU_SECURE_PTBL_INIT	0x04
+#define		QCOM_SCM_MP_VIDEO_VAR		0x08
+#define		QCOM_SCM_MP_ASSIGN		0x16
+
+#define	QCOM_SCM_SVC_OCMEM			0x0f
+#define		QCOM_SCM_OCMEM_LOCK_CMD		0x01
+#define		QCOM_SCM_OCMEM_UNLOCK_CMD	0x02
+
+#define	QCOM_SCM_SVC_ES				0x10
+#define		QCOM_SCM_ES_INVALIDATE_ICE_KEY	0x03
+#define		QCOM_SCM_ES_CONFIG_SET_ICE_KEY	0x04
+
+#define	QCOM_SCM_SVC_HDCP			0x11
+#define		QCOM_SCM_HDCP_INVOKE		0x01
+
+#define	QCOM_SCM_SVC_LMH			0x13
+#define		QCOM_SCM_LMH_LIMIT_PROFILE_CHANGE	0x01
+#define		QCOM_SCM_LMH_LIMIT_DCVSH	0x10
+
+#define	QCOM_SCM_SVC_SMMU_PROGRAM		0x15
+#define		QCOM_SCM_SMMU_CONFIG_ERRATA1	0x03
+#define		QCOM_SCM_SMMU_CONFIG_ERRATA1_CLIENT_ALL	0x02
+
+/*
+ * Return values from the SCM calls.
+ */
+#define	QCOM_SCM_RETVAL_V2_EBUSY		-12
+#define	QCOM_SCM_RETVAL_ENOMEM			-5
+#define	QCOM_SCM_RETVAL_EOPNOTSUPP		-4
+#define	QCOM_SCM_RETVAL_EINVAL_ADDR		-3
+#define	QCOM_SCM_RETVAL_EINVAL_ARG		-2
+#define	QCOM_SCM_RETVAL_ERROR			-1
+#define	QCOM_SCM_RETVAL_INTERRUPTED		1
+
+#endif	/* __QCOM_SCM_DEFS_H__ */
diff --git a/sys/arm/qualcomm/qcom_scm_legacy.c b/sys/arm/qualcomm/qcom_scm_legacy.c
new file mode 100644
index 000000000000..d773e095185b
--- /dev/null
+++ b/sys/arm/qualcomm/qcom_scm_legacy.c
@@ -0,0 +1,88 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include "opt_platform.h"
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/bus.h>
+#include <sys/reboot.h>
+#include <sys/devmap.h>
+#include <sys/smp.h>
+
+#include <vm/vm.h>
+#include <vm/pmap.h>
+
+#include <machine/cpu.h>
+#include <machine/bus.h>
+#include <machine/intr.h>
+#include <machine/machdep.h>
+#include <machine/smp.h>
+
+#include <arm/qualcomm/qcom_scm_defs.h>
+#include <arm/qualcomm/qcom_scm_legacy_defs.h>
+#include <arm/qualcomm/qcom_scm_legacy.h>
+
+#include <dev/psci/smccc.h>
+
+/*
+ * Set the cold boot address for (later) a mask of CPUs.
+ *
+ * Don't set it for CPU0, that CPU is the boot CPU and is already alive.
+ *
+ * For now it sets it on CPU1..3.
+ *
+ * This works on the IPQ4019 as tested; the retval is 0x0.
+ */
+uint32_t
+qcom_scm_legacy_mp_set_cold_boot_address(vm_offset_t mp_entry_func)
+{
+	struct arm_smccc_res res;
+	int ret;
+	int context_id;
+
+	uint32_t scm_arg0 = QCOM_SCM_LEGACY_ATOMIC_ID(QCOM_SCM_SVC_BOOT,
+	    QCOM_SCM_BOOT_SET_ADDR, 2);
+
+	uint32_t scm_arg1 = QCOM_SCM_FLAG_COLDBOOT_CPU1
+	    | QCOM_SCM_FLAG_COLDBOOT_CPU2
+	    | QCOM_SCM_FLAG_COLDBOOT_CPU3;
+	uint32_t scm_arg2 = pmap_kextract((vm_offset_t)mp_entry_func);
+
+	ret = arm_smccc_smc(scm_arg0, (uint32_t) &context_id, scm_arg1,
+	    scm_arg2, 0, 0, 0, 0, &res);
+
+	if (ret == 0 && res.a0 == 0)
+		return (0);
+	printf("%s: called; error; ret=0x%08x; retval[0]=0x%08x\n",
+	    __func__, ret, res.a0);
+
+	return (0);
+}
diff --git a/sys/arm/qualcomm/qcom_scm_legacy.h b/sys/arm/qualcomm/qcom_scm_legacy.h
new file mode 100644
index 000000000000..a0f9d44062cf
--- /dev/null
+++ b/sys/arm/qualcomm/qcom_scm_legacy.h
@@ -0,0 +1,41 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef	__QCOM_SCM_LEGACY_H__
+#define	__QCOM_SCM_LEGACY_H__
+
+/*
+ * These functions are specific to the 32 bit legacy SCM interface
+ * used by the IPQ806x and IPQ401x SoCs.
+ */
+
+extern	uint32_t qcom_scm_legacy_mp_set_cold_boot_address(
+	    vm_offset_t mp_entry_func);
+
+#endif	/* __QCOM_SCM_LEGACY_H__ */
diff --git a/sys/arm/qualcomm/qcom_scm_legacy_defs.h b/sys/arm/qualcomm/qcom_scm_legacy_defs.h
new file mode 100644
index 000000000000..d34a552d4b80
--- /dev/null
+++ b/sys/arm/qualcomm/qcom_scm_legacy_defs.h
@@ -0,0 +1,149 @@
+/*-
+ * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
+ *
+ * Copyright (c) 2021 Adrian Chadd <adrian@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ * $FreeBSD$
+ */
+
+#ifndef	__QCOM_SCM_LEGACY_DEFS_H__
+#define	__QCOM_SCM_LEGACY_DEFS_H__
+
+/*
+ * These definitions are specific to the 32 bit legacy SCM interface
+ * used by the IPQ806x and IPQ401x SoCs.
+ */
+
+/*
+ * Mapping of the SCM service/command fields into the a0 argument
+ * in an SMC instruction call.
+ *
+ * This is particular to the legacy SCM interface, and is not the
+ * same as the non-legacy 32/64 bit FNID mapping layout.
+ */
+#define	QCOM_SCM_LEGACY_SMC_FNID(s, c)		(((s) << 10) | ((c) & 0x3ff))
+
+/*
+ * There are two kinds of SCM calls in this legacy path.
+ *
+ * The first kind are the normal ones - up to a defined max of arguments,
+ * a defined max of responses and some identifiers for all of it.
+ * They can be issues in parallel on different cores, can be interrupted,
+ * etc.
+ *
+ * The second kind are what are termed "atomic" SCM calls -
+ * up to 5 argument DWORDs, up to 3 response DWORDs, done atomically,
+ * not interruptable/parallel.
+ *
+ * The former use the structures below to represent the request and response
+ * in memory.  The latter use defines and a direct SMC call with the
+ * arguments in registers.
+ */
+
+struct qcom_scm_legacy_smc_args {
+	uint32_t args[8];
+};
+
+/*
+ * Atomic SCM call command/response buffer definitions.
+ */
+#define	QCOM_SCM_LEGACY_ATOMIC_MAX_ARGCOUNT		5
+#define	QCOM_SCM_LEGACY_CLASS_REGISTER			(0x2 << 8)
+#define	QCOM_SCM_LEGACY_MASK_IRQS			(1U << 5)
+
+/*
+ * Mapping an SCM service/command/argcount into the a0 register
+ * for an SMC instruction call.
+ */
+#define	QCOM_SCM_LEGACY_ATOMIC_ID(svc, cmd, n) \
+	    ((QCOM_SCM_LEGACY_SMC_FNID((svc), cmd) << 12) | \
+	    QCOM_SCM_LEGACY_CLASS_REGISTER | \
+	    QCOM_SCM_LEGACY_MASK_IRQS | \
+	    ((n) & 0xf))
+
+/*
+ * Legacy command/response buffer definitions.
+ *
+ * The legacy path contains up to the defined maximum arguments
+ * but only a single command/response pair per call.
+ *
+ * A command and response buffer is laid out in memory as such:
+ *
+ * | command header     |
+ * | (buffer payload)   |
+ * | response header    |
+ * | (response payload) |
+ */
+
+/*
+ * The command header.
+ *
+ * len - the length of the total command and response, including
+ *       the headers.
+ *
+ * buf_offset - the offset inside the buffer, starting at the
+ *       beginning of this command header, where the command buffer
+ *       is found.  The end is the byte before the response_header_offset.
+ *
+ * response_header_offset - the offset inside the buffer where
+ *       the response header is found.
+ *
+ * id - the QCOM_SCM_LEGACY_SMC_FNID() - service/command ids
+ */
+struct qcom_scm_legacy_command_header {
+	uint32_t len;
+	uint32_t buf_offset;
+	uint32_t response_header_offset;
+	uint32_t id;
+};
+
+/*
+ * The response header.
+ *
+ * This is found immediately after the command header and command
+ * buffer payload.
+ *
+ * len - the total amount of memory available for the response.
+ *       Linux doesn't set this; it always passes in a response
+ *       buffer large enough to store MAX_QCOM_SCM_RETS * DWORD
+ *       bytes.
+ *
+ *       It's also possible this is set by the firmware.
+ *
+ * buf_offset - start of response buffer, relative to the beginning
+ *       of the command header.  This also isn't set in Linux before
+ *       calling the SMC instruction, but it is checked afterwards
+ *       to assemble a pointer to the response data.  The firmware
+ *       likely sets this.
+ *
+ * is_complete - true if complete.  Linux loops over DMA sync to
+ *       check if this is complete even after the SMC call returns.
+ */
+struct qcom_scm_legacy_response_header {
+	uint32_t len;
+	uint32_t buf_offset;
+	uint32_t is_complete;
+};
+
+#endif	/* __QCOM_SCM_LEGACY_DEFS_H__ */
diff --git a/sys/arm/qualcomm/std.ipq4018 b/sys/arm/qualcomm/std.ipq4018
index 823d7e74cb50..6492dbd06b1b 100644
--- a/sys/arm/qualcomm/std.ipq4018
+++ b/sys/arm/qualcomm/std.ipq4018
@@ -1,4 +1,5 @@
 arm/qualcomm/ipq4018_machdep.c		standard
 arm/qualcomm/ipq4018_mp.c		optional smp
+arm/qualcomm/qcom_scm_legacy.c		standard
 
 dev/qcom_rnd/qcom_rnd.c			optional qcom_rnd



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?202111041602.1A4G2rEo003703>