Page MenuHomeFreeBSD

D58863.diff
No OneTemporary

D58863.diff

diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile
--- a/share/man/man4/Makefile
+++ b/share/man/man4/Makefile
@@ -344,6 +344,7 @@
md.4 \
mdio.4 \
me.4 \
+ mei.4 \
mem.4 \
mfi.4 \
${_mgb.4} \
@@ -784,6 +785,7 @@
MLINKS+=lp.4 plip.4
MLINKS+=malo.4 if_malo.4
MLINKS+=me.4 if_me.4
+MLINKS+=mei.4 meifd.4
MLINKS+=mem.4 kmem.4
MLINKS+=mfi.4 mfi_linux.4 \
mfi.4 mfip.4
diff --git a/share/man/man4/mei.4 b/share/man/man4/mei.4
new file mode 100644
--- /dev/null
+++ b/share/man/man4/mei.4
@@ -0,0 +1,223 @@
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.\" Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com>
+.\"
+.\" 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.
+.\"
+.Dd August 15, 2026
+.Dt MEI 4
+.Os
+.Sh NAME
+.Nm mei ,
+.Nm meifd
+.Nd Intel Management Engine Interface (MEI/HECI) debug probe
+.Sh SYNOPSIS
+To compile this driver into the kernel,
+place the following lines in your
+kernel configuration file:
+.Bd -ragged -offset indent
+.Cd "device pci"
+.Cd "device isa"
+.Cd "device mei"
+.Ed
+.Pp
+Alternatively, to load the driver as a
+module at boot time, place the following line in
+.Xr loader.conf 5 :
+.Bd -literal -offset indent
+mei_load="YES"
+.Ed
+.Sh DESCRIPTION
+The
+.Nm mei
+driver is a debug probe for the Intel Management Engine / HECI
+interface.
+It is supported on
+amd64
+systems only.
+It is deliberately
+.Em not
+a full MEI host-client driver: it attaches only to the PCI function
+and decodes the Host Firmware Status registers available on that generation
+.Pq Dv HFSTS1 Ns \(en Ns Dv HFSTS6 ,
+which live entirely in PCI configuration space.
+BAR0, the circular-buffer messaging ring used for actual host-ME
+communication, is never mapped.
+.Pp
+The register counts and layout are confirmed against the Linux
+.Pa drivers/misc/mei/hw-me-regs.h
+and
+.Pa hw-me.c
+sources.
+ICH10 provides HFSTS1, PCH5 through PCH7 provide HFSTS1 and HFSTS2,
+and PCH8 and later provide all six registers at PCI configuration
+offsets 0x40/0x48/0x60/0x64/0x68/0x6c.
+Older ICH devices without a defined Host Firmware Status register are
+not matched.
+.Pp
+On attach, the driver decodes the Current Working State, Operation
+State, Operation Mode, and Error Code fields into named strings and
+derives a one-word summary
+.Pq Dq Normal , Dq Active , Dq Disabled , or Dq Absent
+for consumers that do not want to parse the raw fields.
+.Ss meifd
+The MEI PCI function never enumerates when firmware has disabled it
+via the PCH Function Disable mechanism, so there is no PCI device
+instance for
+.Nm mei
+to attach to.
+.Nm meifd
+is an isa-attached identify driver, modeled on
+.Xr ichwd 4 Ns 's
+.Fn ichwd_identify ,
+that reads the LPC bridge's RCBA
+.Pq Root Complex Base Address
+and the FD2
+.Pq Function Disable 2
+register at RCBA+0x3428 to determine whether HECI1/HECI2 are present
+in silicon but hidden.
+.Pp
+The RCBA and FD2 layout is supported on Ibex Peak, Cougar Point,
+Panther Point, Lynx Point, and Wildcat Point.
+Other chipsets report one of:
+.Bl -tag -width "unknown-generation"
+.It Cm unknown-generation
+The LPC bridge is an Intel device but is not one of the supported PCH
+generations.
+No assumption is made about its ME capability or disable mechanism.
+.It Cm not-applicable
+The LPC bridge is not a PCI device, or is not an Intel device.
+.It Cm rcba-disabled
+RCBA is present but not enabled, so the Function Disable register
+cannot be read.
+.It Cm checked
+FD2 was read successfully; the
+.Va mei1_disabled
+and
+.Va mei2_disabled
+sysctls reflect its bits.
+.El
+.Sh SYSCTL VARIABLES
+The following
+.Xr sysctl 8
+variables are available under
+.Va dev.mei.N
+when the driver is attached to a PCI MEI/HECI function:
+.Bl -tag -width "update_in_progress"
+.It Va hfs1
+Raw Host Firmware Status 1 register value.
+.It Va hfs2
+Raw Host Firmware Status 2 register value, on PCH5 and later.
+.It Va hfs3 , hfs4 , hfs5 , hfs6
+Raw Host Firmware Status 3 through 6 register values, on PCH8 and later.
+.It Va d0i3
+D0i3 power state, from HFS1, on PCH8 and later.
+.It Va opmode
+ME operation mode, from HFS1, as a numeric value.
+.It Va pm_event
+ME power management event, from HFS2, on PCH5 and later.
+.It Va fw_sku
+ME firmware SKU, from HFS3, on PCH8 and later.
+.It Va mfg_mode
+Non-zero if the ME is running in Manufacturing Mode, from HFS1.
+.It Va update_in_progress
+Non-zero if an ME firmware update is in progress, from HFS1.
+.It Va state
+ME Current Working State, decoded to a string
+.Pq e.g., Dq Normal , Dq Disabled .
+.It Va opstate
+ME Current Operation State, decoded to a string.
+.It Va opmode_str
+ME Current Operation Mode, decoded to a string.
+.It Va error
+ME Error Code, decoded to a string.
+.It Va summary
+One-word ME state summary; one of
+.Dq Normal ,
+.Dq Active ,
+.Dq Disabled ,
+or
+.Dq Absent .
+.El
+.Pp
+The
+.Va status
+.Xr sysctl 8
+variable is available under
+.Va dev.meifd.N
+when the
+.Nm meifd
+identify driver has attached.
+It contains the
+function-disable check result; one of the states listed in
+.Sx meifd
+above.
+.Pp
+When
+.Va status
+is
+.Cm checked ,
+the following additional variables are available:
+.Bl -tag -width "mei1_disabled"
+.It Va fd2
+Raw Function Disable 2 register value.
+.It Va mei1_disabled
+Non-zero if HECI1 is disabled via the Function Disable register.
+.It Va mei2_disabled
+Non-zero if HECI2 is disabled via the Function Disable register.
+.El
+.Sh EXAMPLES
+Show the decoded ME state on a system where MEI enumerates normally:
+.Pp
+.Dl "sysctl dev.mei.0"
+.Pp
+Check whether HECI has been hidden via PCH Function Disable on a
+system where no MEI PCI function is present:
+.Pp
+.Dl "sysctl dev.meifd.0"
+.Sh SEE ALSO
+.Xr ichwd 4 ,
+.Xr pci 4 ,
+.Xr sysctl 8
+.Sh HISTORY
+The
+.Nm mei
+and
+.Nm meifd
+drivers first appeared in
+.Fx 16.0 .
+.Sh AUTHORS
+.An Abdelkader Boudih Aq Mt freebsd@seuros.com .
+.Sh CAVEATS
+.Nm mei
+only decodes PCI-configuration-space firmware status registers; it
+provides no access to the MEI messaging ring and cannot be used to
+communicate with the ME.
+.Pp
+.Nm meifd
+identifies HECI Function Disable state on a known set of PCH
+generations only; chipsets that moved ME-disable to a P2SB-sideband
+mechanism
+.Pq Skylake/Sunrise Point and later
+are reported as
+.Cm unknown-generation
+rather than decoded.
diff --git a/sys/conf/files.amd64 b/sys/conf/files.amd64
--- a/sys/conf/files.amd64
+++ b/sys/conf/files.amd64
@@ -248,6 +248,8 @@
compile-with "${NORMAL_C} -I$S/dev/ixl"
dev/ixl/i40e_dcb.c optional ixl pci \
compile-with "${NORMAL_C} -I$S/dev/ixl"
+dev/mei/mei_pci.c optional mei pci
+dev/mei/mei_fd.c optional mei isa
dev/ncthwm/ncthwm.c optional ncthwm superio
dev/qlxge/qls_dbg.c optional qlxge pci
dev/qlxge/qls_dump.c optional qlxge pci
diff --git a/sys/dev/mei/mei_fd.c b/sys/dev/mei/mei_fd.c
new file mode 100644
--- /dev/null
+++ b/sys/dev/mei/mei_fd.c
@@ -0,0 +1,237 @@
+/*
+ * Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+/*
+ * meifd(4) - detects HECI hidden via the PCH Function Disable register.
+ *
+ * The MEI PCI function never enumerates when firmware (coreboot's
+ * PCH_DISABLE_MEI1/2) has disabled it, so there is no PCI device instance
+ * for mei_pci_attach() to run against. This is an isa-attached identify
+ * driver instead, modeled on ichwd_identify() in sys/dev/ichwd/ichwd.c,
+ * which faces the same problem for the ICH watchdog.
+ *
+ * RCBA (LPC bridge config offset 0xF0) and FD2 (RCBA+0x3428, bit 1 =
+ * MEI1 disabled, bit 2 = MEI2 disabled) use this layout on Ibex Peak,
+ * Cougar Point, Panther Point, Lynx Point, and Wildcat Point. Other
+ * generations report "unknown-generation" instead of assuming that their
+ * disable mechanism or ME capability is known.
+ */
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/sysctl.h>
+
+#include <vm/vm.h>
+#include <vm/pmap.h>
+
+#include <machine/bus.h>
+#include <machine/pmap.h>
+
+#include <dev/pci/pcivar.h>
+#include <dev/ichwd/ichwd.h>
+
+#define MEI_FD_RCBA_OFFSET 0xf0
+#define MEI_FD_RCBA_EN 0x00000001
+#define MEI_FD_RCBA_MASK 0xffffc000
+#define MEI_FD_RCBA_SIZE 0x4000
+#define MEI_FD_FD2_OFFSET 0x3428
+#define MEI_FD_FD2_MEI1_DIS 0x00000002
+#define MEI_FD_FD2_MEI2_DIS 0x00000004
+
+static const uint16_t mei_fd_lpc_ids[] = {
+ DEVICEID_LPT0, DEVICEID_LPT1, DEVICEID_LPT2, DEVICEID_LPT3,
+ DEVICEID_LPT4, DEVICEID_LPT5, DEVICEID_LPT6, DEVICEID_LPT7,
+ DEVICEID_LPT8, DEVICEID_LPT9, DEVICEID_LPT10, DEVICEID_LPT11,
+ DEVICEID_LPT12, DEVICEID_LPT13, DEVICEID_LPT14, DEVICEID_LPT15,
+ DEVICEID_LPT16, DEVICEID_LPT17, DEVICEID_LPT18, DEVICEID_LPT19,
+ DEVICEID_LPT20, DEVICEID_LPT21, DEVICEID_LPT22, DEVICEID_LPT23,
+ DEVICEID_LPT24, DEVICEID_LPT25, DEVICEID_LPT26, DEVICEID_LPT27,
+ DEVICEID_LPT28, DEVICEID_LPT29, DEVICEID_LPT30, DEVICEID_LPT31,
+ DEVICEID_LPT_LP0, DEVICEID_LPT_LP1, DEVICEID_LPT_LP2,
+ DEVICEID_LPT_LP3, DEVICEID_LPT_LP4, DEVICEID_LPT_LP5,
+ DEVICEID_LPT_LP6, DEVICEID_LPT_LP7,
+ DEVICEID_WCPT1, DEVICEID_WCPT2, DEVICEID_WCPT3, DEVICEID_WCPT4,
+ DEVICEID_WCPT6,
+ DEVICEID_WCPT_LP1, DEVICEID_WCPT_LP2, DEVICEID_WCPT_LP3,
+ DEVICEID_WCPT_LP5, DEVICEID_WCPT_LP6, DEVICEID_WCPT_LP7,
+ DEVICEID_WCPT_LP9,
+ DEVICEID_CPT0, DEVICEID_CPT1, DEVICEID_CPT2, DEVICEID_CPT3,
+ DEVICEID_CPT4, DEVICEID_CPT5, DEVICEID_CPT6, DEVICEID_CPT7,
+ DEVICEID_CPT8, DEVICEID_CPT9, DEVICEID_CPT10, DEVICEID_CPT11,
+ DEVICEID_CPT12, DEVICEID_CPT13, DEVICEID_CPT14, DEVICEID_CPT15,
+ DEVICEID_CPT16, DEVICEID_CPT17, DEVICEID_CPT18, DEVICEID_CPT19,
+ DEVICEID_CPT20, DEVICEID_CPT21, DEVICEID_CPT22, DEVICEID_CPT23,
+ DEVICEID_CPT24, DEVICEID_CPT25, DEVICEID_CPT26, DEVICEID_CPT27,
+ DEVICEID_CPT28, DEVICEID_CPT29, DEVICEID_CPT30, DEVICEID_CPT31,
+ DEVICEID_PPT0, DEVICEID_PPT1, DEVICEID_PPT2, DEVICEID_PPT3,
+ DEVICEID_PPT4, DEVICEID_PPT5, DEVICEID_PPT6, DEVICEID_PPT7,
+ DEVICEID_PPT8, DEVICEID_PPT9, DEVICEID_PPT10, DEVICEID_PPT11,
+ DEVICEID_PPT12, DEVICEID_PPT13, DEVICEID_PPT14, DEVICEID_PPT15,
+ DEVICEID_PCH, DEVICEID_PCHM, DEVICEID_P55, DEVICEID_PM55,
+ DEVICEID_H55, DEVICEID_QM57, DEVICEID_H57, DEVICEID_HM55,
+ DEVICEID_Q57, DEVICEID_HM57, DEVICEID_PCHMSFF, DEVICEID_QS57,
+};
+
+struct mei_fd_softc {
+ device_t sc_dev;
+ char sc_status[24];
+ uint32_t sc_fd2;
+ uint32_t sc_mei1_disabled;
+ uint32_t sc_mei2_disabled;
+ bool sc_checked;
+};
+
+static bool
+mei_fd_lpc_known(uint16_t devid)
+{
+ u_int i;
+
+ for (i = 0; i < nitems(mei_fd_lpc_ids); i++) {
+ if (mei_fd_lpc_ids[i] == devid)
+ return (true);
+ }
+ return (false);
+}
+
+static void
+mei_fd_identify(driver_t *driver, device_t parent)
+{
+
+ if (device_find_child(parent, driver->name, 0) == NULL)
+ BUS_ADD_CHILD(parent, 0, driver->name, 0);
+}
+
+static int
+mei_fd_probe(device_t dev)
+{
+
+ device_set_desc(dev, "Intel ME Function Disable check");
+ return (BUS_PROBE_NOWILDCARD);
+}
+
+static void
+mei_fd_set_status(struct mei_fd_softc *sc, const char *status)
+{
+
+ strlcpy(sc->sc_status, status, sizeof(sc->sc_status));
+}
+
+static int
+mei_fd_attach(device_t dev)
+{
+ struct mei_fd_softc *sc;
+ struct sysctl_ctx_list *ctx;
+ struct sysctl_oid *tree;
+ device_t isab;
+ vm_paddr_t rcba;
+ bus_space_handle_t bsh;
+ uint32_t fd2;
+
+ sc = device_get_softc(dev);
+ sc->sc_dev = dev;
+
+ isab = device_get_parent(device_get_parent(dev));
+
+ if (!is_pci_device(isab)) {
+ device_printf(dev, "isa0's parent is not a PCI device - "
+ "cannot locate the LPC bridge\n");
+ mei_fd_set_status(sc, "not-applicable");
+ goto done;
+ }
+
+ if (pci_get_vendor(isab) != VENDORID_INTEL) {
+ device_printf(dev, "non-Intel LPC bridge (vendor 0x%04x) - "
+ "Intel ME does not apply\n", pci_get_vendor(isab));
+ mei_fd_set_status(sc, "not-applicable");
+ goto done;
+ }
+
+ if (!mei_fd_lpc_known(pci_get_device(isab))) {
+ device_printf(dev, "LPC bridge device 0x%04x is not a known "
+ "Ibex Peak/Cougar Point/Panther Point/Lynx Point/Wildcat "
+ "Point PCH - cannot determine ME Function Disable "
+ "state\n", pci_get_device(isab));
+ mei_fd_set_status(sc, "unknown-generation");
+ goto done;
+ }
+
+ rcba = pci_read_config(isab, MEI_FD_RCBA_OFFSET, 4);
+ if ((rcba & MEI_FD_RCBA_EN) == 0 || (rcba & MEI_FD_RCBA_MASK) == 0) {
+ device_printf(dev,
+ "RCBA not enabled, cannot read Function Disable register\n");
+ mei_fd_set_status(sc, "rcba-disabled");
+ goto done;
+ }
+
+ bsh = (bus_space_handle_t)pmap_mapdev(rcba & MEI_FD_RCBA_MASK,
+ MEI_FD_RCBA_SIZE);
+ fd2 = bus_space_read_4(X86_BUS_SPACE_MEM, bsh, MEI_FD_FD2_OFFSET);
+ pmap_unmapdev((void *)bsh, MEI_FD_RCBA_SIZE);
+
+ sc->sc_fd2 = fd2;
+ sc->sc_mei1_disabled = (fd2 & MEI_FD_FD2_MEI1_DIS) != 0;
+ sc->sc_mei2_disabled = (fd2 & MEI_FD_FD2_MEI2_DIS) != 0;
+ sc->sc_checked = true;
+ mei_fd_set_status(sc, "checked");
+
+ if (sc->sc_mei1_disabled) {
+ device_printf(dev, "HECI1 disabled via PCH Function Disable "
+ "register (silicon present, hidden by firmware)\n");
+ } else {
+ device_printf(dev, "HECI1 not disabled - MEI should be "
+ "enumerable as a normal PCI function\n");
+ }
+ if (sc->sc_mei2_disabled)
+ device_printf(dev, "HECI2 disabled via PCH Function Disable "
+ "register\n");
+
+done:
+ ctx = device_get_sysctl_ctx(dev);
+ tree = device_get_sysctl_tree(dev);
+
+ SYSCTL_ADD_STRING(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "status",
+ CTLFLAG_RD, sc->sc_status, 0,
+ "Function Disable check result");
+ if (!sc->sc_checked)
+ return (0);
+ SYSCTL_ADD_U32(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "fd2",
+ CTLFLAG_RD, &sc->sc_fd2, 0,
+ "Function Disable 2 register (raw)");
+ SYSCTL_ADD_U32(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "mei1_disabled",
+ CTLFLAG_RD, &sc->sc_mei1_disabled, 0,
+ "HECI1 disabled via Function Disable register");
+ SYSCTL_ADD_U32(ctx, SYSCTL_CHILDREN(tree), OID_AUTO, "mei2_disabled",
+ CTLFLAG_RD, &sc->sc_mei2_disabled, 0,
+ "HECI2 disabled via Function Disable register");
+
+ return (0);
+}
+
+static int
+mei_fd_detach(device_t dev)
+{
+
+ return (0);
+}
+
+static device_method_t mei_fd_methods[] = {
+ DEVMETHOD(device_identify, mei_fd_identify),
+ DEVMETHOD(device_probe, mei_fd_probe),
+ DEVMETHOD(device_attach, mei_fd_attach),
+ DEVMETHOD(device_detach, mei_fd_detach),
+ DEVMETHOD_END
+};
+
+static driver_t mei_fd_driver = {
+ "meifd",
+ mei_fd_methods,
+ sizeof(struct mei_fd_softc)
+};
+
+DRIVER_MODULE(meifd, isa, mei_fd_driver, 0, 0);
+MODULE_VERSION(meifd, 1);
diff --git a/sys/dev/mei/mei_pci.c b/sys/dev/mei/mei_pci.c
new file mode 100644
--- /dev/null
+++ b/sys/dev/mei/mei_pci.c
@@ -0,0 +1,437 @@
+/*
+ * Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+/*
+ * mei(4) - Intel Management Engine Interface (MEI/HECI) debug probe.
+ *
+ * This is deliberately *not* a full MEI host-client driver: it only
+ * attaches to the PCI function and decodes the Host Firmware Status
+ * registers (HFSTS1-6, as supported by the device generation), which live
+ * entirely in PCI configuration space.
+ * BAR0 (the circular-buffer messaging ring) is never mapped.
+ *
+ * Register counts and layout follow Linux drivers/misc/mei/hw-me.c:
+ * ICH10 provides HFS1, PCH5 through PCH7 provide HFS1-2, and PCH8 and
+ * later provide HFS1-6 at PCI config offsets 0x40/0x48/0x60/0x64/0x68/0x6c.
+ */
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/kernel.h>
+#include <sys/module.h>
+#include <sys/sysctl.h>
+
+#include <dev/pci/pcivar.h>
+#include <dev/pci/pcireg.h>
+#include <dev/ichwd/ichwd.h>
+
+/* Host Firmware Status Registers - PCI configuration space offsets. */
+#define MEI_PCI_CFG_HFS_1 0x40
+#define MEI_HFS_1_CWS_MSK 0x0000000f
+#define MEI_HFS_1_MFG_MODE_MSK 0x00000010
+#define MEI_HFS_1_FPT_BAD_MSK 0x00000020
+#define MEI_HFS_1_OPSTATE_MSK 0x000001c0
+#define MEI_HFS_1_OPSTATE_SHIFT 6
+#define MEI_HFS_1_FW_INIT_CMPL_MSK 0x00000200
+#define MEI_HFS_1_BUP_FAIL_MSK 0x00000400
+#define MEI_HFS_1_UPDATE_INPROG_MSK 0x00000800
+#define MEI_HFS_1_ERROR_MSK 0x0000f000
+#define MEI_HFS_1_ERROR_SHIFT 12
+#define MEI_HFS_1_OPMODE_MSK 0x000f0000
+#define MEI_HFS_1_OPMODE_SHIFT 16
+#define MEI_HFS_1_BOOT_OPT_MSK 0x01000000
+#define MEI_HFS_1_D0I3_MSK 0x80000000
+#define MEI_PCI_CFG_HFS_2 0x48
+#define MEI_HFS_2_PM_EVENT_MSK 0x0f000000
+#define MEI_HFS_2_PM_EVENT_SHIFT 24
+#define MEI_PCI_CFG_HFS_3 0x60
+#define MEI_HFS_3_FW_SKU_MSK 0x00000070
+#define MEI_HFS_3_FW_SKU_SHIFT 4
+#define MEI_PCI_CFG_HFS_4 0x64
+#define MEI_PCI_CFG_HFS_5 0x68
+#define MEI_PCI_CFG_HFS_6 0x6c
+
+/* HFS1 field values. Operation state and mode are sparse encodings. */
+#define MEI_HFS_CWS_RESET 0
+#define MEI_HFS_CWS_INIT 1
+#define MEI_HFS_CWS_RECOVERY 2
+#define MEI_HFS_CWS_TEST 3
+#define MEI_HFS_CWS_DISABLED 4
+#define MEI_HFS_CWS_NORMAL 5
+
+#define MEI_HFS_OPSTATE_PREBOOT 0
+#define MEI_HFS_OPSTATE_M0_UMA 1
+#define MEI_HFS_OPSTATE_M3 4
+#define MEI_HFS_OPSTATE_M0 5
+#define MEI_HFS_OPSTATE_IMAGE_ERROR 7
+
+#define MEI_HFS_OPMODE_NORMAL 0
+#define MEI_HFS_OPMODE_DEBUG 2
+#define MEI_HFS_OPMODE_TEMP_DISABLED 3
+#define MEI_HFS_OPMODE_JUMPER_OVERRIDE 4
+#define MEI_HFS_OPMODE_MEI_OVERRIDE 5
+#define MEI_HFS_OPMODE_SPS 15
+
+#define MEI_HFS_ERROR_NONE 0
+#define MEI_HFS_ERROR_UNCATEGORIZED 1
+#define MEI_HFS_ERROR_DISABLED 2
+#define MEI_HFS_ERROR_IMAGE 3
+#define MEI_HFS_ERROR_DEBUG 4
+
+static const char *const mei_cws_values[] = {
+ [MEI_HFS_CWS_RESET] = "Reset",
+ [MEI_HFS_CWS_INIT] = "Initializing",
+ [MEI_HFS_CWS_RECOVERY] = "Recovery",
+ [MEI_HFS_CWS_TEST] = "Test",
+ [MEI_HFS_CWS_DISABLED] = "Disabled",
+ [MEI_HFS_CWS_NORMAL] = "Normal",
+};
+
+static const char *const mei_opstate_values[] = {
+ [MEI_HFS_OPSTATE_PREBOOT] = "Preboot",
+ [MEI_HFS_OPSTATE_M0_UMA] = "M0 (UMA)",
+ [MEI_HFS_OPSTATE_M3] = "M3 (no UMA)",
+ [MEI_HFS_OPSTATE_M0] = "M0 (no UMA)",
+ [MEI_HFS_OPSTATE_IMAGE_ERROR] = "M0 (invalid firmware image)",
+};
+
+static const char *const mei_opmode_values[] = {
+ [MEI_HFS_OPMODE_NORMAL] = "Normal",
+ [MEI_HFS_OPMODE_DEBUG] = "Debug",
+ [MEI_HFS_OPMODE_TEMP_DISABLED] = "Temporarily disabled",
+ [MEI_HFS_OPMODE_JUMPER_OVERRIDE] = "Security override (jumper)",
+ [MEI_HFS_OPMODE_MEI_OVERRIDE] = "Security override (MEI)",
+ [MEI_HFS_OPMODE_SPS] = "Server Platform Services",
+};
+
+static const char *const mei_error_values[] = {
+ [MEI_HFS_ERROR_NONE] = "None",
+ [MEI_HFS_ERROR_UNCATEGORIZED] = "Uncategorized",
+ [MEI_HFS_ERROR_DISABLED] = "Disabled",
+ [MEI_HFS_ERROR_IMAGE] = "Firmware image",
+ [MEI_HFS_ERROR_DEBUG] = "Debug",
+};
+
+static const char *
+mei_decode(const char *const *tbl, size_t tbl_sz, uint32_t idx)
+{
+
+ if (idx >= tbl_sz || tbl[idx] == NULL)
+ return ("Unknown");
+ return (tbl[idx]);
+}
+#define MEI_DECODE(tbl, idx) mei_decode((tbl), nitems(tbl), (idx))
+
+/*
+ * One-word summary for the log/sysctl consumer who doesn't want to parse
+ * the raw HFS fields: Absent (register read didn't land on a real state),
+ * Disabled (ME explicitly turned off), Active (busy, transitional, or in an
+ * exceptional mode), or Normal (steady-state, with no reported error).
+ */
+static const char *
+mei_state_summary(uint32_t cws, uint32_t opmode, uint32_t error,
+ uint32_t mfg_mode, uint32_t fpt_bad, uint32_t bup_fail,
+ uint32_t fw_init_cmpl, uint32_t update_inprog)
+{
+
+ if (cws >= nitems(mei_cws_values) || mei_cws_values[cws] == NULL)
+ return ("Absent");
+ if (cws == MEI_HFS_CWS_DISABLED ||
+ opmode == MEI_HFS_OPMODE_TEMP_DISABLED ||
+ error == MEI_HFS_ERROR_DISABLED)
+ return ("Disabled");
+ if (cws != MEI_HFS_CWS_NORMAL || opmode != MEI_HFS_OPMODE_NORMAL ||
+ error != MEI_HFS_ERROR_NONE || mfg_mode || fpt_bad || bup_fail ||
+ !fw_init_cmpl || update_inprog)
+ return ("Active");
+ return ("Normal");
+}
+
+struct mei_pci_device {
+ uint16_t device;
+ uint8_t hfs_count;
+ const char *name;
+};
+
+/*
+ * Intel MEI/HECI PCI functions with usable Host Firmware Status registers.
+ * ICH10 has HFS1, PCH5 through PCH7 have HFS1-2, and PCH8 and newer have
+ * HFS1-6. Legacy ICH devices, for which no HFS register is defined, are
+ * deliberately not matched.
+ */
+#define MEI_DEVICE_ICH10(id, desc) { (id), 1, (desc) }
+#define MEI_DEVICE_PCH(id, desc) { (id), 2, (desc) }
+#define MEI_DEVICE_PCH8(id, desc) { (id), 6, (desc) }
+
+static const struct mei_pci_device mei_pci_devices[] = {
+ MEI_DEVICE_ICH10(0x2e04, "Eaglelake MEI"),
+ MEI_DEVICE_ICH10(0x2e14, "Eaglelake MEI"),
+ MEI_DEVICE_ICH10(0x2e24, "Eaglelake MEI"),
+ MEI_DEVICE_ICH10(0x2e34, "Eaglelake MEI"),
+ MEI_DEVICE_PCH(0x3b64, "Calpella MEI"),
+ MEI_DEVICE_PCH(0x3b65, "Calpella MEI"),
+ MEI_DEVICE_PCH(0x1c3a, "Cougar Point MEI"),
+ MEI_DEVICE_PCH(0x1d3a, "C600/X79 Patsburg MEI"),
+ MEI_DEVICE_PCH(0x1e3a, "Panther Point MEI"),
+ MEI_DEVICE_PCH(0x1cba, "Panther Point MEI"),
+ MEI_DEVICE_PCH(0x1dba, "Panther Point MEI"),
+ MEI_DEVICE_PCH8(0x8c3a, "Lynx Point H MEI"),
+ MEI_DEVICE_PCH8(0x8d3a, "Lynx Point Wellsburg MEI"),
+ MEI_DEVICE_PCH8(0x9c3a, "Lynx Point LP MEI"),
+ MEI_DEVICE_PCH8(0x8cba, "Lynx Point H Refresh MEI"),
+ MEI_DEVICE_PCH8(0x9cba, "Wildcat Point LP MEI"),
+ MEI_DEVICE_PCH8(0x9cbb, "Wildcat Point LP 2 MEI"),
+ MEI_DEVICE_PCH8(0x9d3a, "Sunrise Point MEI"),
+ MEI_DEVICE_PCH8(0x9d3b, "Sunrise Point 2 MEI"),
+ MEI_DEVICE_PCH8(0x9d3e, "Sunrise Point 3 (iTouch) MEI"),
+ MEI_DEVICE_PCH8(0xa13a, "Sunrise Point H MEI"),
+ MEI_DEVICE_PCH8(0xa13b, "Sunrise Point H 2 MEI"),
+ MEI_DEVICE_PCH8(0xa1ba, "Lewisburg (SPT) MEI"),
+ MEI_DEVICE_PCH8(0x1a9a, "Broxton M MEI"),
+ MEI_DEVICE_PCH8(0x5a9a, "Apollo Lake I MEI"),
+ MEI_DEVICE_PCH8(0x19e5, "Denverton IE MEI"),
+ MEI_DEVICE_PCH8(0x319a, "Gemini Lake MEI"),
+ MEI_DEVICE_PCH8(0xa2ba, "Kaby Point MEI"),
+ MEI_DEVICE_PCH8(0xa2bb, "Kaby Point 2 MEI"),
+ MEI_DEVICE_PCH8(0xa2be, "Kaby Point 3 (iTouch) MEI"),
+ MEI_DEVICE_PCH8(0x9de0, "Cannon Point LP MEI"),
+ MEI_DEVICE_PCH8(0x9de4, "Cannon Point LP 3 (iTouch) MEI"),
+ MEI_DEVICE_PCH8(0xa360, "Cannon Point H MEI"),
+ MEI_DEVICE_PCH8(0xa364, "Cannon Point H 3 (iTouch) MEI"),
+ MEI_DEVICE_PCH8(0x02e0, "Comet Point LP MEI"),
+ MEI_DEVICE_PCH8(0x02e4, "Comet Point LP 3 (iTouch) MEI"),
+ MEI_DEVICE_PCH8(0xa3ba, "Comet Point Lake V MEI"),
+ MEI_DEVICE_PCH8(0x06e0, "Comet Lake H MEI"),
+ MEI_DEVICE_PCH8(0x06e4, "Comet Lake H 3 (iTouch) MEI"),
+ MEI_DEVICE_PCH8(0x18d3, "Cedar Fork MEI"),
+ MEI_DEVICE_PCH8(0x34e0, "Ice Lake Point LP MEI"),
+ MEI_DEVICE_PCH8(0x38e0, "Ice Lake Point N MEI"),
+ MEI_DEVICE_PCH8(0x4de0, "Jasper Lake Point N MEI"),
+ MEI_DEVICE_PCH8(0xa0e0, "Tiger Lake Point LP MEI"),
+ MEI_DEVICE_PCH8(0x43e0, "Tiger Lake Point H MEI"),
+ MEI_DEVICE_PCH8(0x4b70, "Mule Creek Canyon (EHL) MEI"),
+ MEI_DEVICE_PCH8(0x4b75, "Mule Creek Canyon 4 (EHL) MEI"),
+ MEI_DEVICE_PCH8(0x1be0, "Emmitsburg WS MEI"),
+ MEI_DEVICE_PCH8(0x7ae8, "Alder Lake Point S MEI"),
+ MEI_DEVICE_PCH8(0x7a60, "Alder Lake Point LP MEI"),
+ MEI_DEVICE_PCH8(0x51e0, "Alder Lake Point P MEI"),
+ MEI_DEVICE_PCH8(0x54e0, "Alder Lake Point N MEI"),
+ MEI_DEVICE_PCH8(0x7a68, "Raptor Lake Point S MEI"),
+ MEI_DEVICE_PCH8(0x7e70, "Meteor Lake Point M MEI"),
+ MEI_DEVICE_PCH8(0x7f68, "Arrow Lake Point S MEI"),
+ MEI_DEVICE_PCH8(0x7770, "Arrow Lake Point H MEI"),
+ MEI_DEVICE_PCH8(0xa870, "Lunar Lake Point M MEI"),
+ MEI_DEVICE_PCH8(0xe370, "Panther Lake H MEI"),
+ MEI_DEVICE_PCH8(0xe470, "Panther Lake P MEI"),
+ MEI_DEVICE_PCH8(0x4d70, "Wildcat Lake P MEI"),
+ MEI_DEVICE_PCH8(0x6e68, "Nova Lake Point S MEI"),
+ MEI_DEVICE_PCH8(0xd370, "Nova Lake Point H MEI"),
+ { 0, 0, NULL }
+};
+
+static const struct mei_pci_device *
+mei_pci_find_device(device_t dev)
+{
+ const struct mei_pci_device *id;
+
+ for (id = mei_pci_devices; id->name != NULL; id++) {
+ if (pci_get_device(dev) == id->device)
+ return (id);
+ }
+ return (NULL);
+}
+
+struct mei_softc {
+ device_t sc_dev;
+ struct sysctl_ctx_list *sc_sysctlctx;
+ struct sysctl_oid *sc_sysctlnode;
+ uint32_t sc_hfs[6];
+ uint8_t sc_hfs_count;
+};
+
+static int
+mei_pci_probe(device_t dev)
+{
+ const struct mei_pci_device *id;
+
+ if (pci_get_vendor(dev) != VENDORID_INTEL)
+ return (ENXIO);
+
+ id = mei_pci_find_device(dev);
+ if (id == NULL)
+ return (ENXIO);
+ device_set_desc(dev, id->name);
+ return (BUS_PROBE_DEFAULT);
+}
+
+static int
+mei_pci_attach(device_t dev)
+{
+ const struct mei_pci_device *id;
+ struct mei_softc *sc;
+ const char *summary;
+ static const uint32_t hfs_off[6] = {
+ MEI_PCI_CFG_HFS_1, MEI_PCI_CFG_HFS_2, MEI_PCI_CFG_HFS_3,
+ MEI_PCI_CFG_HFS_4, MEI_PCI_CFG_HFS_5, MEI_PCI_CFG_HFS_6,
+ };
+ uint32_t d0i3, opmode, pm_event, fw_sku;
+ uint32_t cws, opstate, error, mfg_mode, fpt_bad, fw_init_cmpl;
+ uint32_t bup_fail, update_inprog, boot_opt;
+ int i;
+
+ sc = device_get_softc(dev);
+ sc->sc_dev = dev;
+ id = mei_pci_find_device(dev);
+ if (id == NULL)
+ return (ENXIO);
+ sc->sc_hfs_count = id->hfs_count;
+
+ for (i = 0; i < sc->sc_hfs_count; i++)
+ sc->sc_hfs[i] = pci_read_config(dev, hfs_off[i], 4);
+
+ d0i3 = 0;
+ pm_event = 0;
+ fw_sku = 0;
+ if (sc->sc_hfs_count >= 6) {
+ d0i3 = (sc->sc_hfs[0] & MEI_HFS_1_D0I3_MSK) != 0;
+ fw_sku = (sc->sc_hfs[2] & MEI_HFS_3_FW_SKU_MSK) >>
+ MEI_HFS_3_FW_SKU_SHIFT;
+ }
+ if (sc->sc_hfs_count >= 2) {
+ pm_event = (sc->sc_hfs[1] & MEI_HFS_2_PM_EVENT_MSK) >>
+ MEI_HFS_2_PM_EVENT_SHIFT;
+ }
+ opmode = (sc->sc_hfs[0] & MEI_HFS_1_OPMODE_MSK) >>
+ MEI_HFS_1_OPMODE_SHIFT;
+
+ cws = sc->sc_hfs[0] & MEI_HFS_1_CWS_MSK;
+ opstate = (sc->sc_hfs[0] & MEI_HFS_1_OPSTATE_MSK) >>
+ MEI_HFS_1_OPSTATE_SHIFT;
+ error = (sc->sc_hfs[0] & MEI_HFS_1_ERROR_MSK) >> MEI_HFS_1_ERROR_SHIFT;
+ mfg_mode = (sc->sc_hfs[0] & MEI_HFS_1_MFG_MODE_MSK) != 0;
+ fpt_bad = (sc->sc_hfs[0] & MEI_HFS_1_FPT_BAD_MSK) != 0;
+ fw_init_cmpl = (sc->sc_hfs[0] & MEI_HFS_1_FW_INIT_CMPL_MSK) != 0;
+ bup_fail = (sc->sc_hfs[0] & MEI_HFS_1_BUP_FAIL_MSK) != 0;
+ update_inprog = (sc->sc_hfs[0] & MEI_HFS_1_UPDATE_INPROG_MSK) != 0;
+ boot_opt = (sc->sc_hfs[0] & MEI_HFS_1_BOOT_OPT_MSK) != 0;
+ summary = mei_state_summary(cws, opmode, error, mfg_mode, fpt_bad,
+ bup_fail, fw_init_cmpl, update_inprog);
+
+ device_printf(dev,
+ "state=%s opstate=\"%s\" opmode=\"%s\" error=\"%s\" "
+ "mfg_mode=%u fpt_bad=%u fw_init_complete=%u update_in_progress=%u "
+ "boot_options_present=%u\n",
+ MEI_DECODE(mei_cws_values, cws),
+ MEI_DECODE(mei_opstate_values, opstate),
+ MEI_DECODE(mei_opmode_values, opmode),
+ MEI_DECODE(mei_error_values, error),
+ mfg_mode, fpt_bad, fw_init_cmpl, update_inprog, boot_opt);
+
+ device_printf(dev, "ME state: %s\n", summary);
+
+ sc->sc_sysctlctx = device_get_sysctl_ctx(dev);
+ sc->sc_sysctlnode = device_get_sysctl_tree(dev);
+
+ SYSCTL_ADD_U32(sc->sc_sysctlctx, SYSCTL_CHILDREN(sc->sc_sysctlnode),
+ OID_AUTO, "hfs1", CTLFLAG_RD, &sc->sc_hfs[0], 0,
+ "Host Firmware Status 1 (raw)");
+ if (sc->sc_hfs_count >= 2) {
+ SYSCTL_ADD_U32(sc->sc_sysctlctx,
+ SYSCTL_CHILDREN(sc->sc_sysctlnode), OID_AUTO, "hfs2",
+ CTLFLAG_RD, &sc->sc_hfs[1], 0,
+ "Host Firmware Status 2 (raw)");
+ }
+ if (sc->sc_hfs_count >= 6) {
+ SYSCTL_ADD_U32(sc->sc_sysctlctx,
+ SYSCTL_CHILDREN(sc->sc_sysctlnode), OID_AUTO, "hfs3",
+ CTLFLAG_RD, &sc->sc_hfs[2], 0,
+ "Host Firmware Status 3 (raw)");
+ SYSCTL_ADD_U32(sc->sc_sysctlctx,
+ SYSCTL_CHILDREN(sc->sc_sysctlnode), OID_AUTO, "hfs4",
+ CTLFLAG_RD, &sc->sc_hfs[3], 0,
+ "Host Firmware Status 4 (raw)");
+ SYSCTL_ADD_U32(sc->sc_sysctlctx,
+ SYSCTL_CHILDREN(sc->sc_sysctlnode), OID_AUTO, "hfs5",
+ CTLFLAG_RD, &sc->sc_hfs[4], 0,
+ "Host Firmware Status 5 (raw)");
+ SYSCTL_ADD_U32(sc->sc_sysctlctx,
+ SYSCTL_CHILDREN(sc->sc_sysctlnode), OID_AUTO, "hfs6",
+ CTLFLAG_RD, &sc->sc_hfs[5], 0,
+ "Host Firmware Status 6 (raw)");
+ }
+
+ if (sc->sc_hfs_count >= 6) {
+ SYSCTL_ADD_U32(sc->sc_sysctlctx,
+ SYSCTL_CHILDREN(sc->sc_sysctlnode), OID_AUTO, "d0i3",
+ CTLFLAG_RD, NULL, d0i3, "D0i3 power state (from HFS1)");
+ }
+ SYSCTL_ADD_U32(sc->sc_sysctlctx, SYSCTL_CHILDREN(sc->sc_sysctlnode),
+ OID_AUTO, "opmode", CTLFLAG_RD, NULL, opmode,
+ "ME operation mode (from HFS1)");
+ if (sc->sc_hfs_count >= 2) {
+ SYSCTL_ADD_U32(sc->sc_sysctlctx,
+ SYSCTL_CHILDREN(sc->sc_sysctlnode), OID_AUTO, "pm_event",
+ CTLFLAG_RD, NULL, pm_event,
+ "ME power management event (from HFS2)");
+ }
+ if (sc->sc_hfs_count >= 6) {
+ SYSCTL_ADD_U32(sc->sc_sysctlctx,
+ SYSCTL_CHILDREN(sc->sc_sysctlnode), OID_AUTO, "fw_sku",
+ CTLFLAG_RD, NULL, fw_sku,
+ "ME firmware SKU (from HFS3)");
+ }
+ SYSCTL_ADD_U32(sc->sc_sysctlctx, SYSCTL_CHILDREN(sc->sc_sysctlnode),
+ OID_AUTO, "mfg_mode", CTLFLAG_RD, NULL, mfg_mode,
+ "ME running in Manufacturing Mode (from HFS1)");
+ SYSCTL_ADD_U32(sc->sc_sysctlctx, SYSCTL_CHILDREN(sc->sc_sysctlnode),
+ OID_AUTO, "update_in_progress", CTLFLAG_RD, NULL, update_inprog,
+ "ME firmware update in progress (from HFS1)");
+ SYSCTL_ADD_STRING(sc->sc_sysctlctx, SYSCTL_CHILDREN(sc->sc_sysctlnode),
+ OID_AUTO, "state", CTLFLAG_RD,
+ __DECONST(char *, MEI_DECODE(mei_cws_values, cws)), 0,
+ "ME Current Working State (from HFS1)");
+ SYSCTL_ADD_STRING(sc->sc_sysctlctx, SYSCTL_CHILDREN(sc->sc_sysctlnode),
+ OID_AUTO, "opstate", CTLFLAG_RD,
+ __DECONST(char *, MEI_DECODE(mei_opstate_values, opstate)), 0,
+ "ME Current Operation State (from HFS1)");
+ SYSCTL_ADD_STRING(sc->sc_sysctlctx, SYSCTL_CHILDREN(sc->sc_sysctlnode),
+ OID_AUTO, "opmode_str", CTLFLAG_RD,
+ __DECONST(char *, MEI_DECODE(mei_opmode_values, opmode)), 0,
+ "ME Current Operation Mode (from HFS1)");
+ SYSCTL_ADD_STRING(sc->sc_sysctlctx, SYSCTL_CHILDREN(sc->sc_sysctlnode),
+ OID_AUTO, "error", CTLFLAG_RD,
+ __DECONST(char *, MEI_DECODE(mei_error_values, error)), 0,
+ "ME Error Code (from HFS1)");
+ SYSCTL_ADD_STRING(sc->sc_sysctlctx, SYSCTL_CHILDREN(sc->sc_sysctlnode),
+ OID_AUTO, "summary", CTLFLAG_RD, __DECONST(char *, summary), 0,
+ "One-word ME state summary");
+
+ return (0);
+}
+
+static int
+mei_pci_detach(device_t dev)
+{
+
+ return (0);
+}
+
+static device_method_t mei_pci_methods[] = {
+ DEVMETHOD(device_probe, mei_pci_probe),
+ DEVMETHOD(device_attach, mei_pci_attach),
+ DEVMETHOD(device_detach, mei_pci_detach),
+ DEVMETHOD_END
+};
+
+static driver_t mei_pci_driver = {
+ "mei",
+ mei_pci_methods,
+ sizeof(struct mei_softc)
+};
+
+DRIVER_MODULE(mei, pci, mei_pci_driver, 0, 0);
+MODULE_VERSION(mei, 1);
+MODULE_DEPEND(mei, pci, 1, 1, 1);
diff --git a/sys/modules/Makefile b/sys/modules/Makefile
--- a/sys/modules/Makefile
+++ b/sys/modules/Makefile
@@ -255,6 +255,7 @@
${_mana} \
md \
mdio \
+ ${_mei} \
${_mgb} \
mem \
mfi \
@@ -879,6 +880,7 @@
_ioat= ioat
_iwx= iwx
_ixl= ixl
+_mei= mei
_nvdimm= nvdimm
_pms= pms
_pt= pt
diff --git a/sys/modules/mei/Makefile b/sys/modules/mei/Makefile
new file mode 100644
--- /dev/null
+++ b/sys/modules/mei/Makefile
@@ -0,0 +1,11 @@
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com>
+
+.PATH: ${SRCTOP}/sys/dev/mei
+
+KMOD= mei
+SRCS= mei_pci.c mei_fd.c
+SRCS+= bus_if.h device_if.h pci_if.h isa_if.h
+
+.include <bsd.kmod.mk>

File Metadata

Mime Type
text/plain
Expires
Sun, Aug 16, 9:25 PM (15 h, 39 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
36838284
Default Alt Text
D58863.diff (31 KB)

Event Timeline