xref: /freebsd/sys/dev/ipmi/ipmi_isa.c (revision a05a680469a7ac77b195021fed74e3aa58152dd7)
1d72a0786SJohn Baldwin /*-
24d846d26SWarner Losh  * SPDX-License-Identifier: BSD-2-Clause
3718cf2ccSPedro F. Giffuni  *
4d72a0786SJohn Baldwin  * Copyright (c) 2006 IronPort Systems Inc. <ambrisko@ironport.com>
5d72a0786SJohn Baldwin  * All rights reserved.
6d72a0786SJohn Baldwin  *
7d72a0786SJohn Baldwin  * Redistribution and use in source and binary forms, with or without
8d72a0786SJohn Baldwin  * modification, are permitted provided that the following conditions
9d72a0786SJohn Baldwin  * are met:
10d72a0786SJohn Baldwin  * 1. Redistributions of source code must retain the above copyright
11d72a0786SJohn Baldwin  *    notice, this list of conditions and the following disclaimer.
12d72a0786SJohn Baldwin  * 2. Redistributions in binary form must reproduce the above copyright
13d72a0786SJohn Baldwin  *    notice, this list of conditions and the following disclaimer in the
14d72a0786SJohn Baldwin  *    documentation and/or other materials provided with the distribution.
15d72a0786SJohn Baldwin  *
16d72a0786SJohn Baldwin  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17d72a0786SJohn Baldwin  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18d72a0786SJohn Baldwin  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19d72a0786SJohn Baldwin  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20d72a0786SJohn Baldwin  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21d72a0786SJohn Baldwin  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22d72a0786SJohn Baldwin  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23d72a0786SJohn Baldwin  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24d72a0786SJohn Baldwin  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25d72a0786SJohn Baldwin  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26d72a0786SJohn Baldwin  * SUCH DAMAGE.
27d72a0786SJohn Baldwin  */
28d72a0786SJohn Baldwin 
29d72a0786SJohn Baldwin #include <sys/param.h>
30d72a0786SJohn Baldwin #include <sys/systm.h>
31d72a0786SJohn Baldwin #include <sys/bus.h>
32d72a0786SJohn Baldwin #include <sys/condvar.h>
33d72a0786SJohn Baldwin #include <sys/eventhandler.h>
34d72a0786SJohn Baldwin #include <sys/kernel.h>
35d72a0786SJohn Baldwin #include <sys/module.h>
36d72a0786SJohn Baldwin #include <sys/rman.h>
37d72a0786SJohn Baldwin #include <sys/selinfo.h>
38ee8b757aSYinlong Lu #include <sys/efi.h>
39d72a0786SJohn Baldwin 
40d72a0786SJohn Baldwin #include <machine/pci_cfgreg.h>
41d72a0786SJohn Baldwin #include <dev/pci/pcireg.h>
42d72a0786SJohn Baldwin 
43d72a0786SJohn Baldwin #include <isa/isavar.h>
44d72a0786SJohn Baldwin 
45d72a0786SJohn Baldwin #ifdef LOCAL_MODULE
46d72a0786SJohn Baldwin #include <ipmi.h>
47d72a0786SJohn Baldwin #include <ipmivars.h>
48d72a0786SJohn Baldwin #else
49d72a0786SJohn Baldwin #include <sys/ipmi.h>
50d72a0786SJohn Baldwin #include <dev/ipmi/ipmivars.h>
51d72a0786SJohn Baldwin #endif
52d72a0786SJohn Baldwin 
53d72a0786SJohn Baldwin static void
54d72a0786SJohn Baldwin ipmi_isa_identify(driver_t *driver, device_t parent)
55d72a0786SJohn Baldwin {
56d72a0786SJohn Baldwin 	struct ipmi_get_info info;
57d72a0786SJohn Baldwin 	uint32_t devid;
58d72a0786SJohn Baldwin 
599b256674SSean Bruno 	if (ipmi_smbios_identify(&info) && info.iface_type != SSIF_MODE &&
609b256674SSean Bruno 	    device_find_child(parent, "ipmi", -1) == NULL) {
61d72a0786SJohn Baldwin 		/*
62d72a0786SJohn Baldwin 		 * XXX: Hack alert.  On some broken systems, the IPMI
63d72a0786SJohn Baldwin 		 * interface is described via SMBIOS, but the actual
64d72a0786SJohn Baldwin 		 * IO resource is in a PCI device BAR, so we have to let
65d72a0786SJohn Baldwin 		 * the PCI device attach ipmi instead.  In that case don't
66d72a0786SJohn Baldwin 		 * create an isa ipmi device.  For now we hardcode the list
67d72a0786SJohn Baldwin 		 * of bus, device, function tuples.
68d72a0786SJohn Baldwin 		 */
691587a9dbSJohn Baldwin 		devid = pci_cfgregread(0, 0, 4, 2, PCIR_DEVVENDOR, 4);
70d72a0786SJohn Baldwin 		if (devid != 0xffffffff &&
71d72a0786SJohn Baldwin 		    ipmi_pci_match(devid & 0xffff, devid >> 16) != NULL)
72d72a0786SJohn Baldwin 			return;
73*a05a6804SWarner Losh 		BUS_ADD_CHILD(parent, 0, "ipmi", DEVICE_UNIT_ANY);
74d72a0786SJohn Baldwin 	}
75d72a0786SJohn Baldwin }
76d72a0786SJohn Baldwin 
77d72a0786SJohn Baldwin static int
78d72a0786SJohn Baldwin ipmi_isa_probe(device_t dev)
79d72a0786SJohn Baldwin {
80d72a0786SJohn Baldwin 
81ef3103bbSSean Bruno 	/*
82ef3103bbSSean Bruno 	 * Give other drivers precedence.  Unfortunately, this doesn't
83ef3103bbSSean Bruno 	 * work if we have an SMBIOS table that duplicates a PCI device
84ef3103bbSSean Bruno 	 * that's later on the bus than the PCI-ISA bridge.
85ef3103bbSSean Bruno 	 */
86ef3103bbSSean Bruno 	if (ipmi_attached)
87ef3103bbSSean Bruno 		return (ENXIO);
88ef3103bbSSean Bruno 
89d72a0786SJohn Baldwin 	/* Skip any PNP devices. */
90d72a0786SJohn Baldwin 	if (isa_get_logicalid(dev) != 0)
91d72a0786SJohn Baldwin 		return (ENXIO);
92d72a0786SJohn Baldwin 
93d72a0786SJohn Baldwin 	device_set_desc(dev, "IPMI System Interface");
94d72a0786SJohn Baldwin 	return (BUS_PROBE_DEFAULT);
95d72a0786SJohn Baldwin }
96d72a0786SJohn Baldwin 
97d72a0786SJohn Baldwin static int
9872d73315SDoug Ambrisko ipmi_hint_identify(device_t dev, struct ipmi_get_info *info)
9972d73315SDoug Ambrisko {
10072d73315SDoug Ambrisko 	const char *mode, *name;
10172d73315SDoug Ambrisko 	int i, unit, val;
10272d73315SDoug Ambrisko 
10372d73315SDoug Ambrisko 	/* We require at least a "mode" hint. */
10472d73315SDoug Ambrisko 	name = device_get_name(dev);
10572d73315SDoug Ambrisko 	unit = device_get_unit(dev);
10672d73315SDoug Ambrisko 	if (resource_string_value(name, unit, "mode", &mode) != 0)
10772d73315SDoug Ambrisko 		return (0);
10872d73315SDoug Ambrisko 
10972d73315SDoug Ambrisko 	/* Set the mode and default I/O resources for each mode. */
11072d73315SDoug Ambrisko 	bzero(info, sizeof(struct ipmi_get_info));
11172d73315SDoug Ambrisko 	if (strcasecmp(mode, "KCS") == 0) {
11272d73315SDoug Ambrisko 		info->iface_type = KCS_MODE;
11372d73315SDoug Ambrisko 		info->address = 0xca2;
11472d73315SDoug Ambrisko 		info->io_mode = 1;
11572d73315SDoug Ambrisko 		info->offset = 1;
11672d73315SDoug Ambrisko 	} else if (strcasecmp(mode, "SMIC") == 0) {
11772d73315SDoug Ambrisko 		info->iface_type = SMIC_MODE;
11872d73315SDoug Ambrisko 		info->address = 0xca9;
11972d73315SDoug Ambrisko 		info->io_mode = 1;
12072d73315SDoug Ambrisko 		info->offset = 1;
12172d73315SDoug Ambrisko 	} else if (strcasecmp(mode, "BT") == 0) {
12272d73315SDoug Ambrisko 		info->iface_type = BT_MODE;
12372d73315SDoug Ambrisko 		info->address = 0xe4;
12472d73315SDoug Ambrisko 		info->io_mode = 1;
12572d73315SDoug Ambrisko 		info->offset = 1;
12672d73315SDoug Ambrisko 	} else {
12772d73315SDoug Ambrisko 		device_printf(dev, "Invalid mode %s\n", mode);
12872d73315SDoug Ambrisko 		return (0);
12972d73315SDoug Ambrisko 	}
13072d73315SDoug Ambrisko 
13172d73315SDoug Ambrisko 	/*
13272d73315SDoug Ambrisko 	 * Kill any resources that isahint.c might have setup for us
13372d73315SDoug Ambrisko 	 * since it will conflict with how we do resources.
13472d73315SDoug Ambrisko 	 */
13572d73315SDoug Ambrisko 	for (i = 0; i < 2; i++) {
13672d73315SDoug Ambrisko 		bus_delete_resource(dev, SYS_RES_MEMORY, i);
13772d73315SDoug Ambrisko 		bus_delete_resource(dev, SYS_RES_IOPORT, i);
13872d73315SDoug Ambrisko 	}
13972d73315SDoug Ambrisko 
14005e1ac3cSGordon Bergling 	/* Allow the I/O address to be overridden via hints. */
14172d73315SDoug Ambrisko 	if (resource_int_value(name, unit, "port", &val) == 0 && val != 0) {
14272d73315SDoug Ambrisko 		info->address = val;
14372d73315SDoug Ambrisko 		info->io_mode = 1;
14472d73315SDoug Ambrisko 	} else if (resource_int_value(name, unit, "maddr", &val) == 0 &&
14572d73315SDoug Ambrisko 	    val != 0) {
14672d73315SDoug Ambrisko 		info->address = val;
14772d73315SDoug Ambrisko 		info->io_mode = 0;
14872d73315SDoug Ambrisko 	}
14972d73315SDoug Ambrisko 
15005e1ac3cSGordon Bergling 	/* Allow the spacing to be overridden. */
15172d73315SDoug Ambrisko 	if (resource_int_value(name, unit, "spacing", &val) == 0) {
15272d73315SDoug Ambrisko 		switch (val) {
15372d73315SDoug Ambrisko 		case 8:
15472d73315SDoug Ambrisko 			info->offset = 1;
15572d73315SDoug Ambrisko 			break;
15672d73315SDoug Ambrisko 		case 16:
15772d73315SDoug Ambrisko 			info->offset = 2;
15872d73315SDoug Ambrisko 			break;
15972d73315SDoug Ambrisko 		case 32:
16072d73315SDoug Ambrisko 			info->offset = 4;
16172d73315SDoug Ambrisko 			break;
16272d73315SDoug Ambrisko 		default:
16372d73315SDoug Ambrisko 			device_printf(dev, "Invalid register spacing\n");
16472d73315SDoug Ambrisko 			return (0);
16572d73315SDoug Ambrisko 		}
16672d73315SDoug Ambrisko 	}
16772d73315SDoug Ambrisko 	return (1);
16872d73315SDoug Ambrisko }
16972d73315SDoug Ambrisko 
17072d73315SDoug Ambrisko static int
171d72a0786SJohn Baldwin ipmi_isa_attach(device_t dev)
172d72a0786SJohn Baldwin {
173d72a0786SJohn Baldwin 	struct ipmi_softc *sc = device_get_softc(dev);
174d72a0786SJohn Baldwin 	struct ipmi_get_info info;
175d72a0786SJohn Baldwin 	const char *mode;
176d72a0786SJohn Baldwin 	int count, error, i, type;
177d72a0786SJohn Baldwin 
17872d73315SDoug Ambrisko 	/*
17972d73315SDoug Ambrisko 	 * Pull info out of the SMBIOS table.  If that doesn't work, use
18072d73315SDoug Ambrisko 	 * hints to enumerate a device.
18172d73315SDoug Ambrisko 	 */
18272d73315SDoug Ambrisko 	if (!ipmi_smbios_identify(&info) &&
18372d73315SDoug Ambrisko 	    !ipmi_hint_identify(dev, &info))
184d72a0786SJohn Baldwin 		return (ENXIO);
185d72a0786SJohn Baldwin 
186d72a0786SJohn Baldwin 	switch (info.iface_type) {
187d72a0786SJohn Baldwin 	case KCS_MODE:
1881f166509SAndrey V. Elsukov 		count = IPMI_IF_KCS_NRES;
189d72a0786SJohn Baldwin 		mode = "KCS";
190d72a0786SJohn Baldwin 		break;
191d72a0786SJohn Baldwin 	case SMIC_MODE:
1921f166509SAndrey V. Elsukov 		count = IPMI_IF_SMIC_NRES;
193d72a0786SJohn Baldwin 		mode = "SMIC";
194d72a0786SJohn Baldwin 		break;
195d72a0786SJohn Baldwin 	case BT_MODE:
1961f166509SAndrey V. Elsukov 		count = IPMI_IF_BT_NRES;
1971f166509SAndrey V. Elsukov 		mode = "BT";
1981f166509SAndrey V. Elsukov 		break;
199d72a0786SJohn Baldwin 	default:
200d72a0786SJohn Baldwin 		return (ENXIO);
201d72a0786SJohn Baldwin 	}
202d72a0786SJohn Baldwin 	error = 0;
203d72a0786SJohn Baldwin 	sc->ipmi_dev = dev;
204d72a0786SJohn Baldwin 
205d72a0786SJohn Baldwin 	device_printf(dev, "%s mode found at %s 0x%jx alignment 0x%x on %s\n",
206d72a0786SJohn Baldwin 	    mode, info.io_mode ? "io" : "mem",
207d72a0786SJohn Baldwin 	    (uintmax_t)info.address, info.offset,
208d72a0786SJohn Baldwin 	    device_get_name(device_get_parent(dev)));
209d72a0786SJohn Baldwin 	if (info.io_mode)
210d72a0786SJohn Baldwin 		type = SYS_RES_IOPORT;
211d72a0786SJohn Baldwin 	else
212d72a0786SJohn Baldwin 		type = SYS_RES_MEMORY;
213d72a0786SJohn Baldwin 
214d72a0786SJohn Baldwin 	sc->ipmi_io_type = type;
215d72a0786SJohn Baldwin 	sc->ipmi_io_spacing = info.offset;
216d72a0786SJohn Baldwin 	if (info.offset == 1) {
217d72a0786SJohn Baldwin 		sc->ipmi_io_rid = 0;
218d72a0786SJohn Baldwin 		sc->ipmi_io_res[0] = bus_alloc_resource(dev, type,
219d72a0786SJohn Baldwin 		    &sc->ipmi_io_rid, info.address, info.address + count - 1,
220d72a0786SJohn Baldwin 		    count, RF_ACTIVE);
221d72a0786SJohn Baldwin 		if (sc->ipmi_io_res[0] == NULL) {
222d72a0786SJohn Baldwin 			device_printf(dev, "couldn't configure I/O resource\n");
223d72a0786SJohn Baldwin 			return (ENXIO);
224d72a0786SJohn Baldwin 		}
225d72a0786SJohn Baldwin 	} else {
226d72a0786SJohn Baldwin 		for (i = 0; i < count; i++) {
227d72a0786SJohn Baldwin 			sc->ipmi_io_rid = i;
228d72a0786SJohn Baldwin 			sc->ipmi_io_res[i] = bus_alloc_resource(dev, type,
229d72a0786SJohn Baldwin 			    &sc->ipmi_io_rid, info.address + i * info.offset,
230d72a0786SJohn Baldwin 			    info.address + i * info.offset, 1, RF_ACTIVE);
231d72a0786SJohn Baldwin 			if (sc->ipmi_io_res[i] == NULL) {
232d72a0786SJohn Baldwin 				device_printf(dev,
233d72a0786SJohn Baldwin 				    "couldn't configure I/O resource\n");
234d72a0786SJohn Baldwin 				error = ENXIO;
235d72a0786SJohn Baldwin 				sc->ipmi_io_rid = 0;
236d72a0786SJohn Baldwin 				goto bad;
237d72a0786SJohn Baldwin 			}
238d72a0786SJohn Baldwin 		}
239d72a0786SJohn Baldwin 		sc->ipmi_io_rid = 0;
240d72a0786SJohn Baldwin 	}
241d72a0786SJohn Baldwin 
242d72a0786SJohn Baldwin 	if (info.irq != 0) {
243d72a0786SJohn Baldwin 		sc->ipmi_irq_rid = 0;
244d72a0786SJohn Baldwin 		sc->ipmi_irq_res = bus_alloc_resource(dev, SYS_RES_IRQ,
245d72a0786SJohn Baldwin 		    &sc->ipmi_irq_rid, info.irq, info.irq, 1,
246d72a0786SJohn Baldwin 		    RF_SHAREABLE | RF_ACTIVE);
247d72a0786SJohn Baldwin 	}
248d72a0786SJohn Baldwin 
2491f166509SAndrey V. Elsukov 	error = ENXIO;
250d72a0786SJohn Baldwin 	switch (info.iface_type) {
251d72a0786SJohn Baldwin 	case KCS_MODE:
252d72a0786SJohn Baldwin 		error = ipmi_kcs_attach(sc);
253d72a0786SJohn Baldwin 		break;
254d72a0786SJohn Baldwin 	case SMIC_MODE:
255d72a0786SJohn Baldwin 		error = ipmi_smic_attach(sc);
2561f166509SAndrey V. Elsukov 		break;
2571f166509SAndrey V. Elsukov 	case BT_MODE:
2581f166509SAndrey V. Elsukov 		error = ipmi_bt_attach(sc);
259d72a0786SJohn Baldwin 		break;
260d72a0786SJohn Baldwin 	}
261d72a0786SJohn Baldwin 
2621f166509SAndrey V. Elsukov 	if (error)
2631f166509SAndrey V. Elsukov 		goto bad;
264d72a0786SJohn Baldwin 	error = ipmi_attach(dev);
265d72a0786SJohn Baldwin 	if (error)
266d72a0786SJohn Baldwin 		goto bad;
267d72a0786SJohn Baldwin 
268d72a0786SJohn Baldwin 	return (0);
269d72a0786SJohn Baldwin bad:
270d72a0786SJohn Baldwin 	ipmi_release_resources(dev);
271d72a0786SJohn Baldwin 	return (error);
272d72a0786SJohn Baldwin }
273d72a0786SJohn Baldwin 
274d72a0786SJohn Baldwin static device_method_t ipmi_methods[] = {
275d72a0786SJohn Baldwin 	/* Device interface */
276d72a0786SJohn Baldwin 	DEVMETHOD(device_identify,      ipmi_isa_identify),
277d72a0786SJohn Baldwin 	DEVMETHOD(device_probe,		ipmi_isa_probe),
278d72a0786SJohn Baldwin 	DEVMETHOD(device_attach,	ipmi_isa_attach),
279d72a0786SJohn Baldwin 	DEVMETHOD(device_detach,	ipmi_detach),
280d72a0786SJohn Baldwin 	{ 0, 0 }
281d72a0786SJohn Baldwin };
282d72a0786SJohn Baldwin 
283d72a0786SJohn Baldwin static driver_t ipmi_isa_driver = {
284d72a0786SJohn Baldwin 	"ipmi",
285d72a0786SJohn Baldwin 	ipmi_methods,
286d72a0786SJohn Baldwin 	sizeof(struct ipmi_softc),
287d72a0786SJohn Baldwin };
288d72a0786SJohn Baldwin 
289fd773e2bSJohn Baldwin DRIVER_MODULE(ipmi_isa, isa, ipmi_isa_driver, 0, 0);
290ee8b757aSYinlong Lu #ifdef ARCH_MAY_USE_EFI
291ee8b757aSYinlong Lu MODULE_DEPEND(ipmi_isa, efirt, 1, 1, 1);
292ee8b757aSYinlong Lu #endif
293