xref: /freebsd/sys/dev/ipmi/ipmi_bt.c (revision 366d6a424e1faad9c151c5794978e218a79fbed8)
11f166509SAndrey V. Elsukov /*-
21f166509SAndrey V. Elsukov  * SPDX-License-Identifier: BSD-2-Clause
31f166509SAndrey V. Elsukov  *
41f166509SAndrey V. Elsukov  * Copyright (c) 2023 Yandex LLC
51f166509SAndrey V. Elsukov  * Copyright (c) 2023 Andrey V. Elsukov <ae@FreeBSD.org>
61f166509SAndrey V. Elsukov  *
71f166509SAndrey V. Elsukov  * Redistribution and use in source and binary forms, with or without
81f166509SAndrey V. Elsukov  * modification, are permitted provided that the following conditions
91f166509SAndrey V. Elsukov  * are met:
101f166509SAndrey V. Elsukov  * 1. Redistributions of source code must retain the above copyright
111f166509SAndrey V. Elsukov  *    notice, this list of conditions and the following disclaimer.
121f166509SAndrey V. Elsukov  * 2. Redistributions in binary form must reproduce the above copyright
131f166509SAndrey V. Elsukov  *    notice, this list of conditions and the following disclaimer in the
141f166509SAndrey V. Elsukov  *    documentation and/or other materials provided with the distribution.
151f166509SAndrey V. Elsukov  *
161f166509SAndrey V. Elsukov  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
171f166509SAndrey V. Elsukov  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
181f166509SAndrey V. Elsukov  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
191f166509SAndrey V. Elsukov  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
201f166509SAndrey V. Elsukov  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
211f166509SAndrey V. Elsukov  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
221f166509SAndrey V. Elsukov  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
231f166509SAndrey V. Elsukov  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
241f166509SAndrey V. Elsukov  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
251f166509SAndrey V. Elsukov  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
261f166509SAndrey V. Elsukov  * SUCH DAMAGE.
271f166509SAndrey V. Elsukov  */
281f166509SAndrey V. Elsukov 
291f166509SAndrey V. Elsukov #include <sys/param.h>
301f166509SAndrey V. Elsukov #include <sys/systm.h>
311f166509SAndrey V. Elsukov #include <sys/bus.h>
321f166509SAndrey V. Elsukov #include <sys/condvar.h>
331f166509SAndrey V. Elsukov #include <sys/eventhandler.h>
341f166509SAndrey V. Elsukov #include <sys/kernel.h>
351f166509SAndrey V. Elsukov #include <sys/kthread.h>
361f166509SAndrey V. Elsukov #include <sys/module.h>
371f166509SAndrey V. Elsukov #include <sys/rman.h>
381f166509SAndrey V. Elsukov #include <sys/selinfo.h>
391f166509SAndrey V. Elsukov #include <machine/bus.h>
401f166509SAndrey V. Elsukov 
411f166509SAndrey V. Elsukov #include <sys/ipmi.h>
421f166509SAndrey V. Elsukov #include <dev/ipmi/ipmivars.h>
431f166509SAndrey V. Elsukov 
441f166509SAndrey V. Elsukov /*
451f166509SAndrey V. Elsukov  * BT interface
461f166509SAndrey V. Elsukov  */
471f166509SAndrey V. Elsukov 
481f166509SAndrey V. Elsukov #define	DMSG0(sc, fmt, ...)	do {				\
491f166509SAndrey V. Elsukov 	device_printf((sc)->ipmi_dev, "BT: %s: " fmt "\n",	\
501f166509SAndrey V. Elsukov 	    __func__, ## __VA_ARGS__);				\
511f166509SAndrey V. Elsukov } while (0)
521f166509SAndrey V. Elsukov 
531f166509SAndrey V. Elsukov #define	DMSGV(...)		if (bootverbose) {		\
541f166509SAndrey V. Elsukov 	DMSG0(__VA_ARGS__);					\
551f166509SAndrey V. Elsukov }
561f166509SAndrey V. Elsukov 
571f166509SAndrey V. Elsukov #ifdef IPMI_BT_DEBUG
581f166509SAndrey V. Elsukov #define	DMSG(...)		DMSG0(__VA_ARGS__)
591f166509SAndrey V. Elsukov #else
601f166509SAndrey V. Elsukov #define	DMSG(...)
611f166509SAndrey V. Elsukov #endif
621f166509SAndrey V. Elsukov 
631f166509SAndrey V. Elsukov #define	BT_IO_BASE		0xe4
641f166509SAndrey V. Elsukov 
651f166509SAndrey V. Elsukov #define	BT_CTRL_REG		0
661f166509SAndrey V. Elsukov #define	  BT_C_CLR_WR_PTR	(1L << 0)
671f166509SAndrey V. Elsukov #define	  BT_C_CLR_RD_PTR	(1L << 1)
681f166509SAndrey V. Elsukov #define	  BT_C_H2B_ATN		(1L << 2)
691f166509SAndrey V. Elsukov #define	  BT_C_B2H_ATN		(1L << 3)
701f166509SAndrey V. Elsukov #define	  BT_C_SMS_ATN		(1L << 4)
711f166509SAndrey V. Elsukov #define	  BT_C_OEM0		(1L << 5)
721f166509SAndrey V. Elsukov #define	  BT_C_H_BUSY		(1L << 6)
731f166509SAndrey V. Elsukov #define	  BT_C_B_BUSY		(1L << 7)
741f166509SAndrey V. Elsukov 
751f166509SAndrey V. Elsukov #define	BT_CTRL_BITS		"\20\01CLR_WR_PTR\02CLR_RD_PTR\03H2B_ATN\04B2H_ATN"\
761f166509SAndrey V. Elsukov 				"\05SMS_ATN\06OEM0\07H_BUSY\010B_BUSY"
771f166509SAndrey V. Elsukov 
781f166509SAndrey V. Elsukov #define	BT_DATA_REG		1
791f166509SAndrey V. Elsukov #define	 BTMSG_REQLEN		3
801f166509SAndrey V. Elsukov #define	 BTMSG_REPLEN		4
811f166509SAndrey V. Elsukov 
821f166509SAndrey V. Elsukov #define	BT_INTMASK_REG		2
831f166509SAndrey V. Elsukov #define	 BT_IM_B2H_IRQ_EN	(1L << 0)
841f166509SAndrey V. Elsukov #define	 BT_IM_B2H_IRQ		(1L << 1)
851f166509SAndrey V. Elsukov #define	 BT_IM_BMC_HWRST	(1L << 7)
861f166509SAndrey V. Elsukov 
871f166509SAndrey V. Elsukov static int bt_polled_request(struct ipmi_softc *, struct ipmi_request *);
88*366d6a42SGleb Smirnoff static int bt_driver_request(struct ipmi_softc *, struct ipmi_request *);
891f166509SAndrey V. Elsukov static int bt_wait(struct ipmi_softc *, uint8_t, uint8_t);
901f166509SAndrey V. Elsukov static int bt_reset(struct ipmi_softc *);
911f166509SAndrey V. Elsukov 
921f166509SAndrey V. Elsukov static void bt_loop(void *);
931f166509SAndrey V. Elsukov static int bt_startup(struct ipmi_softc *);
941f166509SAndrey V. Elsukov 
951f166509SAndrey V. Elsukov #define	BT_DELAY_MIN	1
961f166509SAndrey V. Elsukov #define	BT_DELAY_MAX	256
971f166509SAndrey V. Elsukov 
981f166509SAndrey V. Elsukov static int
bt_wait(struct ipmi_softc * sc,uint8_t mask,uint8_t wanted)991f166509SAndrey V. Elsukov bt_wait(struct ipmi_softc *sc, uint8_t mask, uint8_t wanted)
1001f166509SAndrey V. Elsukov {
1011f166509SAndrey V. Elsukov 	volatile uint8_t value;
1021f166509SAndrey V. Elsukov 	int delay = BT_DELAY_MIN;
1031f166509SAndrey V. Elsukov 	int count = 20000; /* about 5 seconds */
1041f166509SAndrey V. Elsukov 
1051f166509SAndrey V. Elsukov 	while (count--) {
1061f166509SAndrey V. Elsukov 		value = INB(sc, BT_CTRL_REG);
1071f166509SAndrey V. Elsukov 		if ((value & mask) == wanted)
1081f166509SAndrey V. Elsukov 			return (value);
1091f166509SAndrey V. Elsukov 		/*
1101f166509SAndrey V. Elsukov 		 * The wait delay is increased exponentially to avoid putting
1111f166509SAndrey V. Elsukov 		 * significant load on I/O bus.
1121f166509SAndrey V. Elsukov 		 */
1131f166509SAndrey V. Elsukov 		DELAY(delay);
1141f166509SAndrey V. Elsukov 		if (delay < BT_DELAY_MAX)
1151f166509SAndrey V. Elsukov 			delay <<= 1;
1161f166509SAndrey V. Elsukov 	}
1171f166509SAndrey V. Elsukov 	DMSGV(sc, "failed: m=%b w=%b v=0x%02x\n",
1181f166509SAndrey V. Elsukov 	    mask, BT_CTRL_BITS, wanted, BT_CTRL_BITS, value);
1191f166509SAndrey V. Elsukov 	return (-1);
1201f166509SAndrey V. Elsukov 
1211f166509SAndrey V. Elsukov }
1221f166509SAndrey V. Elsukov 
1231f166509SAndrey V. Elsukov static int
bt_reset(struct ipmi_softc * sc)1241f166509SAndrey V. Elsukov bt_reset(struct ipmi_softc *sc)
1251f166509SAndrey V. Elsukov {
1261f166509SAndrey V. Elsukov 	uint8_t v;
1271f166509SAndrey V. Elsukov 
1281f166509SAndrey V. Elsukov 	v = INB(sc, BT_CTRL_REG);
1291f166509SAndrey V. Elsukov 	DMSG(sc, "ctrl: %b", v, BT_CTRL_BITS);
1301f166509SAndrey V. Elsukov 	v &= BT_C_H_BUSY; /* clear H_BUSY iff it set */
1311f166509SAndrey V. Elsukov 	v |= BT_C_CLR_WR_PTR | BT_C_CLR_RD_PTR | BT_C_B2H_ATN | BT_C_H2B_ATN;
1321f166509SAndrey V. Elsukov 
1331f166509SAndrey V. Elsukov 	bt_wait(sc, BT_C_B_BUSY, 0);
1341f166509SAndrey V. Elsukov 	OUTB(sc, BT_CTRL_REG, v);
1351f166509SAndrey V. Elsukov 
1361f166509SAndrey V. Elsukov 	v = BT_IM_B2H_IRQ | BT_IM_BMC_HWRST;
1371f166509SAndrey V. Elsukov 	OUTB(sc, BT_INTMASK_REG, v);
1381f166509SAndrey V. Elsukov 
1391f166509SAndrey V. Elsukov 	return (0);
1401f166509SAndrey V. Elsukov }
1411f166509SAndrey V. Elsukov 
1421f166509SAndrey V. Elsukov /*
1431f166509SAndrey V. Elsukov  * Send a request message and collect the reply. Returns 1 if we
1441f166509SAndrey V. Elsukov  * succeed.
1451f166509SAndrey V. Elsukov  */
1461f166509SAndrey V. Elsukov static int
bt_polled_request(struct ipmi_softc * sc,struct ipmi_request * req)1471f166509SAndrey V. Elsukov bt_polled_request(struct ipmi_softc *sc, struct ipmi_request *req)
1481f166509SAndrey V. Elsukov {
1491f166509SAndrey V. Elsukov 	uint8_t addr, cmd, seq, v;
1501f166509SAndrey V. Elsukov 	int i;
1511f166509SAndrey V. Elsukov 
1521f166509SAndrey V. Elsukov 	IPMI_IO_LOCK(sc);
1531f166509SAndrey V. Elsukov 
1541f166509SAndrey V. Elsukov 	/*
1551f166509SAndrey V. Elsukov 	 * Send the request:
1561f166509SAndrey V. Elsukov 	 *
1571f166509SAndrey V. Elsukov 	 * Byte 1 | Byte 2    | Byte 3 | Byte 4 | Byte 5:N
1581f166509SAndrey V. Elsukov 	 * -------+-----------+--------+--------+---------
1591f166509SAndrey V. Elsukov 	 * Length | NetFn/LUN | Seq    | Cmd    | Data
1601f166509SAndrey V. Elsukov 	 */
1611f166509SAndrey V. Elsukov 
1621f166509SAndrey V. Elsukov 	if (bt_wait(sc, BT_C_B_BUSY | BT_C_H2B_ATN, 0) < 0) {
1631f166509SAndrey V. Elsukov 		DMSG(sc, "failed to start write transfer");
1641f166509SAndrey V. Elsukov 		goto fail;
1651f166509SAndrey V. Elsukov 	}
1661f166509SAndrey V. Elsukov 	DMSG(sc, "request: length=%d, addr=0x%02x, seq=%u, cmd=0x%02x",
1671f166509SAndrey V. Elsukov 	    (int)req->ir_requestlen, req->ir_addr, sc->ipmi_bt_seq, req->ir_command);
1681f166509SAndrey V. Elsukov 	OUTB(sc, BT_CTRL_REG, BT_C_CLR_WR_PTR);
1691f166509SAndrey V. Elsukov 	OUTB(sc, BT_DATA_REG, req->ir_requestlen + BTMSG_REQLEN);
1701f166509SAndrey V. Elsukov 	OUTB(sc, BT_DATA_REG, req->ir_addr);
1711f166509SAndrey V. Elsukov 	OUTB(sc, BT_DATA_REG, sc->ipmi_bt_seq);
1721f166509SAndrey V. Elsukov 	OUTB(sc, BT_DATA_REG, req->ir_command);
1731f166509SAndrey V. Elsukov 	for (i = 0; i < req->ir_requestlen; i++)
1741f166509SAndrey V. Elsukov 		OUTB(sc, BT_DATA_REG, req->ir_request[i]);
1751f166509SAndrey V. Elsukov 	OUTB(sc, BT_CTRL_REG, BT_C_H2B_ATN);
1761f166509SAndrey V. Elsukov 
1771f166509SAndrey V. Elsukov 	if (bt_wait(sc, BT_C_B_BUSY | BT_C_H2B_ATN, 0) < 0) {
1781f166509SAndrey V. Elsukov 		DMSG(sc, "failed to finish write transfer");
1791f166509SAndrey V. Elsukov 		goto fail;
1801f166509SAndrey V. Elsukov 	}
1811f166509SAndrey V. Elsukov 
1821f166509SAndrey V. Elsukov 	/*
1831f166509SAndrey V. Elsukov 	 * Read the reply:
1841f166509SAndrey V. Elsukov 	 *
1851f166509SAndrey V. Elsukov 	 * Byte 1 | Byte 2    | Byte 3 | Byte 4 | Byte 5          | Byte 6:N
1861f166509SAndrey V. Elsukov 	 * -------+-----------+--------+--------+-----------------+---------
1871f166509SAndrey V. Elsukov 	 * Length | NetFn/LUN | Seq    | Cmd    | Completion code | Data
1881f166509SAndrey V. Elsukov 	 */
1891f166509SAndrey V. Elsukov 	if (bt_wait(sc, BT_C_B2H_ATN, BT_C_B2H_ATN) < 0) {
1901f166509SAndrey V. Elsukov 		DMSG(sc, "got no reply from BMC");
1911f166509SAndrey V. Elsukov 		goto fail;
1921f166509SAndrey V. Elsukov 	}
1931f166509SAndrey V. Elsukov 	OUTB(sc, BT_CTRL_REG, BT_C_H_BUSY);
1941f166509SAndrey V. Elsukov 	OUTB(sc, BT_CTRL_REG, BT_C_B2H_ATN);
1951f166509SAndrey V. Elsukov 	OUTB(sc, BT_CTRL_REG, BT_C_CLR_RD_PTR);
1961f166509SAndrey V. Elsukov 
1971f166509SAndrey V. Elsukov 	i = INB(sc, BT_DATA_REG);
1981f166509SAndrey V. Elsukov 	if (i < BTMSG_REPLEN) {
1991f166509SAndrey V. Elsukov 		DMSG(sc, "wrong data length: %d", i);
2001f166509SAndrey V. Elsukov 		goto fail;
2011f166509SAndrey V. Elsukov 	}
2021f166509SAndrey V. Elsukov 	req->ir_replylen = i - BTMSG_REPLEN;
2031f166509SAndrey V. Elsukov 	DMSG(sc, "data length: %d, frame length: %d", req->ir_replylen, i);
2041f166509SAndrey V. Elsukov 
2051f166509SAndrey V. Elsukov 	addr = INB(sc, BT_DATA_REG);
2061f166509SAndrey V. Elsukov 	if (addr != IPMI_REPLY_ADDR(req->ir_addr)) {
2071f166509SAndrey V. Elsukov 		DMSGV(sc, "address doesn't match: addr=0x%02x vs. 0x%02x",
2081f166509SAndrey V. Elsukov 		    req->ir_addr, addr);
2091f166509SAndrey V. Elsukov 	}
2101f166509SAndrey V. Elsukov 
2111f166509SAndrey V. Elsukov 	seq = INB(sc, BT_DATA_REG);
2121f166509SAndrey V. Elsukov 	if (seq != sc->ipmi_bt_seq) {
2131f166509SAndrey V. Elsukov 		DMSGV(sc, "seq number doesn't match: seq=0x%02x vs. 0x%02x",
2141f166509SAndrey V. Elsukov 		    sc->ipmi_bt_seq, seq);
2151f166509SAndrey V. Elsukov 	}
2161f166509SAndrey V. Elsukov 
2171f166509SAndrey V. Elsukov 	cmd = INB(sc, BT_DATA_REG);
2181f166509SAndrey V. Elsukov 	if (cmd != req->ir_command) {
2191f166509SAndrey V. Elsukov 		DMSGV(sc, "command doesn't match: cmd=0x%02x vs. 0x%02x",
2201f166509SAndrey V. Elsukov 		    req->ir_command, cmd);
2211f166509SAndrey V. Elsukov 	}
2221f166509SAndrey V. Elsukov 
2231f166509SAndrey V. Elsukov 	req->ir_compcode = INB(sc, BT_DATA_REG);
2241f166509SAndrey V. Elsukov 	for (i = 0; i < req->ir_replylen; i++) {
2251f166509SAndrey V. Elsukov 		v = INB(sc, BT_DATA_REG);
2261f166509SAndrey V. Elsukov 		if (i < req->ir_replybuflen)
2271f166509SAndrey V. Elsukov 			req->ir_reply[i] = v;
2281f166509SAndrey V. Elsukov 	}
2291f166509SAndrey V. Elsukov 
2301f166509SAndrey V. Elsukov 	OUTB(sc, BT_CTRL_REG, BT_C_H_BUSY);
2311f166509SAndrey V. Elsukov 	IPMI_IO_UNLOCK(sc);
2321f166509SAndrey V. Elsukov 	DMSG(sc, "reply: length=%d, addr=0x%02x, seq=%u, cmd=0x%02x, code=0x%02x",
2331f166509SAndrey V. Elsukov 	    (int)req->ir_replylen, addr, seq, req->ir_command, req->ir_compcode);
2341f166509SAndrey V. Elsukov 	return (1);
2351f166509SAndrey V. Elsukov fail:
2361f166509SAndrey V. Elsukov 	bt_reset(sc);
2371f166509SAndrey V. Elsukov 	IPMI_IO_UNLOCK(sc);
2381f166509SAndrey V. Elsukov 	return (0);
2391f166509SAndrey V. Elsukov }
2401f166509SAndrey V. Elsukov 
2411f166509SAndrey V. Elsukov static void
bt_loop(void * arg)2421f166509SAndrey V. Elsukov bt_loop(void *arg)
2431f166509SAndrey V. Elsukov {
2441f166509SAndrey V. Elsukov 	struct ipmi_softc *sc = arg;
2451f166509SAndrey V. Elsukov 	struct ipmi_request *req;
2461f166509SAndrey V. Elsukov 
2471f166509SAndrey V. Elsukov 	IPMI_LOCK(sc);
2481f166509SAndrey V. Elsukov 	while ((req = ipmi_dequeue_request(sc)) != NULL) {
2491f166509SAndrey V. Elsukov 		IPMI_UNLOCK(sc);
250*366d6a42SGleb Smirnoff 		(void)bt_driver_request(sc, req);
2511f166509SAndrey V. Elsukov 		IPMI_LOCK(sc);
2521f166509SAndrey V. Elsukov 		sc->ipmi_bt_seq++;
2531f166509SAndrey V. Elsukov 		ipmi_complete_request(sc, req);
2541f166509SAndrey V. Elsukov 	}
2551f166509SAndrey V. Elsukov 	IPMI_UNLOCK(sc);
2561f166509SAndrey V. Elsukov 	kproc_exit(0);
2571f166509SAndrey V. Elsukov }
2581f166509SAndrey V. Elsukov 
2591f166509SAndrey V. Elsukov static int
bt_startup(struct ipmi_softc * sc)2601f166509SAndrey V. Elsukov bt_startup(struct ipmi_softc *sc)
2611f166509SAndrey V. Elsukov {
2621f166509SAndrey V. Elsukov 
2631f166509SAndrey V. Elsukov 	return (kproc_create(bt_loop, sc, &sc->ipmi_kthread, 0, 0, "%s: bt",
2641f166509SAndrey V. Elsukov 	    device_get_nameunit(sc->ipmi_dev)));
2651f166509SAndrey V. Elsukov }
2661f166509SAndrey V. Elsukov 
2671f166509SAndrey V. Elsukov static int
bt_driver_request(struct ipmi_softc * sc,struct ipmi_request * req)268*366d6a42SGleb Smirnoff bt_driver_request(struct ipmi_softc *sc, struct ipmi_request *req)
2691f166509SAndrey V. Elsukov {
2701f166509SAndrey V. Elsukov 	int i, ok;
2711f166509SAndrey V. Elsukov 
2721f166509SAndrey V. Elsukov 	ok = 0;
2731f166509SAndrey V. Elsukov 	for (i = 0; i < 3 && !ok; i++)
2741f166509SAndrey V. Elsukov 		ok = bt_polled_request(sc, req);
2751f166509SAndrey V. Elsukov 	if (ok)
2761f166509SAndrey V. Elsukov 		req->ir_error = 0;
2771f166509SAndrey V. Elsukov 	else
2781f166509SAndrey V. Elsukov 		req->ir_error = EIO;
2791f166509SAndrey V. Elsukov 	return (req->ir_error);
2801f166509SAndrey V. Elsukov }
2811f166509SAndrey V. Elsukov 
2821f166509SAndrey V. Elsukov int
ipmi_bt_attach(struct ipmi_softc * sc)2831f166509SAndrey V. Elsukov ipmi_bt_attach(struct ipmi_softc *sc)
2841f166509SAndrey V. Elsukov {
2851f166509SAndrey V. Elsukov 	/* Setup function pointers. */
2861f166509SAndrey V. Elsukov 	sc->ipmi_startup = bt_startup;
2871f166509SAndrey V. Elsukov 	sc->ipmi_enqueue_request = ipmi_polled_enqueue_request;
2881f166509SAndrey V. Elsukov 	sc->ipmi_driver_request = bt_driver_request;
2891f166509SAndrey V. Elsukov 	sc->ipmi_driver_requests_polled = 1;
2901f166509SAndrey V. Elsukov 	sc->ipmi_bt_seq = 1;
2911f166509SAndrey V. Elsukov 
2921f166509SAndrey V. Elsukov 	return (bt_reset(sc));
2931f166509SAndrey V. Elsukov }
294