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$");
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
59253811Ssbruno	if (ipmi_smbios_identify(&info) && info.iface_type != SSIF_MODE &&
60253811Ssbruno	    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
81253813Ssbruno	/*
82253813Ssbruno	 * Give other drivers precedence.  Unfortunately, this doesn't
83253813Ssbruno	 * work if we have an SMBIOS table that duplicates a PCI device
84253813Ssbruno	 * that's later on the bus than the PCI-ISA bridge.
85253813Ssbruno	 */
86253813Ssbruno	if (ipmi_attached)
87253813Ssbruno		return (ENXIO);
88253813Ssbruno
89162562Sjhb	/* Skip any PNP devices. */
90162562Sjhb	if (isa_get_logicalid(dev) != 0)
91162562Sjhb		return (ENXIO);
92162562Sjhb
93162562Sjhb	device_set_desc(dev, "IPMI System Interface");
94162562Sjhb	return (BUS_PROBE_DEFAULT);
95162562Sjhb}
96162562Sjhb
97162562Sjhbstatic int
98171464Sambriskoipmi_hint_identify(device_t dev, struct ipmi_get_info *info)
99171464Sambrisko{
100171464Sambrisko	const char *mode, *name;
101171464Sambrisko	int i, unit, val;
102171464Sambrisko
103171464Sambrisko	/* We require at least a "mode" hint. */
104171464Sambrisko	name = device_get_name(dev);
105171464Sambrisko	unit = device_get_unit(dev);
106171464Sambrisko	if (resource_string_value(name, unit, "mode", &mode) != 0)
107171464Sambrisko		return (0);
108171464Sambrisko
109171464Sambrisko	/* Set the mode and default I/O resources for each mode. */
110171464Sambrisko	bzero(info, sizeof(struct ipmi_get_info));
111171464Sambrisko	if (strcasecmp(mode, "KCS") == 0) {
112171464Sambrisko		info->iface_type = KCS_MODE;
113171464Sambrisko		info->address = 0xca2;
114171464Sambrisko		info->io_mode = 1;
115171464Sambrisko		info->offset = 1;
116171464Sambrisko	} else if (strcasecmp(mode, "SMIC") == 0) {
117171464Sambrisko		info->iface_type = SMIC_MODE;
118171464Sambrisko		info->address = 0xca9;
119171464Sambrisko		info->io_mode = 1;
120171464Sambrisko		info->offset = 1;
121171464Sambrisko	} else if (strcasecmp(mode, "BT") == 0) {
122171464Sambrisko		info->iface_type = BT_MODE;
123171464Sambrisko		info->address = 0xe4;
124171464Sambrisko		info->io_mode = 1;
125171464Sambrisko		info->offset = 1;
126171464Sambrisko	} else {
127171464Sambrisko		device_printf(dev, "Invalid mode %s\n", mode);
128171464Sambrisko		return (0);
129171464Sambrisko	}
130171464Sambrisko
131171464Sambrisko	/*
132171464Sambrisko	 * Kill any resources that isahint.c might have setup for us
133171464Sambrisko	 * since it will conflict with how we do resources.
134171464Sambrisko	 */
135171464Sambrisko	for (i = 0; i < 2; i++) {
136171464Sambrisko		bus_delete_resource(dev, SYS_RES_MEMORY, i);
137171464Sambrisko		bus_delete_resource(dev, SYS_RES_IOPORT, i);
138171464Sambrisko	}
139171464Sambrisko
140171464Sambrisko	/* Allow the I/O address to be overriden via hints. */
141171464Sambrisko	if (resource_int_value(name, unit, "port", &val) == 0 && val != 0) {
142171464Sambrisko		info->address = val;
143171464Sambrisko		info->io_mode = 1;
144171464Sambrisko	} else if (resource_int_value(name, unit, "maddr", &val) == 0 &&
145171464Sambrisko	    val != 0) {
146171464Sambrisko		info->address = val;
147171464Sambrisko		info->io_mode = 0;
148171464Sambrisko	}
149171464Sambrisko
150171464Sambrisko	/* Allow the spacing to be overriden. */
151171464Sambrisko	if (resource_int_value(name, unit, "spacing", &val) == 0) {
152171464Sambrisko		switch (val) {
153171464Sambrisko		case 8:
154171464Sambrisko			info->offset = 1;
155171464Sambrisko			break;
156171464Sambrisko		case 16:
157171464Sambrisko			info->offset = 2;
158171464Sambrisko			break;
159171464Sambrisko		case 32:
160171464Sambrisko			info->offset = 4;
161171464Sambrisko			break;
162171464Sambrisko		default:
163171464Sambrisko			device_printf(dev, "Invalid register spacing\n");
164171464Sambrisko			return (0);
165171464Sambrisko		}
166171464Sambrisko	}
167171464Sambrisko	return (1);
168171464Sambrisko}
169171464Sambrisko
170171464Sambriskostatic int
171162562Sjhbipmi_isa_attach(device_t dev)
172162562Sjhb{
173162562Sjhb	struct ipmi_softc *sc = device_get_softc(dev);
174162562Sjhb	struct ipmi_get_info info;
175162562Sjhb	const char *mode;
176162562Sjhb	int count, error, i, type;
177162562Sjhb
178171464Sambrisko	/*
179171464Sambrisko	 * Pull info out of the SMBIOS table.  If that doesn't work, use
180171464Sambrisko	 * hints to enumerate a device.
181171464Sambrisko	 */
182171464Sambrisko	if (!ipmi_smbios_identify(&info) &&
183171464Sambrisko	    !ipmi_hint_identify(dev, &info))
184162562Sjhb		return (ENXIO);
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