diff --git a/sys/conf/files.arm64 b/sys/conf/files.arm64 --- a/sys/conf/files.arm64 +++ b/sys/conf/files.arm64 @@ -338,6 +338,7 @@ dev/ipmi/ipmi.c optional ipmi dev/ipmi/ipmi_acpi.c optional ipmi acpi +dev/ipmi/ipmi_bt.c optional ipmi dev/ipmi/ipmi_kcs.c optional ipmi dev/ipmi/ipmi_smic.c optional ipmi diff --git a/sys/conf/files.x86 b/sys/conf/files.x86 --- a/sys/conf/files.x86 +++ b/sys/conf/files.x86 @@ -153,6 +153,7 @@ dev/ipmi/ipmi.c optional ipmi dev/ipmi/ipmi_acpi.c optional ipmi acpi dev/ipmi/ipmi_isa.c optional ipmi isa +dev/ipmi/ipmi_bt.c optional ipmi dev/ipmi/ipmi_kcs.c optional ipmi dev/ipmi/ipmi_smic.c optional ipmi dev/ipmi/ipmi_smbus.c optional ipmi smbus diff --git a/sys/dev/ipmi/ipmi_acpi.c b/sys/dev/ipmi/ipmi_acpi.c --- a/sys/dev/ipmi/ipmi_acpi.c +++ b/sys/dev/ipmi/ipmi_acpi.c @@ -94,16 +94,17 @@ switch (interface_type) { case KCS_MODE: - count = 2; + count = IPMI_IF_KCS_NRES; mode = "KCS"; break; case SMIC_MODE: - count = 3; + count = IPMI_IF_SMIC_NRES; mode = "SMIC"; break; case BT_MODE: - device_printf(dev, "BT interface not supported\n"); - return (ENXIO); + count = IPMI_IF_BT_NRES; + mode = "BT"; + break; case SSIF_MODE: device_printf(dev, "SSIF interface not supported on ACPI\n"); return (0); @@ -170,18 +171,20 @@ * We assume an alignment of 1 byte as currently the IPMI spec * doesn't provide any way to determine the alignment via ACPI. */ + error = ENXIO; switch (interface_type) { case KCS_MODE: error = ipmi_kcs_attach(sc); - if (error) - goto bad; break; case SMIC_MODE: error = ipmi_smic_attach(sc); - if (error) - goto bad; + break; + case BT_MODE: + error = ipmi_bt_attach(sc); break; } + if (error) + goto bad; error = ipmi_attach(dev); if (error) goto bad; diff --git a/sys/dev/ipmi/ipmi_bt.c b/sys/dev/ipmi/ipmi_bt.c new file mode 100644 --- /dev/null +++ b/sys/dev/ipmi/ipmi_bt.c @@ -0,0 +1,296 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2023 Yandex LLC + * Copyright (c) 2023 Andrey V. Elsukov + * + * 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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +/* + * BT interface + */ + +#define DMSG0(sc, fmt, ...) do { \ + device_printf((sc)->ipmi_dev, "BT: %s: " fmt "\n", \ + __func__, ## __VA_ARGS__); \ +} while (0) + +#define DMSGV(...) if (bootverbose) { \ + DMSG0(__VA_ARGS__); \ +} + +#ifdef IPMI_BT_DEBUG +#define DMSG(...) DMSG0(__VA_ARGS__) +#else +#define DMSG(...) +#endif + +#define BT_IO_BASE 0xe4 + +#define BT_CTRL_REG 0 +#define BT_C_CLR_WR_PTR (1L << 0) +#define BT_C_CLR_RD_PTR (1L << 1) +#define BT_C_H2B_ATN (1L << 2) +#define BT_C_B2H_ATN (1L << 3) +#define BT_C_SMS_ATN (1L << 4) +#define BT_C_OEM0 (1L << 5) +#define BT_C_H_BUSY (1L << 6) +#define BT_C_B_BUSY (1L << 7) + +#define BT_CTRL_BITS "\20\01CLR_WR_PTR\02CLR_RD_PTR\03H2B_ATN\04B2H_ATN"\ + "\05SMS_ATN\06OEM0\07H_BUSY\010B_BUSY" + +#define BT_DATA_REG 1 +#define BTMSG_REQLEN 3 +#define BTMSG_REPLEN 4 + +#define BT_INTMASK_REG 2 +#define BT_IM_B2H_IRQ_EN (1L << 0) +#define BT_IM_B2H_IRQ (1L << 1) +#define BT_IM_BMC_HWRST (1L << 7) + +static int bt_polled_request(struct ipmi_softc *, struct ipmi_request *); +static int bt_driver_request(struct ipmi_softc *, struct ipmi_request *, int); +static int bt_wait(struct ipmi_softc *, uint8_t, uint8_t); +static int bt_reset(struct ipmi_softc *); + +static void bt_loop(void *); +static int bt_startup(struct ipmi_softc *); + +#define BT_DELAY_MIN 1 +#define BT_DELAY_MAX 256 + +static int +bt_wait(struct ipmi_softc *sc, uint8_t mask, uint8_t wanted) +{ + volatile uint8_t value; + int delay = BT_DELAY_MIN; + int count = 20000; /* about 5 seconds */ + + while (count--) { + value = INB(sc, BT_CTRL_REG); + if ((value & mask) == wanted) + return (value); + /* + * The wait delay is increased exponentially to avoid putting + * significant load on I/O bus. + */ + DELAY(delay); + if (delay < BT_DELAY_MAX) + delay <<= 1; + } + DMSGV(sc, "failed: m=%b w=%b v=0x%02x\n", + mask, BT_CTRL_BITS, wanted, BT_CTRL_BITS, value); + return (-1); + +} + +static int +bt_reset(struct ipmi_softc *sc) +{ + uint8_t v; + + v = INB(sc, BT_CTRL_REG); + DMSG(sc, "ctrl: %b", v, BT_CTRL_BITS); + v &= BT_C_H_BUSY; /* clear H_BUSY iff it set */ + v |= BT_C_CLR_WR_PTR | BT_C_CLR_RD_PTR | BT_C_B2H_ATN | BT_C_H2B_ATN; + + bt_wait(sc, BT_C_B_BUSY, 0); + OUTB(sc, BT_CTRL_REG, v); + + v = BT_IM_B2H_IRQ | BT_IM_BMC_HWRST; + OUTB(sc, BT_INTMASK_REG, v); + + return (0); +} + +/* + * Send a request message and collect the reply. Returns 1 if we + * succeed. + */ +static int +bt_polled_request(struct ipmi_softc *sc, struct ipmi_request *req) +{ + uint8_t addr, cmd, seq, v; + int i; + + IPMI_IO_LOCK(sc); + + /* + * Send the request: + * + * Byte 1 | Byte 2 | Byte 3 | Byte 4 | Byte 5:N + * -------+-----------+--------+--------+--------- + * Length | NetFn/LUN | Seq | Cmd | Data + */ + + if (bt_wait(sc, BT_C_B_BUSY | BT_C_H2B_ATN, 0) < 0) { + DMSG(sc, "failed to start write transfer"); + goto fail; + } + DMSG(sc, "request: length=%d, addr=0x%02x, seq=%u, cmd=0x%02x", + (int)req->ir_requestlen, req->ir_addr, sc->ipmi_bt_seq, req->ir_command); + OUTB(sc, BT_CTRL_REG, BT_C_CLR_WR_PTR); + OUTB(sc, BT_DATA_REG, req->ir_requestlen + BTMSG_REQLEN); + OUTB(sc, BT_DATA_REG, req->ir_addr); + OUTB(sc, BT_DATA_REG, sc->ipmi_bt_seq); + OUTB(sc, BT_DATA_REG, req->ir_command); + for (i = 0; i < req->ir_requestlen; i++) + OUTB(sc, BT_DATA_REG, req->ir_request[i]); + OUTB(sc, BT_CTRL_REG, BT_C_H2B_ATN); + + if (bt_wait(sc, BT_C_B_BUSY | BT_C_H2B_ATN, 0) < 0) { + DMSG(sc, "failed to finish write transfer"); + goto fail; + } + + /* + * Read the reply: + * + * Byte 1 | Byte 2 | Byte 3 | Byte 4 | Byte 5 | Byte 6:N + * -------+-----------+--------+--------+-----------------+--------- + * Length | NetFn/LUN | Seq | Cmd | Completion code | Data + */ + if (bt_wait(sc, BT_C_B2H_ATN, BT_C_B2H_ATN) < 0) { + DMSG(sc, "got no reply from BMC"); + goto fail; + } + OUTB(sc, BT_CTRL_REG, BT_C_H_BUSY); + OUTB(sc, BT_CTRL_REG, BT_C_B2H_ATN); + OUTB(sc, BT_CTRL_REG, BT_C_CLR_RD_PTR); + + i = INB(sc, BT_DATA_REG); + if (i < BTMSG_REPLEN) { + DMSG(sc, "wrong data length: %d", i); + goto fail; + } + req->ir_replylen = i - BTMSG_REPLEN; + DMSG(sc, "data length: %d, frame length: %d", req->ir_replylen, i); + + addr = INB(sc, BT_DATA_REG); + if (addr != IPMI_REPLY_ADDR(req->ir_addr)) { + DMSGV(sc, "address doesn't match: addr=0x%02x vs. 0x%02x", + req->ir_addr, addr); + } + + seq = INB(sc, BT_DATA_REG); + if (seq != sc->ipmi_bt_seq) { + DMSGV(sc, "seq number doesn't match: seq=0x%02x vs. 0x%02x", + sc->ipmi_bt_seq, seq); + } + + cmd = INB(sc, BT_DATA_REG); + if (cmd != req->ir_command) { + DMSGV(sc, "command doesn't match: cmd=0x%02x vs. 0x%02x", + req->ir_command, cmd); + } + + req->ir_compcode = INB(sc, BT_DATA_REG); + for (i = 0; i < req->ir_replylen; i++) { + v = INB(sc, BT_DATA_REG); + if (i < req->ir_replybuflen) + req->ir_reply[i] = v; + } + + OUTB(sc, BT_CTRL_REG, BT_C_H_BUSY); + IPMI_IO_UNLOCK(sc); + DMSG(sc, "reply: length=%d, addr=0x%02x, seq=%u, cmd=0x%02x, code=0x%02x", + (int)req->ir_replylen, addr, seq, req->ir_command, req->ir_compcode); + return (1); +fail: + bt_reset(sc); + IPMI_IO_UNLOCK(sc); + return (0); +} + +static void +bt_loop(void *arg) +{ + struct ipmi_softc *sc = arg; + struct ipmi_request *req; + + IPMI_LOCK(sc); + while ((req = ipmi_dequeue_request(sc)) != NULL) { + IPMI_UNLOCK(sc); + (void)bt_driver_request(sc, req, 0); + IPMI_LOCK(sc); + sc->ipmi_bt_seq++; + ipmi_complete_request(sc, req); + } + IPMI_UNLOCK(sc); + kproc_exit(0); +} + +static int +bt_startup(struct ipmi_softc *sc) +{ + + return (kproc_create(bt_loop, sc, &sc->ipmi_kthread, 0, 0, "%s: bt", + device_get_nameunit(sc->ipmi_dev))); +} + +static int +bt_driver_request(struct ipmi_softc *sc, struct ipmi_request *req, int timo __unused) +{ + int i, ok; + + ok = 0; + for (i = 0; i < 3 && !ok; i++) + ok = bt_polled_request(sc, req); + if (ok) + req->ir_error = 0; + else + req->ir_error = EIO; + return (req->ir_error); +} + +int +ipmi_bt_attach(struct ipmi_softc *sc) +{ + /* Setup function pointers. */ + sc->ipmi_startup = bt_startup; + sc->ipmi_enqueue_request = ipmi_polled_enqueue_request; + sc->ipmi_driver_request = bt_driver_request; + sc->ipmi_driver_requests_polled = 1; + sc->ipmi_bt_seq = 1; + + return (bt_reset(sc)); +} diff --git a/sys/dev/ipmi/ipmi_isa.c b/sys/dev/ipmi/ipmi_isa.c --- a/sys/dev/ipmi/ipmi_isa.c +++ b/sys/dev/ipmi/ipmi_isa.c @@ -188,16 +188,17 @@ switch (info.iface_type) { case KCS_MODE: - count = 2; + count = IPMI_IF_KCS_NRES; mode = "KCS"; break; case SMIC_MODE: - count = 3; + count = IPMI_IF_SMIC_NRES; mode = "SMIC"; break; case BT_MODE: - device_printf(dev, "BT mode is unsupported\n"); - return (ENXIO); + count = IPMI_IF_BT_NRES; + mode = "BT"; + break; default: return (ENXIO); } @@ -248,19 +249,21 @@ RF_SHAREABLE | RF_ACTIVE); } + error = ENXIO; switch (info.iface_type) { case KCS_MODE: error = ipmi_kcs_attach(sc); - if (error) - goto bad; break; case SMIC_MODE: error = ipmi_smic_attach(sc); - if (error) - goto bad; + break; + case BT_MODE: + error = ipmi_bt_attach(sc); break; } + if (error) + goto bad; error = ipmi_attach(dev); if (error) goto bad; diff --git a/sys/dev/ipmi/ipmi_pci.c b/sys/dev/ipmi/ipmi_pci.c --- a/sys/dev/ipmi/ipmi_pci.c +++ b/sys/dev/ipmi/ipmi_pci.c @@ -112,8 +112,8 @@ mode = "SMIC"; break; case BT_MODE: - device_printf(dev, "BT mode is unsupported\n"); - return (ENXIO); + mode = "BT"; + break; default: device_printf(dev, "No IPMI interface found\n"); return (ENXIO); @@ -143,18 +143,20 @@ sc->ipmi_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->ipmi_irq_rid, RF_SHAREABLE | RF_ACTIVE); + error = ENXIO; switch (info.iface_type) { case KCS_MODE: error = ipmi_kcs_attach(sc); - if (error) - goto bad; break; case SMIC_MODE: error = ipmi_smic_attach(sc); - if (error) - goto bad; + break; + case BT_MODE: + error = ipmi_bt_attach(sc); break; } + if (error) + goto bad; error = ipmi_attach(dev); if (error) goto bad; @@ -215,8 +217,7 @@ break; case PCIP_SERIALBUS_IPMI_BT: iface = BT_MODE; - device_printf(dev, "BT interface unsupported\n"); - return (ENXIO); + break; default: device_printf(dev, "Unsupported interface: %d\n", pci_get_progif(dev)); @@ -242,6 +243,7 @@ sc->ipmi_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ, &sc->ipmi_irq_rid, RF_SHAREABLE | RF_ACTIVE); + error = ENXIO; switch (iface) { case KCS_MODE: device_printf(dev, "using KSC interface\n"); @@ -252,7 +254,6 @@ */ if (!ipmi_kcs_probe_align(sc)) { device_printf(dev, "Unable to determine alignment\n"); - error = ENXIO; goto bad; } @@ -262,12 +263,15 @@ break; case SMIC_MODE: device_printf(dev, "using SMIC interface\n"); - error = ipmi_smic_attach(sc); - if (error) - goto bad; + break; + case BT_MODE: + device_printf(dev, "using BT interface\n"); + error = ipmi_bt_attach(sc); break; } + if (error) + goto bad; error = ipmi_attach(dev); if (error) goto bad; diff --git a/sys/dev/ipmi/ipmi_smbios.c b/sys/dev/ipmi/ipmi_smbios.c --- a/sys/dev/ipmi/ipmi_smbios.c +++ b/sys/dev/ipmi/ipmi_smbios.c @@ -104,6 +104,7 @@ switch (s->interface_type) { case KCS_MODE: case SMIC_MODE: + case BT_MODE: info->address = IPMI_BAR_ADDR(s->base_address) | IPMI_BAM_ADDR_LSB(s->base_address_modifier); info->io_mode = IPMI_BAR_MODE(s->base_address); diff --git a/sys/dev/ipmi/ipmivars.h b/sys/dev/ipmi/ipmivars.h --- a/sys/dev/ipmi/ipmivars.h +++ b/sys/dev/ipmi/ipmivars.h @@ -59,6 +59,10 @@ uint8_t ir_ipmb_command; }; +#define IPMI_IF_KCS_NRES 2 +#define IPMI_IF_SMIC_NRES 3 +#define IPMI_IF_BT_NRES 3 + #define MAX_RES 3 #define KCS_DATA 0 #define KCS_CTL_STS 1 @@ -80,6 +84,10 @@ u_char ipmi_lun; }; +struct ipmi_bt { + uint8_t seq; +}; + struct ipmi_kcs { }; @@ -94,6 +102,7 @@ struct ipmi_softc { device_t ipmi_dev; union { + struct ipmi_bt bt; struct ipmi_kcs kcs; struct ipmi_smic smic; struct ipmi_ssif ssif; @@ -131,11 +140,12 @@ #define ipmi_ssif_smbus_address _iface.ssif.smbus_address #define ipmi_ssif_smbus _iface.ssif.smbus +#define ipmi_bt_seq _iface.bt.seq -#define KCS_MODE 0x01 -#define SMIC_MODE 0x02 -#define BT_MODE 0x03 -#define SSIF_MODE 0x04 +#define KCS_MODE 0x01 +#define SMIC_MODE 0x02 +#define BT_MODE 0x03 +#define SSIF_MODE 0x04 /* KCS status flags */ #define KCS_STATUS_OBF 0x01 /* Data Out ready from BMC */ @@ -252,6 +262,7 @@ int ipmi_kcs_attach(struct ipmi_softc *); int ipmi_kcs_probe_align(struct ipmi_softc *); int ipmi_smic_attach(struct ipmi_softc *); +int ipmi_bt_attach(struct ipmi_softc *); int ipmi_ssif_attach(struct ipmi_softc *, device_t, int); extern int ipmi_attached; diff --git a/sys/modules/ipmi/Makefile b/sys/modules/ipmi/Makefile --- a/sys/modules/ipmi/Makefile +++ b/sys/modules/ipmi/Makefile @@ -10,7 +10,7 @@ .if ${MACHINE_CPUARCH} == "i386" || ${MACHINE_CPUARCH} == "amd64" || \ ${MACHINE_CPUARCH} == "aarch64" -SRCS+= ipmi_kcs.c ipmi_smic.c +SRCS+= ipmi_kcs.c ipmi_smic.c ipmi_bt.c SRCS+= ipmi_acpi.c SRCS+= opt_acpi.h SRCS+= acpi_if.h