ipmi_isa.c revision 171464
1162562Sjhb/*-
2162562Sjhb * Copyright (c) 2006 IronPort Systems Inc. <ambrisko@ironport.com>
3162562Sjhb * All rights reserved.
4162562Sjhb *
5162562Sjhb * Redistribution and use in source and binary forms, with or without
6162562Sjhb * modification, are permitted provided that the following conditions
7162562Sjhb * are met:
8162562Sjhb * 1. Redistributions of source code must retain the above copyright
9162562Sjhb *    notice, this list of conditions and the following disclaimer.
10162562Sjhb * 2. Redistributions in binary form must reproduce the above copyright
11162562Sjhb *    notice, this list of conditions and the following disclaimer in the
12162562Sjhb *    documentation and/or other materials provided with the distribution.
13162562Sjhb *
14162562Sjhb * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15162562Sjhb * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16162562Sjhb * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17162562Sjhb * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18162562Sjhb * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19162562Sjhb * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20162562Sjhb * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21162562Sjhb * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22162562Sjhb * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23162562Sjhb * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24162562Sjhb * SUCH DAMAGE.
25162562Sjhb */
26162562Sjhb
27162562Sjhb#include <sys/cdefs.h>
28162562Sjhb__FBSDID("$FreeBSD: head/sys/dev/ipmi/ipmi_isa.c 171464 2007-07-16 17:03:48Z ambrisko $");
29162562Sjhb
30162562Sjhb#include <sys/param.h>
31162562Sjhb#include <sys/systm.h>
32162562Sjhb#include <sys/bus.h>
33162562Sjhb#include <sys/condvar.h>
34162562Sjhb#include <sys/eventhandler.h>
35162562Sjhb#include <sys/kernel.h>
36162562Sjhb#include <sys/module.h>
37162562Sjhb#include <sys/rman.h>
38162562Sjhb#include <sys/selinfo.h>
39162562Sjhb
40162562Sjhb#include <machine/pci_cfgreg.h>
41162562Sjhb#include <dev/pci/pcireg.h>
42162562Sjhb
43162562Sjhb#include <isa/isavar.h>
44162562Sjhb
45162562Sjhb#ifdef LOCAL_MODULE
46162562Sjhb#include <ipmi.h>
47162562Sjhb#include <ipmivars.h>
48162562Sjhb#else
49162562Sjhb#include <sys/ipmi.h>
50162562Sjhb#include <dev/ipmi/ipmivars.h>
51162562Sjhb#endif
52162562Sjhb
53162562Sjhbstatic void
54162562Sjhbipmi_isa_identify(driver_t *driver, device_t parent)
55162562Sjhb{
56162562Sjhb	struct ipmi_get_info info;
57162562Sjhb	uint32_t devid;
58162562Sjhb
59162562Sjhb	if (ipmi_smbios_identify(&info) && info.iface_type != SSIF_MODE &&
60162562Sjhb	    device_find_child(parent, "ipmi", -1) == NULL) {
61162562Sjhb		/*
62162562Sjhb		 * XXX: Hack alert.  On some broken systems, the IPMI
63162562Sjhb		 * interface is described via SMBIOS, but the actual
64162562Sjhb		 * IO resource is in a PCI device BAR, so we have to let
65162562Sjhb		 * the PCI device attach ipmi instead.  In that case don't
66162562Sjhb		 * create an isa ipmi device.  For now we hardcode the list
67162562Sjhb		 * of bus, device, function tuples.
68162562Sjhb		 */
69162562Sjhb		devid = pci_cfgregread(0, 4, 2, PCIR_DEVVENDOR, 4);
70162562Sjhb		if (devid != 0xffffffff &&
71162562Sjhb		    ipmi_pci_match(devid & 0xffff, devid >> 16) != NULL)
72162562Sjhb			return;
73162562Sjhb		BUS_ADD_CHILD(parent, 0, "ipmi", -1);
74162562Sjhb	}
75162562Sjhb}
76162562Sjhb
77162562Sjhbstatic int
78162562Sjhbipmi_isa_probe(device_t dev)
79162562Sjhb{
80162562Sjhb
81162562Sjhb	/* Skip any PNP devices. */
82162562Sjhb	if (isa_get_logicalid(dev) != 0)
83162562Sjhb		return (ENXIO);
84162562Sjhb
85162562Sjhb	device_set_desc(dev, "IPMI System Interface");
86162562Sjhb	return (BUS_PROBE_DEFAULT);
87162562Sjhb}
88162562Sjhb
89162562Sjhbstatic int
90171464Sambriskoipmi_hint_identify(device_t dev, struct ipmi_get_info *info)
91171464Sambrisko{
92171464Sambrisko	const char *mode, *name;
93171464Sambrisko	int i, unit, val;
94171464Sambrisko
95171464Sambrisko	/* We require at least a "mode" hint. */
96171464Sambrisko	name = device_get_name(dev);
97171464Sambrisko	unit = device_get_unit(dev);
98171464Sambrisko	if (resource_string_value(name, unit, "mode", &mode) != 0)
99171464Sambrisko		return (0);
100171464Sambrisko
101171464Sambrisko	/* Set the mode and default I/O resources for each mode. */
102171464Sambrisko	bzero(info, sizeof(struct ipmi_get_info));
103171464Sambrisko	if (strcasecmp(mode, "KCS") == 0) {
104171464Sambrisko		info->iface_type = KCS_MODE;
105171464Sambrisko		info->address = 0xca2;
106171464Sambrisko		info->io_mode = 1;
107171464Sambrisko		info->offset = 1;
108171464Sambrisko	} else if (strcasecmp(mode, "SMIC") == 0) {
109171464Sambrisko		info->iface_type = SMIC_MODE;
110171464Sambrisko		info->address = 0xca9;
111171464Sambrisko		info->io_mode = 1;
112171464Sambrisko		info->offset = 1;
113171464Sambrisko	} else if (strcasecmp(mode, "BT") == 0) {
114171464Sambrisko		info->iface_type = BT_MODE;
115171464Sambrisko		info->address = 0xe4;
116171464Sambrisko		info->io_mode = 1;
117171464Sambrisko		info->offset = 1;
118171464Sambrisko	} else {
119171464Sambrisko		device_printf(dev, "Invalid mode %s\n", mode);
120171464Sambrisko		return (0);
121171464Sambrisko	}
122171464Sambrisko
123171464Sambrisko	/*
124171464Sambrisko	 * Kill any resources that isahint.c might have setup for us
125171464Sambrisko	 * since it will conflict with how we do resources.
126171464Sambrisko	 */
127171464Sambrisko	for (i = 0; i < 2; i++) {
128171464Sambrisko		bus_delete_resource(dev, SYS_RES_MEMORY, i);
129171464Sambrisko		bus_delete_resource(dev, SYS_RES_IOPORT, i);
130171464Sambrisko	}
131171464Sambrisko
132171464Sambrisko	/* Allow the I/O address to be overriden via hints. */
133171464Sambrisko	if (resource_int_value(name, unit, "port", &val) == 0 && val != 0) {
134171464Sambrisko		info->address = val;
135171464Sambrisko		info->io_mode = 1;
136171464Sambrisko	} else if (resource_int_value(name, unit, "maddr", &val) == 0 &&
137171464Sambrisko	    val != 0) {
138171464Sambrisko		info->address = val;
139171464Sambrisko		info->io_mode = 0;
140171464Sambrisko	}
141171464Sambrisko
142171464Sambrisko	/* Allow the spacing to be overriden. */
143171464Sambrisko	if (resource_int_value(name, unit, "spacing", &val) == 0) {
144171464Sambrisko		switch (val) {
145171464Sambrisko		case 8:
146171464Sambrisko			info->offset = 1;
147171464Sambrisko			break;
148171464Sambrisko		case 16:
149171464Sambrisko			info->offset = 2;
150171464Sambrisko			break;
151171464Sambrisko		case 32:
152171464Sambrisko			info->offset = 4;
153171464Sambrisko			break;
154171464Sambrisko		default:
155171464Sambrisko			device_printf(dev, "Invalid register spacing\n");
156171464Sambrisko			return (0);
157171464Sambrisko		}
158171464Sambrisko	}
159171464Sambrisko	return (1);
160171464Sambrisko}
161171464Sambrisko
162171464Sambriskostatic int
163162562Sjhbipmi_isa_attach(device_t dev)
164162562Sjhb{
165162562Sjhb	struct ipmi_softc *sc = device_get_softc(dev);
166162562Sjhb	struct ipmi_get_info info;
167162562Sjhb	const char *mode;
168162562Sjhb	int count, error, i, type;
169162562Sjhb
170171464Sambrisko	/*
171171464Sambrisko	 * Pull info out of the SMBIOS table.  If that doesn't work, use
172171464Sambrisko	 * hints to enumerate a device.
173171464Sambrisko	 */
174171464Sambrisko	if (!ipmi_smbios_identify(&info) &&
175171464Sambrisko	    !ipmi_hint_identify(dev, &info))
176162562Sjhb		return (ENXIO);
177162562Sjhb
178162562Sjhb	/*
179162562Sjhb	 * Give other drivers precedence.  Unfortunately, this doesn't
180162562Sjhb	 * work if we have an SMBIOS table that duplicates a PCI device
181162562Sjhb	 * that's later on the bus than the PCI-ISA bridge.
182162562Sjhb	 */
183162562Sjhb	if (ipmi_attached)
184162562Sjhb		return (EBUSY);
185162562Sjhb
186162562Sjhb	switch (info.iface_type) {
187162562Sjhb	case KCS_MODE:
188162562Sjhb		count = 2;
189162562Sjhb		mode = "KCS";
190162562Sjhb		break;
191162562Sjhb	case SMIC_MODE:
192162562Sjhb		count = 3;
193162562Sjhb		mode = "SMIC";
194162562Sjhb		break;
195162562Sjhb	case BT_MODE:
196162562Sjhb		device_printf(dev, "BT mode is unsupported\n");
197162562Sjhb		return (ENXIO);
198162562Sjhb	default:
199162562Sjhb		return (ENXIO);
200162562Sjhb	}
201162562Sjhb	error = 0;
202162562Sjhb	sc->ipmi_dev = dev;
203162562Sjhb
204162562Sjhb	device_printf(dev, "%s mode found at %s 0x%jx alignment 0x%x on %s\n",
205162562Sjhb	    mode, info.io_mode ? "io" : "mem",
206162562Sjhb	    (uintmax_t)info.address, info.offset,
207162562Sjhb	    device_get_name(device_get_parent(dev)));
208162562Sjhb	if (info.io_mode)
209162562Sjhb		type = SYS_RES_IOPORT;
210162562Sjhb	else
211162562Sjhb		type = SYS_RES_MEMORY;
212162562Sjhb
213162562Sjhb	sc->ipmi_io_type = type;
214162562Sjhb	sc->ipmi_io_spacing = info.offset;
215162562Sjhb	if (info.offset == 1) {
216162562Sjhb		sc->ipmi_io_rid = 0;
217162562Sjhb		sc->ipmi_io_res[0] = bus_alloc_resource(dev, type,
218162562Sjhb		    &sc->ipmi_io_rid, info.address, info.address + count - 1,
219162562Sjhb		    count, RF_ACTIVE);
220162562Sjhb		if (sc->ipmi_io_res[0] == NULL) {
221162562Sjhb			device_printf(dev, "couldn't configure I/O resource\n");
222162562Sjhb			return (ENXIO);
223162562Sjhb		}
224162562Sjhb	} else {
225162562Sjhb		for (i = 0; i < count; i++) {
226162562Sjhb			sc->ipmi_io_rid = i;
227162562Sjhb			sc->ipmi_io_res[i] = bus_alloc_resource(dev, type,
228162562Sjhb			    &sc->ipmi_io_rid, info.address + i * info.offset,
229162562Sjhb			    info.address + i * info.offset, 1, RF_ACTIVE);
230162562Sjhb			if (sc->ipmi_io_res[i] == NULL) {
231162562Sjhb				device_printf(dev,
232162562Sjhb				    "couldn't configure I/O resource\n");
233162562Sjhb				error = ENXIO;
234162562Sjhb				sc->ipmi_io_rid = 0;
235162562Sjhb				goto bad;
236162562Sjhb			}
237162562Sjhb		}
238162562Sjhb		sc->ipmi_io_rid = 0;
239162562Sjhb	}
240162562Sjhb
241162562Sjhb	if (info.irq != 0) {
242162562Sjhb		sc->ipmi_irq_rid = 0;
243162562Sjhb		sc->ipmi_irq_res = bus_alloc_resource(dev, SYS_RES_IRQ,
244162562Sjhb		    &sc->ipmi_irq_rid, info.irq, info.irq, 1,
245162562Sjhb		    RF_SHAREABLE | RF_ACTIVE);
246162562Sjhb	}
247162562Sjhb
248162562Sjhb	switch (info.iface_type) {
249162562Sjhb	case KCS_MODE:
250162562Sjhb		error = ipmi_kcs_attach(sc);
251162562Sjhb		if (error)
252162562Sjhb			goto bad;
253162562Sjhb		break;
254162562Sjhb	case SMIC_MODE:
255162562Sjhb		error = ipmi_smic_attach(sc);
256162562Sjhb		if (error)
257162562Sjhb			goto bad;
258162562Sjhb		break;
259162562Sjhb	}
260162562Sjhb
261162562Sjhb	error = ipmi_attach(dev);
262162562Sjhb	if (error)
263162562Sjhb		goto bad;
264162562Sjhb
265162562Sjhb	return (0);
266162562Sjhbbad:
267162562Sjhb	ipmi_release_resources(dev);
268162562Sjhb	return (error);
269162562Sjhb}
270162562Sjhb
271162562Sjhbstatic device_method_t ipmi_methods[] = {
272162562Sjhb	/* Device interface */
273162562Sjhb	DEVMETHOD(device_identify,      ipmi_isa_identify),
274162562Sjhb	DEVMETHOD(device_probe,		ipmi_isa_probe),
275162562Sjhb	DEVMETHOD(device_attach,	ipmi_isa_attach),
276162562Sjhb	DEVMETHOD(device_detach,	ipmi_detach),
277162562Sjhb	{ 0, 0 }
278162562Sjhb};
279162562Sjhb
280162562Sjhbstatic driver_t ipmi_isa_driver = {
281162562Sjhb	"ipmi",
282162562Sjhb	ipmi_methods,
283162562Sjhb	sizeof(struct ipmi_softc),
284162562Sjhb};
285162562Sjhb
286162562SjhbDRIVER_MODULE(ipmi_isa, isa, ipmi_isa_driver, ipmi_devclass, 0, 0);
287