Update the ipmi(4) driver:

- Split out the communication protocols into their own files and use
  a couple of function pointers in the softc that the commuication
  protocols setup in their own attach routine.
- Add support for the SSIF interface (talking to IPMI over SMBus).
- Add an ACPI attachment.
- Add a PCI attachment that attaches to devices with the IPMI interface
  subclass.
- Split the ISA attachment out into its own file: ipmi_isa.c.
- Change the code to probe the SMBIOS table for an IPMI entry to just use
  pmap_mapbios() to map the table in rather than trying to setup a fake
  resource on an isa device and then activating the resource to map in the
  table.
- Make bus attachments leaner by adding attach functions for each
  communication interface (ipmi_kcs_attach(), ipmi_smic_attach(), etc.)
  that setup per-interface data.
- Formalize the model used by the driver to handle requests by adding an
  explicit struct ipmi_request object that holds the state of a given
  request and reply for the entire lifetime of the request.  By bundling
  the request into an object, it is easier to add retry logic to the various
  communication backends (as well as eventually support BT mode which uses
  a slightly different message format than KCS, SMIC, and SSIF).
- Add a per-softc lock and remove D_NEEDGIANT as the driver is now MPSAFE.
- Add 32-bit compatibility ioctl shims so you can use a 32-bit ipmitool
  on FreeBSD/amd64.
- Add ipmi(4) to i386 and amd64 NOTES.

Submitted by:	ambrisko (large portions of 2 and 3)
Sponsored by:	IronPort Systems, Yahoo!
MFC after:	6 days
This commit is contained in:
John Baldwin 2006-09-22 22:11:29 +00:00
parent 0a6f8a5050
commit d72a078647
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=162562
16 changed files with 3159 additions and 1424 deletions

View File

@ -346,6 +346,7 @@ options SAFE_RNDTEST # enable rndtest support
#
# Miscellaneous hardware:
#
# ipmi: Intelligent Platform Management Interface
# smbios: DMI/SMBIOS entry point
# vpd: Vital Product Data kernel interface
# cy: Cyclades serial driver
@ -368,6 +369,7 @@ device digi_EPCX_PCI
device digi_Xe
device digi_Xem
device digi_Xr
device ipmi
# Parallel (8255 PPI) basic I/O (mode 0) port (e.g. Advantech PCL-724)
device pbio
hint.pbio.0.at="isa"

View File

@ -161,7 +161,13 @@ dev/if_ndis/if_ndis_pci.c optional ndis cardbus | ndis pci
dev/if_ndis/if_ndis_usb.c optional ndis usb
dev/io/iodev.c optional io
dev/ipmi/ipmi.c optional ipmi
dev/ipmi/ipmi_smbios.c optional ipmi isa
dev/ipmi/ipmi_acpi.c optional ipmi acpi
dev/ipmi/ipmi_isa.c optional ipmi isa
dev/ipmi/ipmi_kcs.c optional ipmi
dev/ipmi/ipmi_smic.c optional ipmi
dev/ipmi/ipmi_smbus.c optional ipmi smbus
dev/ipmi/ipmi_smbios.c optional ipmi
dev/ipmi/ipmi_ssif.c optional ipmi smbus
dev/ipmi/ipmi_pci.c optional ipmi pci
dev/fdc/fdc.c optional fdc
dev/fdc/fdc_acpi.c optional fdc

View File

@ -196,7 +196,13 @@ dev/if_ndis/if_ndis_pci.c optional ndis cardbus | ndis pci
dev/if_ndis/if_ndis_usb.c optional ndis usb
dev/io/iodev.c optional io
dev/ipmi/ipmi.c optional ipmi
dev/ipmi/ipmi_smbios.c optional ipmi isa
dev/ipmi/ipmi_acpi.c optional ipmi acpi
dev/ipmi/ipmi_isa.c optional ipmi isa
dev/ipmi/ipmi_kcs.c optional ipmi
dev/ipmi/ipmi_smic.c optional ipmi
dev/ipmi/ipmi_smbus.c optional ipmi smbus
dev/ipmi/ipmi_smbios.c optional ipmi
dev/ipmi/ipmi_ssif.c optional ipmi smbus
dev/ipmi/ipmi_pci.c optional ipmi pci
dev/kbd/kbd.c optional atkbd | sc | ukbd | vt
dev/le/if_le_isa.c optional le isa

File diff suppressed because it is too large Load Diff

210
sys/dev/ipmi/ipmi_acpi.c Normal file
View File

@ -0,0 +1,210 @@
/*-
* Copyright (c) 2006 IronPort Systems Inc. <ambrisko@ironport.com>
* All rights reserved.
*
* 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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/condvar.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/rman.h>
#include <sys/selinfo.h>
#include <contrib/dev/acpica/acpi.h>
#include <dev/acpica/acpivar.h>
/* Hooks for the ACPI CA debugging infrastructure */
#define _COMPONENT ACPI_BUTTON
ACPI_MODULE_NAME("IPMI")
#ifdef LOCAL_MODULE
#include <ipmi.h>
#include <ipmivars.h>
#else
#include <sys/ipmi.h>
#include <dev/ipmi/ipmivars.h>
#endif
static int ipmi_acpi_probe(device_t);
static int ipmi_acpi_attach(device_t);
int
ipmi_acpi_probe(device_t dev)
{
static char *ipmi_ids[] = {"IPI0001", NULL};
if (ipmi_attached)
return (EBUSY);
if (acpi_disabled("ipmi") ||
ACPI_ID_PROBE(device_get_parent(dev), dev, ipmi_ids) == NULL)
return (ENXIO);
device_set_desc(dev, "IPMI System Interface");
return (0);
}
static int
ipmi_acpi_attach(device_t dev)
{
ACPI_HANDLE devh;
const char *mode;
struct ipmi_get_info info;
struct ipmi_softc *sc = device_get_softc(dev);
int count, error, flags, i, type;
int interface_type = 0, interface_version = 0;
error = 0;
devh = acpi_get_handle(dev);
if (ACPI_FAILURE(acpi_GetInteger(devh, "_IFT", &interface_type)))
return (ENXIO);
if (ACPI_FAILURE(acpi_GetInteger(devh, "_SRV", &interface_version)))
return (ENXIO);
switch (interface_type) {
case KCS_MODE:
count = 2;
mode = "KCS";
break;
case SMIC_MODE:
count = 3;
mode = "SMIC";
break;
case BT_MODE:
device_printf(dev, "BT interface not supported\n");
return (ENXIO);
case SSIF_MODE:
if (ACPI_FAILURE(acpi_GetInteger(devh, "_ADR", &flags)))
return (ENXIO);
info.address = flags >> 1;
device_printf(dev, "SSIF interface not supported on ACPI\n");
return (0);
default:
return (ENXIO);
}
if (bus_get_resource(dev, SYS_RES_IOPORT, 0, NULL, NULL) == 0)
type = SYS_RES_IOPORT;
else if (bus_get_resource(dev, SYS_RES_MEMORY, 0, NULL, NULL) == 0)
type = SYS_RES_MEMORY;
else {
device_printf(dev, "unknown resource type\n");
return (ENXIO);
}
sc->ipmi_io_rid = 0;
sc->ipmi_io_res[0] = bus_alloc_resource_any(dev, type,
&sc->ipmi_io_rid, RF_ACTIVE);
sc->ipmi_io_type = type;
sc->ipmi_io_spacing = 1;
if (sc->ipmi_io_res[0] == NULL) {
device_printf(dev, "couldn't configure I/O resource\n");
return (ENXIO);
}
/* If we have multiple resources, allocate up to MAX_RES. */
for (i = 1; i < MAX_RES; i++) {
sc->ipmi_io_rid = i;
sc->ipmi_io_res[i] = bus_alloc_resource_any(dev, type,
&sc->ipmi_io_rid, RF_ACTIVE);
if (sc->ipmi_io_res[i] == NULL)
break;
}
sc->ipmi_io_rid = 0;
/* If we have multiple resources, make sure we have enough of them. */
if (sc->ipmi_io_res[1] != NULL && sc->ipmi_io_res[count - 1] == NULL) {
device_printf(dev, "too few I/O resources\n");
error = ENXIO;
goto bad;
}
device_printf(dev, "%s mode found at %s 0x%jx on %s\n",
mode, type == SYS_RES_IOPORT ? "io" : "mem",
(uintmax_t)rman_get_start(sc->ipmi_io_res[0]),
device_get_name(device_get_parent(dev)));
sc->ipmi_dev = dev;
/*
* Setup an interrupt if we have an interrupt resource. We
* don't support GPE interrupts via _GPE yet.
*/
sc->ipmi_irq_rid = 0;
sc->ipmi_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ,
&sc->ipmi_irq_rid, RF_SHAREABLE | RF_ACTIVE);
/* Warn if _GPE exists. */
if (ACPI_SUCCESS(AcpiEvaluateObject(devh, "_GPE", NULL, NULL)))
device_printf(dev, "_GPE support not implemented\n");
/*
* We assume an alignment of 1 byte as currently the IPMI spec
* doesn't provide any way to determine the alignment via ACPI.
*/
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;
}
error = ipmi_attach(dev);
if (error)
goto bad;
return (0);
bad:
ipmi_release_resources(dev);
return (error);
}
static device_method_t ipmi_methods[] = {
/* Device interface */
DEVMETHOD(device_probe, ipmi_acpi_probe),
DEVMETHOD(device_attach, ipmi_acpi_attach),
DEVMETHOD(device_detach, ipmi_detach),
{ 0, 0 }
};
static driver_t ipmi_acpi_driver = {
"ipmi",
ipmi_methods,
sizeof(struct ipmi_softc),
};
DRIVER_MODULE(ipmi_acpi, acpi, ipmi_acpi_driver, ipmi_devclass, 0, 0);
MODULE_DEPEND(ipmi_acpi, acpi, 1, 1, 1);

209
sys/dev/ipmi/ipmi_isa.c Normal file
View File

@ -0,0 +1,209 @@
/*-
* Copyright (c) 2006 IronPort Systems Inc. <ambrisko@ironport.com>
* All rights reserved.
*
* 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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/condvar.h>
#include <sys/eventhandler.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/rman.h>
#include <sys/selinfo.h>
#include <machine/pci_cfgreg.h>
#include <dev/pci/pcireg.h>
#include <isa/isavar.h>
#ifdef LOCAL_MODULE
#include <ipmi.h>
#include <ipmivars.h>
#else
#include <sys/ipmi.h>
#include <dev/ipmi/ipmivars.h>
#endif
static void
ipmi_isa_identify(driver_t *driver, device_t parent)
{
struct ipmi_get_info info;
uint32_t devid;
if (ipmi_smbios_identify(&info) && info.iface_type != SSIF_MODE &&
device_find_child(parent, "ipmi", -1) == NULL) {
/*
* XXX: Hack alert. On some broken systems, the IPMI
* interface is described via SMBIOS, but the actual
* IO resource is in a PCI device BAR, so we have to let
* the PCI device attach ipmi instead. In that case don't
* create an isa ipmi device. For now we hardcode the list
* of bus, device, function tuples.
*/
devid = pci_cfgregread(0, 4, 2, PCIR_DEVVENDOR, 4);
if (devid != 0xffffffff &&
ipmi_pci_match(devid & 0xffff, devid >> 16) != NULL)
return;
BUS_ADD_CHILD(parent, 0, "ipmi", -1);
}
}
static int
ipmi_isa_probe(device_t dev)
{
/* Skip any PNP devices. */
if (isa_get_logicalid(dev) != 0)
return (ENXIO);
device_set_desc(dev, "IPMI System Interface");
return (BUS_PROBE_DEFAULT);
}
static int
ipmi_isa_attach(device_t dev)
{
struct ipmi_softc *sc = device_get_softc(dev);
struct ipmi_get_info info;
const char *mode;
int count, error, i, type;
/* This should never fail. */
if (!ipmi_smbios_identify(&info))
return (ENXIO);
/*
* Give other drivers precedence. Unfortunately, this doesn't
* work if we have an SMBIOS table that duplicates a PCI device
* that's later on the bus than the PCI-ISA bridge.
*/
if (ipmi_attached)
return (EBUSY);
switch (info.iface_type) {
case KCS_MODE:
count = 2;
mode = "KCS";
break;
case SMIC_MODE:
count = 3;
mode = "SMIC";
break;
case BT_MODE:
device_printf(dev, "BT mode is unsupported\n");
return (ENXIO);
default:
return (ENXIO);
}
error = 0;
sc->ipmi_dev = dev;
device_printf(dev, "%s mode found at %s 0x%jx alignment 0x%x on %s\n",
mode, info.io_mode ? "io" : "mem",
(uintmax_t)info.address, info.offset,
device_get_name(device_get_parent(dev)));
if (info.io_mode)
type = SYS_RES_IOPORT;
else
type = SYS_RES_MEMORY;
sc->ipmi_io_type = type;
sc->ipmi_io_spacing = info.offset;
if (info.offset == 1) {
sc->ipmi_io_rid = 0;
sc->ipmi_io_res[0] = bus_alloc_resource(dev, type,
&sc->ipmi_io_rid, info.address, info.address + count - 1,
count, RF_ACTIVE);
if (sc->ipmi_io_res[0] == NULL) {
device_printf(dev, "couldn't configure I/O resource\n");
return (ENXIO);
}
} else {
for (i = 0; i < count; i++) {
sc->ipmi_io_rid = i;
sc->ipmi_io_res[i] = bus_alloc_resource(dev, type,
&sc->ipmi_io_rid, info.address + i * info.offset,
info.address + i * info.offset, 1, RF_ACTIVE);
if (sc->ipmi_io_res[i] == NULL) {
device_printf(dev,
"couldn't configure I/O resource\n");
error = ENXIO;
sc->ipmi_io_rid = 0;
goto bad;
}
}
sc->ipmi_io_rid = 0;
}
if (info.irq != 0) {
sc->ipmi_irq_rid = 0;
sc->ipmi_irq_res = bus_alloc_resource(dev, SYS_RES_IRQ,
&sc->ipmi_irq_rid, info.irq, info.irq, 1,
RF_SHAREABLE | RF_ACTIVE);
}
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;
}
error = ipmi_attach(dev);
if (error)
goto bad;
return (0);
bad:
ipmi_release_resources(dev);
return (error);
}
static device_method_t ipmi_methods[] = {
/* Device interface */
DEVMETHOD(device_identify, ipmi_isa_identify),
DEVMETHOD(device_probe, ipmi_isa_probe),
DEVMETHOD(device_attach, ipmi_isa_attach),
DEVMETHOD(device_detach, ipmi_detach),
{ 0, 0 }
};
static driver_t ipmi_isa_driver = {
"ipmi",
ipmi_methods,
sizeof(struct ipmi_softc),
};
DRIVER_MODULE(ipmi_isa, isa, ipmi_isa_driver, ipmi_devclass, 0, 0);

607
sys/dev/ipmi/ipmi_kcs.c Normal file
View File

@ -0,0 +1,607 @@
/*-
* Copyright (c) 2006 IronPort Systems Inc. <ambrisko@ironport.com>
* All rights reserved.
*
* 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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/condvar.h>
#include <sys/eventhandler.h>
#include <sys/kernel.h>
#include <sys/kthread.h>
#include <sys/rman.h>
#include <sys/selinfo.h>
#include <machine/bus.h>
#ifdef LOCAL_MODULE
#include <ipmi.h>
#include <ipmivars.h>
#else
#include <sys/ipmi.h>
#include <dev/ipmi/ipmivars.h>
#endif
static void kcs_clear_obf(struct ipmi_softc *, int);
static void kcs_error(struct ipmi_softc *);
static int kcs_wait_for_ibf(struct ipmi_softc *, int);
static int kcs_wait_for_obf(struct ipmi_softc *, int);
static int
kcs_wait_for_ibf(struct ipmi_softc *sc, int state)
{
int status, start = ticks;
status = INB(sc, KCS_CTL_STS);
if (state == 0) {
/* WAIT FOR IBF = 0 */
while (ticks - start < MAX_TIMEOUT && status & KCS_STATUS_IBF) {
DELAY(100);
status = INB(sc, KCS_CTL_STS);
}
} else {
/* WAIT FOR IBF = 1 */
while (ticks - start < MAX_TIMEOUT &&
!(status & KCS_STATUS_IBF)) {
DELAY(100);
status = INB(sc, KCS_CTL_STS);
}
}
return (status);
}
static int
kcs_wait_for_obf(struct ipmi_softc *sc, int state)
{
int status, start = ticks;
status = INB(sc, KCS_CTL_STS);
if (state == 0) {
/* WAIT FOR OBF = 0 */
while (ticks - start < MAX_TIMEOUT && status & KCS_STATUS_OBF) {
DELAY(100);
status = INB(sc, KCS_CTL_STS);
}
} else {
/* WAIT FOR OBF = 1 */
while (ticks - start < MAX_TIMEOUT &&
!(status & KCS_STATUS_OBF)) {
DELAY(100);
status = INB(sc, KCS_CTL_STS);
}
}
return (status);
}
static void
kcs_clear_obf(struct ipmi_softc *sc, int status)
{
int data;
/* Clear OBF */
if (status & KCS_STATUS_OBF) {
data = INB(sc, KCS_DATA);
}
}
static void
kcs_error(struct ipmi_softc *sc)
{
int retry, status;
u_char data;
for (retry = 0; retry < 2; retry++) {
/* Wait for IBF = 0 */
status = kcs_wait_for_ibf(sc, 0);
/* ABORT */
OUTB(sc, KCS_CTL_STS, KCS_CONTROL_GET_STATUS_ABORT);
/* Wait for IBF = 0 */
status = kcs_wait_for_ibf(sc, 0);
/* Clear OBF */
kcs_clear_obf(sc, status);
if (status & KCS_STATUS_OBF) {
data = INB(sc, KCS_DATA);
if (data != 0)
device_printf(sc->ipmi_dev,
"KCS Error Data %02x\n", data);
}
/* 0x00 to DATA_IN */
OUTB(sc, KCS_DATA, 0x00);
/* Wait for IBF = 0 */
status = kcs_wait_for_ibf(sc, 0);
if (KCS_STATUS_STATE(status) == KCS_STATUS_STATE_READ) {
/* Wait for OBF = 1 */
status = kcs_wait_for_obf(sc, 1);
/* Read error status */
data = INB(sc, KCS_DATA);
if (data != 0)
device_printf(sc->ipmi_dev, "KCS error: %02x\n",
data);
/* Write READ into Data_in */
OUTB(sc, KCS_DATA, KCS_DATA_IN_READ);
/* Wait for IBF = 0 */
status = kcs_wait_for_ibf(sc, 0);
}
/* IDLE STATE */
if (KCS_STATUS_STATE(status) == KCS_STATUS_STATE_IDLE) {
/* Wait for OBF = 1 */
status = kcs_wait_for_obf(sc, 1);
/* Clear OBF */
kcs_clear_obf(sc, status);
return;
}
}
device_printf(sc->ipmi_dev, "KCS Error retry exhausted\n");
}
/*
* Start to write a request. Waits for IBF to clear and then sends the
* WR_START command.
*/
static int
kcs_start_write(struct ipmi_softc *sc)
{
int retry, status;
for (retry = 0; retry < 10; retry++) {
/* Wait for IBF = 0 */
status = kcs_wait_for_ibf(sc, 0);
/* Clear OBF */
kcs_clear_obf(sc, status);
/* Write start to command */
OUTB(sc, KCS_CTL_STS, KCS_CONTROL_WRITE_START);
/* Wait for IBF = 0 */
status = kcs_wait_for_ibf(sc, 0);
if (KCS_STATUS_STATE(status) == KCS_STATUS_STATE_WRITE)
break;
DELAY(1000000);
}
if (KCS_STATUS_STATE(status) != KCS_STATUS_STATE_WRITE)
/* error state */
return (0);
/* Clear OBF */
kcs_clear_obf(sc, status);
return (1);
}
/*
* Write a byte of the request message, excluding the last byte of the
* message which requires special handling.
*/
static int
kcs_write_byte(struct ipmi_softc *sc, u_char data)
{
int status;
/* Data to Data */
OUTB(sc, KCS_DATA, data);
/* Wait for IBF = 0 */
status = kcs_wait_for_ibf(sc, 0);
if (KCS_STATUS_STATE(status) != KCS_STATUS_STATE_WRITE)
return (0);
/* Clear OBF */
kcs_clear_obf(sc, status);
return (1);
}
/*
* Write the last byte of a request message.
*/
static int
kcs_write_last_byte(struct ipmi_softc *sc, u_char data)
{
int status;
/* Write end to command */
OUTB(sc, KCS_CTL_STS, KCS_CONTROL_WRITE_END);
/* Wait for IBF = 0 */
status = kcs_wait_for_ibf(sc, 0);
if (KCS_STATUS_STATE(status) != KCS_STATUS_STATE_WRITE)
/* error state */
return (0);
/* Clear OBF */
kcs_clear_obf(sc, status);
/* Send data byte to DATA. */
OUTB(sc, KCS_DATA, data);
return (1);
}
/*
* Read one byte of the reply message.
*/
static int
kcs_read_byte(struct ipmi_softc *sc, u_char *data)
{
int status;
u_char dummy;
/* Wait for IBF = 0 */
status = kcs_wait_for_ibf(sc, 0);
/* Read State */
if (KCS_STATUS_STATE(status) == KCS_STATUS_STATE_READ) {
/* Wait for OBF = 1 */
status = kcs_wait_for_obf(sc, 1);
/* Read Data_out */
*data = INB(sc, KCS_DATA);
/* Write READ into Data_in */
OUTB(sc, KCS_DATA, KCS_DATA_IN_READ);
return (1);
}
/* Idle State */
if (KCS_STATUS_STATE(status) == KCS_STATUS_STATE_IDLE) {
/* Wait for OBF = 1*/
status = kcs_wait_for_obf(sc, 1);
/* Read Dummy */
dummy = INB(sc, KCS_DATA);
return (2);
}
/* Error State */
return (0);
}
/*
* Send a request message and collect the reply. Returns true if we
* succeed.
*/
static int
kcs_polled_request(struct ipmi_softc *sc, struct ipmi_request *req)
{
u_char *cp, data;
int i, state;
/* Send the request. */
if (!kcs_start_write(sc)) {
device_printf(sc->ipmi_dev, "KCS: Failed to start write\n");
goto fail;
}
#ifdef KCS_DEBUG
device_printf(sc->ipmi_dev, "KCS: WRITE_START... ok\n");
#endif
if (!kcs_write_byte(sc, req->ir_addr)) {
device_printf(sc->ipmi_dev, "KCS: Failed to write address\n");
goto fail;
}
#ifdef KCS_DEBUG
device_printf(sc->ipmi_dev, "KCS: Wrote address: %02x\n", req->ir_addr);
#endif
if (req->ir_requestlen == 0) {
if (!kcs_write_last_byte(sc, req->ir_command)) {
device_printf(sc->ipmi_dev,
"KCS: Failed to write command\n");
goto fail;
}
#ifdef KCS_DEBUG
device_printf(sc->ipmi_dev, "KCS: Wrote command: %02x\n",
req->ir_command);
#endif
} else {
if (!kcs_write_byte(sc, req->ir_command)) {
device_printf(sc->ipmi_dev,
"KCS: Failed to write command\n");
goto fail;
}
#ifdef KCS_DEBUG
device_printf(sc->ipmi_dev, "KCS: Wrote command: %02x\n",
req->ir_command);
#endif
cp = req->ir_request;
for (i = 0; i < req->ir_requestlen - 1; i++) {
if (!kcs_write_byte(sc, *cp++)) {
device_printf(sc->ipmi_dev,
"KCS: Failed to write data byte %d\n",
i + 1);
goto fail;
}
#ifdef KCS_DEBUG
device_printf(sc->ipmi_dev, "KCS: Wrote data: %02x\n",
cp[-1]);
#endif
}
if (!kcs_write_last_byte(sc, *cp)) {
device_printf(sc->ipmi_dev,
"KCS: Failed to write last dta byte\n");
goto fail;
}
#ifdef KCS_DEBUG
device_printf(sc->ipmi_dev, "KCS: Wrote last data: %02x\n",
*cp);
#endif
}
/* Read the reply. First, read the NetFn/LUN. */
if (kcs_read_byte(sc, &data) != 1) {
device_printf(sc->ipmi_dev, "KCS: Failed to read address\n");
goto fail;
}
#ifdef KCS_DEBUG
device_printf(sc->ipmi_dev, "KCS: Read address: %02x\n", data);
#endif
if (data != IPMI_REPLY_ADDR(req->ir_addr)) {
device_printf(sc->ipmi_dev, "KCS: Reply address mismatch\n");
goto fail;
}
/* Next we read the command. */
if (kcs_read_byte(sc, &data) != 1) {
device_printf(sc->ipmi_dev, "KCS: Failed to read command\n");
goto fail;
}
#ifdef KCS_DEBUG
device_printf(sc->ipmi_dev, "KCS: Read command: %02x\n", data);
#endif
if (data != req->ir_command) {
device_printf(sc->ipmi_dev, "KCS: Command mismatch\n");
goto fail;
}
/* Next we read the completion code. */
if (kcs_read_byte(sc, &req->ir_compcode) != 1) {
device_printf(sc->ipmi_dev,
"KCS: Failed to read completion code\n");
goto fail;
}
#ifdef KCS_DEBUG
device_printf(sc->ipmi_dev, "KCS: Read completion code: %02x\n",
req->ir_compcode);
#endif
/* Finally, read the reply from the BMC. */
i = 0;
for (;;) {
state = kcs_read_byte(sc, &data);
if (state == 0) {
device_printf(sc->ipmi_dev,
"KCS: Read failed on byte %d\n", i + 1);
goto fail;
}
if (state == 2)
break;
if (i < req->ir_replybuflen) {
req->ir_reply[i] = data;
#ifdef KCS_DEBUG
device_printf(sc->ipmi_dev, "KCS: Read data %02x\n",
data);
} else {
device_printf(sc->ipmi_dev,
"KCS: Read short %02x byte %d\n", data, i + 1);
#endif
}
i++;
}
req->ir_replylen = i;
#ifdef KCS_DEBUG
device_printf(sc->ipmi_dev, "KCS: READ finished (%d bytes)\n", i);
if (req->ir_replybuflen < i)
#else
if (req->ir_replybuflen < i && req->ir_replybuflen != 0)
#endif
device_printf(sc->ipmi_dev,
"KCS: Read short: %zd buffer, %d actual\n",
req->ir_replybuflen, i);
return (1);
fail:
kcs_error(sc);
return (0);
}
static void
kcs_loop(void *arg)
{
struct ipmi_softc *sc = arg;
struct ipmi_request *req;
int i, ok;
IPMI_LOCK(sc);
while ((req = ipmi_dequeue_request(sc)) != NULL) {
ok = 0;
for (i = 0; i < 3 && !ok; i++)
ok = kcs_polled_request(sc, req);
if (ok)
req->ir_error = 0;
else
req->ir_error = EIO;
ipmi_complete_request(sc, req);
}
IPMI_UNLOCK(sc);
kthread_exit(0);
}
static int
kcs_startup(struct ipmi_softc *sc)
{
return (kthread_create(kcs_loop, sc, &sc->ipmi_kthread, 0, 0, "%s: kcs",
device_get_nameunit(sc->ipmi_dev)));
}
int
ipmi_kcs_attach(struct ipmi_softc *sc)
{
int status;
/* Setup function pointers. */
sc->ipmi_startup = kcs_startup;
sc->ipmi_enqueue_request = ipmi_polled_enqueue_request;
/* See if we can talk to the controller. */
status = INB(sc, KCS_CTL_STS);
if (status == 0xff) {
device_printf(sc->ipmi_dev, "couldn't find it\n");
return (ENXIO);
}
#ifdef KCS_DEBUG
device_printf(sc->ipmi_dev, "KCS: initial state: %02x\n", status);
#endif
if (status & KCS_STATUS_OBF ||
KCS_STATUS_STATE(status) != KCS_STATUS_STATE_IDLE)
kcs_error(sc);
return (0);
}
/*
* Determine the alignment automatically for a PCI attachment. In this case,
* any unused bytes will return 0x00 when read. We make use of the C/D bit
* in the CTL_STS register to try to start a GET_STATUS transaction. When
* we write the command, that bit should be set, so we should get a non-zero
* value back when we read CTL_STS if the offset we are testing is the CTL_STS
* register.
*/
int
ipmi_kcs_probe_align(struct ipmi_softc *sc)
{
int data, status;
sc->ipmi_io_spacing = 1;
retry:
#ifdef KCS_DEBUG
device_printf(sc->ipmi_dev, "Trying KCS align %d... ", sc->ipmi_io_spacing);
#endif
/* Wait for IBF = 0 */
status = INB(sc, KCS_CTL_STS);
while (status & KCS_STATUS_IBF) {
DELAY(100);
status = INB(sc, KCS_CTL_STS);
}
OUTB(sc, KCS_CTL_STS, KCS_CONTROL_GET_STATUS_ABORT);
/* Wait for IBF = 0 */
status = INB(sc, KCS_CTL_STS);
while (status & KCS_STATUS_IBF) {
DELAY(100);
status = INB(sc, KCS_CTL_STS);
}
/* If we got 0x00 back, then this must not be the CTL_STS register. */
if (status == 0) {
#ifdef KCS_DEBUG
printf("failed\n");
#endif
sc->ipmi_io_spacing <<= 1;
if (sc->ipmi_io_spacing > 4)
return (0);
goto retry;
}
#ifdef KCS_DEBUG
printf("ok\n");
#endif
/* Finish out the transaction. */
/* Clear OBF */
if (status && KCS_STATUS_OBF)
data = INB(sc, KCS_DATA);
/* 0x00 to DATA_IN */
OUTB(sc, KCS_DATA, 0);
/* Wait for IBF = 0 */
status = INB(sc, KCS_CTL_STS);
while (status & KCS_STATUS_IBF) {
DELAY(100);
status = INB(sc, KCS_CTL_STS);
}
if (KCS_STATUS_STATE(status) == KCS_STATUS_STATE_READ) {
/* Wait for IBF = 1 */
while (!(status & KCS_STATUS_OBF)) {
DELAY(100);
status = INB(sc, KCS_CTL_STS);
}
/* Read error status. */
data = INB(sc, KCS_DATA);
/* Write dummy READ to DATA_IN. */
OUTB(sc, KCS_DATA, KCS_DATA_IN_READ);
/* Wait for IBF = 0 */
status = INB(sc, KCS_CTL_STS);
while (status & KCS_STATUS_IBF) {
DELAY(100);
status = INB(sc, KCS_CTL_STS);
}
}
if (KCS_STATUS_STATE(status) == KCS_STATUS_STATE_IDLE) {
/* Wait for IBF = 1 */
while (!(status & KCS_STATUS_OBF)) {
DELAY(100);
status = INB(sc, KCS_CTL_STS);
}
/* Clear OBF */
if (status && KCS_STATUS_OBF)
data = INB(sc, KCS_DATA);
} else
device_printf(sc->ipmi_dev, "KCS probe: end state %x\n",
KCS_STATUS_STATE(status));
return (1);
}

View File

@ -28,18 +28,14 @@
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/malloc.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/condvar.h>
#include <sys/eventhandler.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/selinfo.h>
#include <sys/bus.h>
#include <sys/conf.h>
#include <machine/bus.h>
#include <machine/resource.h>
#include <sys/rman.h>
#include <sys/selinfo.h>
#include <dev/pci/pcireg.h>
#include <dev/pci/pcivar.h>
@ -52,17 +48,8 @@ __FBSDID("$FreeBSD$");
static int ipmi_pci_probe(device_t dev);
static int ipmi_pci_attach(device_t dev);
static int ipmi_pci_detach(device_t dev);
static device_method_t ipmi_methods[] = {
/* Device interface */
DEVMETHOD(device_probe, ipmi_pci_probe),
DEVMETHOD(device_attach, ipmi_pci_attach),
DEVMETHOD(device_detach, ipmi_pci_detach),
{ 0, 0 }
};
struct ipmi_ident
static struct ipmi_ident
{
u_int16_t vendor;
u_int16_t device;
@ -72,238 +59,237 @@ struct ipmi_ident
{0, 0, 0}
};
static int
ipmi_pci_probe(device_t dev) {
const char *
ipmi_pci_match(uint16_t vendor, uint16_t device)
{
struct ipmi_ident *m;
if (ipmi_attached)
return ENXIO;
for (m = ipmi_identifiers; m->vendor != 0; m++) {
if ((m->vendor == pci_get_vendor(dev)) &&
(m->device == pci_get_device(dev))) {
device_set_desc(dev, m->desc);
return (BUS_PROBE_DEFAULT);
}
}
return ENXIO;
for (m = ipmi_identifiers; m->vendor != 0; m++)
if (m->vendor == vendor && m->device == device)
return (m->desc);
return (NULL);
}
static int
ipmi_pci_attach(device_t dev) {
ipmi_pci_probe(device_t dev)
{
const char *desc;
if (ipmi_attached)
return (ENXIO);
desc = ipmi_pci_match(pci_get_vendor(dev), pci_get_device(dev));
if (desc != NULL) {
device_set_desc(dev, desc);
return (BUS_PROBE_DEFAULT);
}
return (ENXIO);
}
static int
ipmi_pci_attach(device_t dev)
{
struct ipmi_softc *sc = device_get_softc(dev);
device_t parent, smbios_attach_dev = NULL;
devclass_t dc;
int status, flags;
int error;
struct ipmi_get_info info;
const char *mode;
int error, type;
/* Look for an IPMI entry in the SMBIOS table. */
if (!ipmi_smbios_identify(&info))
return (ENXIO);
/*
* We need to attach to something that can address the BIOS/
* SMBIOS memory range. This is usually the isa bus however
* during a static kernel boot the isa bus is not available
* so we run up the tree to the nexus bus. A module load
* will use the isa bus attachment. If neither work bail
* since the SMBIOS defines stuff we need to know to attach to
* this device.
*/
dc = devclass_find("isa");
if (dc != NULL) {
smbios_attach_dev = devclass_get_device(dc, 0);
}
if (smbios_attach_dev == NULL) {
smbios_attach_dev = dev;
for (;;) {
parent = device_get_parent(smbios_attach_dev);
if (parent == NULL)
break;
if (strcmp(device_get_name(smbios_attach_dev),
"nexus") == 0)
break;
smbios_attach_dev = parent;
}
}
if (smbios_attach_dev == NULL) {
device_printf(dev, "Couldn't find isa/nexus device\n");
goto bad;
}
sc->ipmi_smbios_dev = ipmi_smbios_identify(NULL, smbios_attach_dev);
if (sc->ipmi_smbios_dev == NULL) {
device_printf(dev, "Couldn't find isa device\n");
goto bad;
}
error = ipmi_smbios_probe(sc->ipmi_smbios_dev);
if (error != 0) {
goto bad;
}
sc->ipmi_dev = dev;
error = ipmi_smbios_query(dev);
device_delete_child(dev, sc->ipmi_smbios_dev);
if (error != 0)
goto bad;
/* Now we know about the IPMI attachment info. */
if (sc->ipmi_bios_info.kcs_mode) {
if (sc->ipmi_bios_info.io_mode)
device_printf(dev, "KCS mode found at io 0x%llx "
"alignment 0x%x on %s\n",
(long long)sc->ipmi_bios_info.address,
sc->ipmi_bios_info.offset,
device_get_name(device_get_parent(sc->ipmi_dev)));
else
device_printf(dev, "KCS mode found at mem 0x%llx "
"alignment 0x%x on %s\n",
(long long)sc->ipmi_bios_info.address,
sc->ipmi_bios_info.offset,
device_get_name(device_get_parent(sc->ipmi_dev)));
sc->ipmi_kcs_status_reg = sc->ipmi_bios_info.offset;
sc->ipmi_kcs_command_reg = sc->ipmi_bios_info.offset;
sc->ipmi_kcs_data_out_reg = 0;
sc->ipmi_kcs_data_in_reg = 0;
if (sc->ipmi_bios_info.io_mode) {
sc->ipmi_io_rid = PCIR_BAR(0);
sc->ipmi_io_res = bus_alloc_resource(dev,
SYS_RES_IOPORT, &sc->ipmi_io_rid,
sc->ipmi_bios_info.address,
sc->ipmi_bios_info.address +
(sc->ipmi_bios_info.offset * 2),
sc->ipmi_bios_info.offset * 2,
RF_ACTIVE);
} else {
sc->ipmi_mem_rid = PCIR_BAR(0);
sc->ipmi_mem_res = bus_alloc_resource(dev,
SYS_RES_MEMORY, &sc->ipmi_mem_rid,
sc->ipmi_bios_info.address,
sc->ipmi_bios_info.address +
(sc->ipmi_bios_info.offset * 2),
sc->ipmi_bios_info.offset * 2,
RF_ACTIVE);
}
if (!sc->ipmi_io_res){
device_printf(dev, "couldn't configure pci io res\n");
goto bad;
}
status = INB(sc, sc->ipmi_kcs_status_reg);
if (status == 0xff) {
device_printf(dev, "couldn't find it\n");
goto bad;
}
if(status & KCS_STATUS_OBF){
ipmi_read(dev, NULL, 0);
}
} else if (sc->ipmi_bios_info.smic_mode) {
if (sc->ipmi_bios_info.io_mode)
device_printf(dev, "SMIC mode found at io 0x%llx "
"alignment 0x%x on %s\n",
(long long)sc->ipmi_bios_info.address,
sc->ipmi_bios_info.offset,
device_get_name(device_get_parent(sc->ipmi_dev)));
else
device_printf(dev, "SMIC mode found at mem 0x%llx "
"alignment 0x%x on %s\n",
(long long)sc->ipmi_bios_info.address,
sc->ipmi_bios_info.offset,
device_get_name(device_get_parent(sc->ipmi_dev)));
sc->ipmi_smic_data = 0;
sc->ipmi_smic_ctl_sts = sc->ipmi_bios_info.offset;
sc->ipmi_smic_flags = sc->ipmi_bios_info.offset * 2;
if (sc->ipmi_bios_info.io_mode) {
sc->ipmi_io_rid = PCIR_BAR(0);
sc->ipmi_io_res = bus_alloc_resource(dev,
SYS_RES_IOPORT, &sc->ipmi_io_rid,
sc->ipmi_bios_info.address,
sc->ipmi_bios_info.address +
(sc->ipmi_bios_info.offset * 3),
sc->ipmi_bios_info.offset * 3,
RF_ACTIVE);
} else {
sc->ipmi_mem_rid = PCIR_BAR(0);
sc->ipmi_mem_res = bus_alloc_resource(dev,
SYS_RES_MEMORY, &sc->ipmi_mem_rid,
sc->ipmi_bios_info.address,
sc->ipmi_bios_info.address +
(sc->ipmi_bios_info.offset * 2),
sc->ipmi_bios_info.offset * 2,
RF_ACTIVE);
}
if (!sc->ipmi_io_res && !sc->ipmi_mem_res){
device_printf(dev, "couldn't configure pci res\n");
goto bad;
}
flags = INB(sc, sc->ipmi_smic_flags);
if (flags == 0xff) {
device_printf(dev, "couldn't find it\n");
goto bad;
}
if ((flags & SMIC_STATUS_SMS_ATN)
&& (flags & SMIC_STATUS_RX_RDY)){
ipmi_read(dev, NULL, 0);
}
} else {
switch (info.iface_type) {
case KCS_MODE:
mode = "KCS";
break;
case SMIC_MODE:
mode = "SMIC";
break;
case BT_MODE:
device_printf(dev, "BT mode is unsupported\n");
return (ENXIO);
default:
device_printf(dev, "No IPMI interface found\n");
goto bad;
return (ENXIO);
}
device_printf(dev, "%s mode found at %s 0x%jx alignment 0x%x on %s\n",
mode, info.io_mode ? "io" : "mem",
(uintmax_t)info.address, info.offset,
device_get_name(device_get_parent(dev)));
if (info.io_mode)
type = SYS_RES_IOPORT;
else
type = SYS_RES_MEMORY;
sc->ipmi_io_rid = PCIR_BAR(0);
sc->ipmi_io_res[0] = bus_alloc_resource_any(dev, type,
&sc->ipmi_io_rid, RF_ACTIVE);
sc->ipmi_io_type = type;
sc->ipmi_io_spacing = info.offset;
if (sc->ipmi_io_res[0] == NULL) {
device_printf(dev, "couldn't configure pci io res\n");
return (ENXIO);
}
ipmi_attach(dev);
sc->ipmi_irq_rid = 0;
sc->ipmi_irq_res = bus_alloc_resource_any(sc->ipmi_dev, SYS_RES_IRQ,
sc->ipmi_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ,
&sc->ipmi_irq_rid, RF_SHAREABLE | RF_ACTIVE);
if (sc->ipmi_irq_res == NULL) {
device_printf(sc->ipmi_dev, "can't allocate interrupt\n");
} else {
if (bus_setup_intr(sc->ipmi_dev, sc->ipmi_irq_res,
INTR_TYPE_MISC, ipmi_intr,
sc->ipmi_dev, &sc->ipmi_irq)) {
device_printf(sc->ipmi_dev,
"can't set up interrupt\n");
return (EINVAL);
}
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;
}
error = ipmi_attach(dev);
if (error)
goto bad;
return 0;
return (0);
bad:
return ENXIO;
ipmi_release_resources(dev);
return (error);
}
static int ipmi_pci_detach(device_t dev) {
struct ipmi_softc *sc;
sc = device_get_softc(dev);
ipmi_detach(dev);
if (sc->ipmi_ev_tag)
EVENTHANDLER_DEREGISTER(watchdog_list, sc->ipmi_ev_tag);
if (sc->ipmi_mem_res)
bus_release_resource(dev, SYS_RES_MEMORY, sc->ipmi_mem_rid,
sc->ipmi_mem_res);
if (sc->ipmi_io_res)
bus_release_resource(dev, SYS_RES_IOPORT, sc->ipmi_io_rid,
sc->ipmi_io_res);
if (sc->ipmi_irq)
bus_teardown_intr(sc->ipmi_dev, sc->ipmi_irq_res,
sc->ipmi_irq);
if (sc->ipmi_irq_res)
bus_release_resource(sc->ipmi_dev, SYS_RES_IRQ,
sc->ipmi_irq_rid, sc->ipmi_irq_res);
return 0;
}
static driver_t ipmi_pci_driver = {
"ipmi",
ipmi_methods,
sizeof(struct ipmi_softc)
static device_method_t ipmi_methods[] = {
/* Device interface */
DEVMETHOD(device_probe, ipmi_pci_probe),
DEVMETHOD(device_attach, ipmi_pci_attach),
DEVMETHOD(device_detach, ipmi_detach),
{ 0, 0 }
};
DRIVER_MODULE(ipmi_foo, pci, ipmi_pci_driver, ipmi_devclass, 0, 0);
static driver_t ipmi_pci_driver = {
"ipmi",
ipmi_methods,
sizeof(struct ipmi_softc)
};
DRIVER_MODULE(ipmi_pci, pci, ipmi_pci_driver, ipmi_devclass, 0, 0);
/* Native IPMI on PCI driver. */
static int
ipmi2_pci_probe(device_t dev)
{
if (pci_get_class(dev) == PCIC_SERIALBUS &&
pci_get_subclass(dev) == 0x07) {
device_set_desc(dev, "IPMI System Interface");
return (BUS_PROBE_GENERIC);
}
return (ENXIO);
}
static int
ipmi2_pci_attach(device_t dev)
{
struct ipmi_softc *sc;
int error, iface, type;
sc = device_get_softc(dev);
sc->ipmi_dev = dev;
/* Interface is determined by progif. */
switch (pci_get_progif(dev)) {
case 0:
iface = SMIC_MODE;
break;
case 1:
iface = KCS_MODE;
break;
case 2:
iface = BT_MODE;
device_printf(dev, "BT interface unsupported\n");
return (ENXIO);
default:
device_printf(dev, "Unsupported interface: %d\n",
pci_get_progif(dev));
return (ENXIO);
}
/*
* Bottom bit of bar indicates resouce type. There should be
* constants in pcireg.h for fields in a BAR.
*/
sc->ipmi_io_rid = PCIR_BAR(0);
if (pci_read_config(dev, PCIR_BAR(0), 4) & 0x1)
type = SYS_RES_IOPORT;
else
type = SYS_RES_MEMORY;
sc->ipmi_io_type = type;
sc->ipmi_io_spacing = 1;
sc->ipmi_io_res[0] = bus_alloc_resource_any(dev, type,
&sc->ipmi_io_rid, RF_ACTIVE);
if (sc->ipmi_io_res[0] == NULL) {
device_printf(dev, "couldn't map ports/memory\n");
return (ENXIO);
}
sc->ipmi_irq_rid = 0;
sc->ipmi_irq_res = bus_alloc_resource_any(dev, SYS_RES_IRQ,
&sc->ipmi_irq_rid, RF_SHAREABLE | RF_ACTIVE);
switch (iface) {
case KCS_MODE:
device_printf(dev, "using KSC interface\n");
/*
* We have to examine the resource directly to determine the
* alignment.
*/
if (!ipmi_kcs_probe_align(sc)) {
device_printf(dev, "Unable to determine alignment\n");
error = ENXIO;
goto bad;
}
error = ipmi_kcs_attach(sc);
if (error)
goto bad;
break;
case SMIC_MODE:
device_printf(dev, "using SMIC interface\n");
error = ipmi_smic_attach(sc);
if (error)
goto bad;
break;
}
error = ipmi_attach(dev);
if (error)
goto bad;
return (0);
bad:
ipmi_release_resources(dev);
return (error);
}
static device_method_t ipmi2_methods[] = {
/* Device interface */
DEVMETHOD(device_probe, ipmi2_pci_probe),
DEVMETHOD(device_attach, ipmi2_pci_attach),
DEVMETHOD(device_detach, ipmi_detach),
{ 0, 0 }
};
static driver_t ipmi2_pci_driver = {
"ipmi",
ipmi2_methods,
sizeof(struct ipmi_softc)
};
DRIVER_MODULE(ipmi2_pci, pci, ipmi2_pci_driver, ipmi_devclass, 0, 0);

View File

@ -29,20 +29,13 @@ __FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/condvar.h>
#include <sys/eventhandler.h>
#include <sys/kernel.h>
#include <sys/poll.h>
#include <sys/selinfo.h>
#include <sys/module.h>
#include <sys/bus.h>
#include <machine/bus.h>
#include <machine/resource.h>
#include <sys/rman.h>
#include <sys/watchdog.h>
#include <vm/vm.h>
#include <vm/vm_param.h>
#include <vm/pmap.h>
#include <machine/pc/bios.h>
@ -77,7 +70,7 @@ struct structure_header {
uint16_t handle;
};
struct ipmi_device {
struct ipmi_entry {
uint8_t type;
uint8_t length;
uint16_t handle;
@ -90,50 +83,88 @@ struct ipmi_device {
uint8_t interrupt_number;
};
/* Fields in the base_address field of an IPMI entry. */
#define IPMI_BAR_MODE(ba) ((ba) & 0x0000000000000001)
#define IPMI_BAR_ADDR(ba) ((ba) & 0xfffffffffffffffe)
/* Fields in the base_address_modifier field of an IPMI entry. */
#define IPMI_BAM_IRQ_TRIGGER 0x01
#define IPMI_BAM_IRQ_POLARITY 0x02
#define IPMI_BAM_IRQ_VALID 0x08
#define IPMI_BAM_ADDR_LSB(bam) (((bam) & 0x10) >> 4)
#define IPMI_BAM_REG_SPACING(bam) (((bam) & 0xc0) >> 6)
#define SPACING_8 0x0
#define SPACING_32 0x1
#define SPACING_16 0x2
#define SMBIOS_START 0xf0000
#define SMBIOS_STEP 0x10
#define SMBIOS_OFF 0
#define SMBIOS_LEN 4
#define SMBIOS_SIG "_SM_"
devclass_t ipmi_devclass;
typedef void (*dispatchproc_t)(uint8_t *p, char **table,
struct ipmi_get_info *info);
static void smbios_run_table(uint8_t *, int, dispatchproc_t *,
struct ipmi_get_info *);
static char *get_strings(char *, char **);
static int smbios_cksum (struct smbios_table_entry *);
static void smbios_t38_proc_info(uint8_t *, char **, struct ipmi_get_info *);
static int ipmi_smbios_attach (device_t);
static int ipmi_smbios_modevent(module_t, int, void *);
static struct ipmi_get_info ipmi_info;
static int ipmi_probed;
static struct mtx ipmi_info_mtx;
MTX_SYSINIT(ipmi_info, &ipmi_info_mtx, "ipmi info", MTX_DEF);
static char *get_strings(char *, char **);
static void ipmi_smbios_probe(struct ipmi_get_info *);
static int smbios_cksum (struct smbios_table_entry *);
static void smbios_run_table(uint8_t *, int, dispatchproc_t *,
struct ipmi_get_info *);
static void smbios_t38_proc_info(uint8_t *, char **,
struct ipmi_get_info *);
static void
smbios_t38_proc_info(uint8_t *p, char **table, struct ipmi_get_info *info)
{
struct ipmi_device *s = (struct ipmi_device *) p;
struct ipmi_entry *s = (struct ipmi_entry *)p;
bzero(info, sizeof(struct ipmi_get_info));
if (s->interface_type == 0x01)
info->kcs_mode = 1;
if (s->interface_type == 0x02)
info->smic_mode = 1;
info->address = s->base_address & ~1;
switch (s->base_address_modifier >> 6) {
case 0x00:
info->offset = 1;
switch (s->interface_type) {
case KCS_MODE:
case SMIC_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);
switch (IPMI_BAM_REG_SPACING(s->base_address_modifier)) {
case SPACING_8:
info->offset = 1;
break;
case SPACING_32:
info->offset = 4;
break;
case SPACING_16:
info->offset = 2;
break;
default:
printf("SMBIOS: Invalid register spacing\n");
return;
}
break;
case 0x01:
info->offset = 4;
break;
case 0x10:
info->offset = 2;
break;
case 0x11:
info->offset = 0;
case SSIF_MODE:
if ((s->base_address & 0xffffffffffffff00) != 0) {
printf("SMBIOS: Invalid SSIF SMBus address, using BMC I2C slave address instead\n");
info->address = s->i2c_slave_address >> 1;
break;
}
info->address = IPMI_BAR_ADDR(s->base_address) >> 1;
break;
default:
return;
}
info->io_mode = s->base_address & 1;
if (s->length > offsetof(struct ipmi_entry, interrupt_number)) {
if (s->interrupt_number > 15)
printf("SMBIOS: Non-ISA IRQ %d for IPMI\n",
s->interrupt_number);
else
info->irq = s->interrupt_number;
}
info->iface_type = s->interface_type;
}
static char *
@ -147,7 +178,7 @@ get_strings(char *p, char **table)
*table = 0;
/* Skip past terminating null byte */
return p + 1;
return (p + 1);
}
@ -177,282 +208,88 @@ smbios_run_table(uint8_t *p, int entries, dispatchproc_t *dispatchstatus,
}
}
device_t
ipmi_smbios_identify (driver_t *driver, device_t parent)
/*
* Walk the SMBIOS table looking for an IPMI (type 38) entry. If we find
* one, return the parsed data in the passed in ipmi_get_info structure and
* return true. If we don't find one, return false.
*/
static void
ipmi_smbios_probe(struct ipmi_get_info *info)
{
device_t child = NULL;
dispatchproc_t dispatch_smbios_ipmi[256];
struct smbios_table_entry *header;
void *table;
u_int32_t addr;
int length;
int rid1, rid2;
bzero(info, sizeof(struct ipmi_get_info));
/* Find the SMBIOS table header. */
addr = bios_sigsearch(SMBIOS_START, SMBIOS_SIG, SMBIOS_LEN,
SMBIOS_STEP, SMBIOS_OFF);
if (addr != 0) {
rid1 = 0;
length = ((struct smbios_table_entry *)BIOS_PADDRTOVADDR(addr))
->length;
if (addr == 0)
return;
child = BUS_ADD_CHILD(parent, 0, "ipmi", -1);
if (driver != NULL)
device_set_driver(child, driver);
bus_set_resource(child, SYS_RES_MEMORY, rid1, addr, length);
rid2 = 1;
length = ((struct smbios_table_entry *)BIOS_PADDRTOVADDR(addr))
->structure_table_length;
addr = ((struct smbios_table_entry *)BIOS_PADDRTOVADDR(addr))
->structure_table_address;
bus_set_resource(child, SYS_RES_MEMORY, rid2, addr, length);
device_set_desc(child, "System Management BIOS");
} else {
device_printf(parent, "Failed to find SMBIOS signature\n");
/*
* Map the header. We first map a fixed size to get the actual
* length and then map it a second time with the actual length so
* we can verify the checksum.
*/
header = pmap_mapbios(addr, sizeof(struct smbios_table_entry));
table = pmap_mapbios(addr, header->length);
pmap_unmapbios((vm_offset_t)header, sizeof(struct smbios_table_entry));
header = table;
if (smbios_cksum(header) != 0) {
pmap_unmapbios((vm_offset_t)header, header->length);
return;
}
return child;
}
int
ipmi_smbios_probe(device_t dev)
{
struct resource *res1 = NULL, *res2 = NULL;
int rid1, rid2;
int error;
if (ipmi_attached)
return(EBUSY);
error = 0;
rid1 = 0;
rid2 = 1;
res1 = bus_alloc_resource(dev, SYS_RES_MEMORY, &rid1,
0ul, ~0ul, 1, RF_ACTIVE | RF_SHAREABLE);
if (res1 == NULL) {
device_printf(dev, "Unable to allocate memory resource.\n");
error = ENOMEM;
goto bad;
}
res2 = bus_alloc_resource(dev, SYS_RES_MEMORY, &rid2,
0ul, ~0ul, 1, RF_ACTIVE | RF_SHAREABLE);
if (res2 == NULL) {
device_printf(dev, "Unable to allocate memory resource.\n");
error = ENOMEM;
goto bad;
}
if (smbios_cksum(
(struct smbios_table_entry *)rman_get_virtual(res1))) {
device_printf(dev, "SMBIOS checksum failed.\n");
error = ENXIO;
goto bad;
}
bad:
if (res1)
bus_release_resource(dev, SYS_RES_MEMORY, rid1, res1);
if (res2)
bus_release_resource(dev, SYS_RES_MEMORY, rid2, res2);
return error;
}
int
ipmi_smbios_query(device_t dev)
{
struct ipmi_softc *sc = device_get_softc(dev);
dispatchproc_t dispatch_smbios_ipmi[256];
struct resource *res = NULL , *res2 = NULL;
int rid, rid2;
int error;
error = 0;
rid = 0;
res = bus_alloc_resource(sc->ipmi_smbios_dev, SYS_RES_MEMORY, &rid,
0ul, ~0ul, 1, RF_ACTIVE | RF_SHAREABLE );
if (res == NULL) {
device_printf(dev, "Unable to allocate memory resource.\n");
error = ENOMEM;
goto bad;
}
rid2 = 1;
res2 = bus_alloc_resource(sc->ipmi_smbios_dev, SYS_RES_MEMORY, &rid2,
0ul, ~0ul, 1, RF_ACTIVE | RF_SHAREABLE);
if (res2 == NULL) {
device_printf(dev, "Unable to allocate memory resource.\n");
error = ENOMEM;
goto bad;
}
sc->ipmi_smbios =
(struct smbios_table_entry *)rman_get_virtual(res);
sc->ipmi_busy = 0;
device_printf(sc->ipmi_smbios_dev, "SMBIOS Version: %d.%02d",
sc->ipmi_smbios->major_version,
sc->ipmi_smbios->minor_version);
if (bcd2bin(sc->ipmi_smbios->BCD_revision))
printf(", revision: %d.%02d",
bcd2bin(sc->ipmi_smbios->BCD_revision >> 4),
bcd2bin(sc->ipmi_smbios->BCD_revision & 0x0f));
printf("\n");
bzero(&sc->ipmi_bios_info, sizeof(sc->ipmi_bios_info));
/* Now map the actual table and walk it looking for an IPMI entry. */
table = pmap_mapbios(header->structure_table_address,
header->structure_table_length);
bzero((void *)dispatch_smbios_ipmi, sizeof(dispatch_smbios_ipmi));
dispatch_smbios_ipmi[38] = (void *)smbios_t38_proc_info;
smbios_run_table(
(void *)rman_get_virtual(res2),
sc->ipmi_smbios->number_structures,
dispatch_smbios_ipmi,
(void*)&sc->ipmi_bios_info);
smbios_run_table(table, header->number_structures, dispatch_smbios_ipmi,
info);
bad:
if (res)
bus_release_resource(sc->ipmi_smbios_dev, SYS_RES_MEMORY,
rid, res);
res = NULL;
if (res2)
bus_release_resource(sc->ipmi_smbios_dev, SYS_RES_MEMORY,
rid2, res2);
res2 = NULL;
return 0;
/* Unmap everything. */
pmap_unmapbios((vm_offset_t)table, header->structure_table_length);
pmap_unmapbios((vm_offset_t)header, header->length);
}
static int
ipmi_smbios_attach(device_t dev)
/*
* Return the SMBIOS IPMI table entry info to the caller. If we haven't
* searched the IPMI table yet, search it. Otherwise, return a cached
* copy of the data.
*/
int
ipmi_smbios_identify(struct ipmi_get_info *info)
{
struct ipmi_softc *sc = device_get_softc(dev);
int error;
int status, flags;
error = 0;
sc->ipmi_smbios_dev = dev;
sc->ipmi_dev = dev;
ipmi_smbios_query(dev);
if (sc->ipmi_bios_info.kcs_mode) {
if (sc->ipmi_bios_info.io_mode)
device_printf(dev, "KCS mode found at io 0x%llx "
"alignment 0x%x on %s\n",
(long long)sc->ipmi_bios_info.address,
sc->ipmi_bios_info.offset,
device_get_name(device_get_parent(sc->ipmi_dev)));
else
device_printf(dev, "KCS mode found at mem 0x%llx "
"alignment 0x%x on %s\n",
(long long)sc->ipmi_bios_info.address,
sc->ipmi_bios_info.offset,
device_get_name(device_get_parent(sc->ipmi_dev)));
sc->ipmi_kcs_status_reg = sc->ipmi_bios_info.offset;
sc->ipmi_kcs_command_reg = sc->ipmi_bios_info.offset;
sc->ipmi_kcs_data_out_reg = 0;
sc->ipmi_kcs_data_in_reg = 0;
if (sc->ipmi_bios_info.io_mode) {
sc->ipmi_io_rid = 2;
sc->ipmi_io_res = bus_alloc_resource(dev,
SYS_RES_IOPORT, &sc->ipmi_io_rid,
sc->ipmi_bios_info.address,
sc->ipmi_bios_info.address +
(sc->ipmi_bios_info.offset * 2),
sc->ipmi_bios_info.offset * 2,
RF_ACTIVE);
} else {
sc->ipmi_mem_rid = 2;
sc->ipmi_mem_res = bus_alloc_resource(dev,
SYS_RES_MEMORY, &sc->ipmi_mem_rid,
sc->ipmi_bios_info.address,
sc->ipmi_bios_info.address +
(sc->ipmi_bios_info.offset * 2),
sc->ipmi_bios_info.offset * 2,
RF_ACTIVE);
}
if (!sc->ipmi_io_res){
device_printf(dev,
"couldn't configure smbios io res\n");
goto bad;
}
status = INB(sc, sc->ipmi_kcs_status_reg);
if (status == 0xff) {
device_printf(dev, "couldn't find it\n");
error = ENXIO;
goto bad;
}
if(status & KCS_STATUS_OBF){
ipmi_read(dev, NULL, 0);
}
} else if (sc->ipmi_bios_info.smic_mode) {
if (sc->ipmi_bios_info.io_mode)
device_printf(dev, "SMIC mode found at io 0x%llx "
"alignment 0x%x on %s\n",
(long long)sc->ipmi_bios_info.address,
sc->ipmi_bios_info.offset,
device_get_name(device_get_parent(sc->ipmi_dev)));
else
device_printf(dev, "SMIC mode found at mem 0x%llx "
"alignment 0x%x on %s\n",
(long long)sc->ipmi_bios_info.address,
sc->ipmi_bios_info.offset,
device_get_name(device_get_parent(sc->ipmi_dev)));
sc->ipmi_smic_data = 0;
sc->ipmi_smic_ctl_sts = sc->ipmi_bios_info.offset;
sc->ipmi_smic_flags = sc->ipmi_bios_info.offset * 2;
if (sc->ipmi_bios_info.io_mode) {
sc->ipmi_io_rid = 2;
sc->ipmi_io_res = bus_alloc_resource(dev,
SYS_RES_IOPORT, &sc->ipmi_io_rid,
sc->ipmi_bios_info.address,
sc->ipmi_bios_info.address +
(sc->ipmi_bios_info.offset * 3),
sc->ipmi_bios_info.offset * 3,
RF_ACTIVE);
} else {
sc->ipmi_mem_res = bus_alloc_resource(dev,
SYS_RES_MEMORY, &sc->ipmi_mem_rid,
sc->ipmi_bios_info.address,
sc->ipmi_bios_info.address +
(sc->ipmi_bios_info.offset * 2),
sc->ipmi_bios_info.offset * 2,
RF_ACTIVE);
}
if (!sc->ipmi_io_res && !sc->ipmi_mem_res){
device_printf(dev, "couldn't configure smbios res\n");
error = ENXIO;
goto bad;
}
flags = INB(sc, sc->ipmi_smic_flags);
if (flags == 0xff) {
device_printf(dev, "couldn't find it\n");
error = ENXIO;
goto bad;
}
if ((flags & SMIC_STATUS_SMS_ATN)
&& (flags & SMIC_STATUS_RX_RDY)){
/* skip in smbio mode
ipmi_read(dev, NULL, 0);
*/
}
} else {
device_printf(dev, "No IPMI interface found\n");
error = ENXIO;
goto bad;
mtx_lock(&ipmi_info_mtx);
switch (ipmi_probed) {
case 0:
/* Need to probe the SMBIOS table. */
ipmi_probed++;
mtx_unlock(&ipmi_info_mtx);
ipmi_smbios_probe(&ipmi_info);
mtx_lock(&ipmi_info_mtx);
ipmi_probed++;
wakeup(&ipmi_info);
break;
case 1:
/* Another thread is currently probing the table, so wait. */
while (ipmi_probed == 1)
msleep(&ipmi_info, &ipmi_info_mtx, 0, "ipmi info", 0);
break;
default:
/* The cached data is available. */
break;
}
ipmi_attach(dev);
return 0;
bad:
/*
device_delete_child(device_get_parent(dev), dev);
*/
return error;
bcopy(&ipmi_info, info, sizeof(ipmi_info));
mtx_unlock(&ipmi_info_mtx);
return (info->iface_type != 0);
}
static int
@ -468,71 +305,5 @@ smbios_cksum (struct smbios_table_entry *e)
cksum += ptr[i];
}
return cksum;
return (cksum);
}
static int
ipmi_smbios_detach (device_t dev)
{
struct ipmi_softc *sc;
sc = device_get_softc(dev);
ipmi_detach(dev);
if (sc->ipmi_ev_tag)
EVENTHANDLER_DEREGISTER(watchdog_list, sc->ipmi_ev_tag);
if (sc->ipmi_mem_res)
bus_release_resource(dev, SYS_RES_MEMORY, sc->ipmi_mem_rid,
sc->ipmi_mem_res);
if (sc->ipmi_io_res)
bus_release_resource(dev, SYS_RES_IOPORT, sc->ipmi_io_rid,
sc->ipmi_io_res);
return 0;
}
static device_method_t ipmi_methods[] = {
/* Device interface */
DEVMETHOD(device_identify, ipmi_smbios_identify),
DEVMETHOD(device_probe, ipmi_smbios_probe),
DEVMETHOD(device_attach, ipmi_smbios_attach),
DEVMETHOD(device_detach, ipmi_smbios_detach),
{ 0, 0 }
};
static driver_t ipmi_smbios_driver = {
"ipmi",
ipmi_methods,
sizeof(struct ipmi_softc),
};
static int
ipmi_smbios_modevent (mod, what, arg)
module_t mod;
int what;
void * arg;
{
device_t * devs;
int count;
int i;
switch (what) {
case MOD_LOAD:
return 0;
case MOD_UNLOAD:
devclass_get_devices(ipmi_devclass, &devs, &count);
for (i = 0; i < count; i++) {
device_delete_child(device_get_parent(devs[i]),
devs[i]);
}
break;
default:
break;
}
return 0;
}
DRIVER_MODULE(ipmi, isa, ipmi_smbios_driver, ipmi_devclass,
ipmi_smbios_modevent, 0);

131
sys/dev/ipmi/ipmi_smbus.c Normal file
View File

@ -0,0 +1,131 @@
/*-
* Copyright (c) 2006 IronPort Systems Inc. <ambrisko@ironport.com>
* All rights reserved.
*
* 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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/condvar.h>
#include <sys/eventhandler.h>
#include <sys/kernel.h>
#include <sys/module.h>
#include <sys/rman.h>
#include <sys/selinfo.h>
#include <dev/smbus/smbconf.h>
#include <dev/smbus/smbus.h>
#include <dev/smbus/smb.h>
#include "smbus_if.h"
#ifdef LOCAL_MODULE
#include <ipmivars.h>
#else
#include <dev/ipmi/ipmivars.h>
#endif
static void ipmi_smbus_identify(driver_t *driver, device_t parent);
static int ipmi_smbus_probe(device_t dev);
static int ipmi_smbus_attach(device_t dev);
static void
ipmi_smbus_identify(driver_t *driver, device_t parent)
{
struct ipmi_get_info info;
if (ipmi_smbios_identify(&info) && info.iface_type == SSIF_MODE &&
device_find_child(parent, "ipmi", -1) == NULL)
BUS_ADD_CHILD(parent, 0, "ipmi", -1);
}
static int
ipmi_smbus_probe(device_t dev)
{
device_set_desc(dev, "IPMI System Interface");
return (BUS_PROBE_DEFAULT);
}
static int
ipmi_smbus_attach(device_t dev)
{
struct ipmi_softc *sc = device_get_softc(dev);
struct ipmi_get_info info;
int error;
/* This should never fail. */
if (!ipmi_smbios_identify(&info))
return (ENXIO);
if (info.iface_type != SSIF_MODE) {
device_printf(dev, "No SSIF IPMI interface found\n");
return (ENXIO);
}
sc->ipmi_dev = dev;
if (info.irq != 0) {
sc->ipmi_irq_rid = 0;
sc->ipmi_irq_res = bus_alloc_resource(dev, SYS_RES_IRQ,
&sc->ipmi_irq_rid, info.irq, info.irq, 1,
RF_SHAREABLE | RF_ACTIVE);
}
device_printf(dev, "SSIF mode found at address 0x%llx on %s\n",
(long long)info.address, device_get_name(device_get_parent(dev)));
error = ipmi_ssif_attach(sc, device_get_parent(dev), info.address);
if (error)
goto bad;
error = ipmi_attach(dev);
if (error)
goto bad;
return (0);
bad:
ipmi_release_resources(dev);
return (error);
}
static device_method_t ipmi_methods[] = {
/* Device interface */
DEVMETHOD(device_identify, ipmi_smbus_identify),
DEVMETHOD(device_probe, ipmi_smbus_probe),
DEVMETHOD(device_attach, ipmi_smbus_attach),
DEVMETHOD(device_detach, ipmi_detach),
{ 0, 0 }
};
static driver_t ipmi_smbus_driver = {
"ipmi",
ipmi_methods,
sizeof(struct ipmi_softc)
};
DRIVER_MODULE(ipmi_smbus, smbus, ipmi_smbus_driver, ipmi_devclass, 0, 0);
MODULE_DEPEND(ipmi_smbus, smbus, SMBUS_MINVER, SMBUS_PREFVER, SMBUS_MAXVER);

361
sys/dev/ipmi/ipmi_smic.c Normal file
View File

@ -0,0 +1,361 @@
/*-
* Copyright (c) 2006 IronPort Systems Inc. <ambrisko@ironport.com>
* All rights reserved.
*
* 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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/condvar.h>
#include <sys/eventhandler.h>
#include <sys/kernel.h>
#include <sys/kthread.h>
#include <sys/module.h>
#include <sys/rman.h>
#include <sys/selinfo.h>
#include <machine/bus.h>
#ifdef LOCAL_MODULE
#include <ipmi.h>
#include <ipmivars.h>
#else
#include <sys/ipmi.h>
#include <dev/ipmi/ipmivars.h>
#endif
static void smic_wait_for_tx_okay(struct ipmi_softc *);
static void smic_wait_for_rx_okay(struct ipmi_softc *);
static void smic_wait_for_not_busy(struct ipmi_softc *);
static void smic_set_busy(struct ipmi_softc *);
static void
smic_wait_for_tx_okay(struct ipmi_softc *sc)
{
int flags;
do {
flags = INB(sc, SMIC_FLAGS);
} while (!(flags & SMIC_STATUS_TX_RDY));
}
static void
smic_wait_for_rx_okay(struct ipmi_softc *sc)
{
int flags;
do {
flags = INB(sc, SMIC_FLAGS);
} while (!(flags & SMIC_STATUS_RX_RDY));
}
static void
smic_wait_for_not_busy(struct ipmi_softc *sc)
{
int flags;
do {
flags = INB(sc, SMIC_FLAGS);
} while (flags & SMIC_STATUS_BUSY);
}
static void
smic_set_busy(struct ipmi_softc *sc)
{
int flags;
flags = INB(sc, SMIC_FLAGS);
flags |= SMIC_STATUS_BUSY;
flags &= ~SMIC_STATUS_RESERVED;
OUTB(sc, SMIC_FLAGS, flags);
}
/*
* Start a transfer with a WR_START transaction that sends the NetFn/LUN
* address.
*/
static int
smic_start_write(struct ipmi_softc *sc, u_char data)
{
u_char error, status;
smic_wait_for_not_busy(sc);
OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_WR_START);
OUTB(sc, SMIC_DATA, data);
smic_set_busy(sc);
smic_wait_for_not_busy(sc);
status = INB(sc, SMIC_CTL_STS);
if (status != SMIC_SC_SMS_WR_START) {
error = INB(sc, SMIC_DATA);
device_printf(sc->ipmi_dev, "SMIC: Write did not start %02x\n",
error);
return (0);
}
return (1);
}
/*
* Write a byte in the middle of the message (either the command or one of
* the data bytes) using a WR_NEXT transaction.
*/
static int
smic_write_next(struct ipmi_softc *sc, u_char data)
{
u_char error, status;
OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_WR_NEXT);
smic_wait_for_tx_okay(sc);
OUTB(sc, SMIC_DATA, data);
smic_set_busy(sc);
smic_wait_for_not_busy(sc);
status = INB(sc, SMIC_CTL_STS);
if (status != SMIC_SC_SMS_WR_NEXT) {
error = INB(sc, SMIC_DATA);
device_printf(sc->ipmi_dev, "SMIC: Write did not next %02x\n",
error);
return (0);
}
return (1);
}
/*
* Write the last byte of a transfer to end the write phase via a WR_END
* transaction.
*/
static int
smic_write_last(struct ipmi_softc *sc, u_char data)
{
u_char error, status;
OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_WR_END);
smic_wait_for_tx_okay(sc);
OUTB(sc, SMIC_DATA, data);
smic_set_busy(sc);
smic_wait_for_not_busy(sc);
status = INB(sc, SMIC_CTL_STS);
if (status != SMIC_SC_SMS_WR_END) {
error = INB(sc, SMIC_DATA);
device_printf(sc->ipmi_dev, "SMIC: Write did not end %02x\n",
error);
return (0);
}
return (1);
}
/*
* Start the read phase of a transfer with a RD_START transaction.
*/
static int
smic_start_read(struct ipmi_softc *sc, u_char *data)
{
u_char error, status;
smic_wait_for_not_busy(sc);
OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_RD_START);
smic_wait_for_rx_okay(sc);
smic_set_busy(sc);
smic_wait_for_not_busy(sc);
status = INB(sc, SMIC_CTL_STS);
if (status != SMIC_SC_SMS_RD_START) {
error = INB(sc, SMIC_DATA);
device_printf(sc->ipmi_dev, "SMIC: Read did not start %02x\n",
error);
return (0);
}
*data = INB(sc, SMIC_DATA);
return (1);
}
/*
* Read a byte via a RD_NEXT transaction. If this was the last byte, return
* 2 rather than 1.
*/
static int
smic_read_byte(struct ipmi_softc *sc, u_char *data)
{
u_char error, status;
OUTB(sc, SMIC_CTL_STS, SMIC_SC_SMS_RD_NEXT);
smic_wait_for_rx_okay(sc);
smic_set_busy(sc);
smic_wait_for_not_busy(sc);
status = INB(sc, SMIC_CTL_STS);
if (status != SMIC_SC_SMS_RD_NEXT &&
status != SMIC_SC_SMS_RD_END) {
error = INB(sc, SMIC_DATA);
device_printf(sc->ipmi_dev, "SMIC: Read did not next %02x\n",
error);
return (0);
}
*data = INB(sc, SMIC_DATA);
if (status == SMIC_SC_SMS_RD_NEXT)
return (1);
else
return (2);
}
/* Complete a transfer via a RD_END transaction after reading the last byte. */
static int
smic_read_end(struct ipmi_softc *sc)
{
u_char error, status;
OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_RD_END);
smic_set_busy(sc);
smic_wait_for_not_busy(sc);
status = INB(sc, SMIC_CTL_STS);
if (status != SMIC_SC_SMS_RDY) {
error = INB(sc, SMIC_DATA);
device_printf(sc->ipmi_dev, "SMIC: Read did not end %02x\n",
error);
return (0);
}
return (1);
}
static int
smic_polled_request(struct ipmi_softc *sc, struct ipmi_request *req)
{
u_char *cp, data;
int i, state;
/* First, start the message with the address. */
if (!smic_start_write(sc, req->ir_addr))
return (0);
if (req->ir_requestlen == 0) {
/* Send the command as the last byte. */
if (!smic_write_last(sc, req->ir_command))
return (0);
} else {
/* Send the command. */
if (!smic_write_next(sc, req->ir_command))
return (0);
/* Send the payload. */
cp = req->ir_request;
for (i = 0; i < req->ir_requestlen - 1; i++)
if (!smic_write_next(sc, *cp++))
return (0);
if (!smic_write_last(sc, *cp))
return (0);
}
/* Start the read phase by reading the NetFn/LUN. */
if (smic_start_read(sc, &data) != 1)
return (0);
if (data != IPMI_REPLY_ADDR(req->ir_addr)) {
device_printf(sc->ipmi_dev, "SMIC: Reply address mismatch\n");
return (0);
}
/* Read the command. */
if (smic_read_byte(sc, &data) != 1)
return (0);
if (data != req->ir_command) {
device_printf(sc->ipmi_dev, "SMIC: Command mismatch\n");
return (0);
}
/* Read the completion code. */
state = smic_read_byte(sc, &req->ir_compcode);
if (state == 0)
return (0);
/* Finally, read the reply from the BMC. */
i = 0;
while (state == 1) {
state = smic_read_byte(sc, &data);
if (state == 0)
return (0);
if (state == 2)
break;
if (i < req->ir_replybuflen)
req->ir_reply[i] = data;
i++;
}
/* Terminate the transfer. */
if (!smic_read_end(sc))
return (0);
req->ir_replylen = i;
if (req->ir_replybuflen < i && req->ir_replybuflen != 0)
device_printf(sc->ipmi_dev,
"SMIC: Read short: %zd buffer, %d actual\n",
req->ir_replybuflen, i);
return (1);
}
static void
smic_loop(void *arg)
{
struct ipmi_softc *sc = arg;
struct ipmi_request *req;
int i, ok;
IPMI_LOCK(sc);
while ((req = ipmi_dequeue_request(sc)) != NULL) {
ok = 0;
for (i = 0; i < 3 && !ok; i++)
ok = smic_polled_request(sc, req);
if (ok)
req->ir_error = 0;
else
req->ir_error = EIO;
ipmi_complete_request(sc, req);
}
IPMI_UNLOCK(sc);
kthread_exit(0);
}
static int
smic_startup(struct ipmi_softc *sc)
{
return (kthread_create(smic_loop, sc, &sc->ipmi_kthread, 0, 0,
"%s: smic", device_get_nameunit(sc->ipmi_dev)));
}
int
ipmi_smic_attach(struct ipmi_softc *sc)
{
int flags;
/* Setup function pointers. */
sc->ipmi_startup = smic_startup;
sc->ipmi_enqueue_request = ipmi_polled_enqueue_request;
/* See if we can talk to the controller. */
flags = INB(sc, SMIC_FLAGS);
if (flags == 0xff) {
device_printf(sc->ipmi_dev, "couldn't find it\n");
return (ENXIO);
}
return (0);
}

375
sys/dev/ipmi/ipmi_ssif.c Normal file
View File

@ -0,0 +1,375 @@
/*-
* Copyright (c) 2006 IronPort Systems Inc. <ambrisko@ironport.com>
* All rights reserved.
*
* 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 <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/bus.h>
#include <sys/condvar.h>
#include <sys/eventhandler.h>
#include <sys/kernel.h>
#include <sys/kthread.h>
#include <sys/module.h>
#include <sys/selinfo.h>
#include <dev/smbus/smbconf.h>
#include <dev/smbus/smb.h>
#include "smbus_if.h"
#ifdef LOCAL_MODULE
#include <ipmivars.h>
#else
#include <dev/ipmi/ipmivars.h>
#endif
#define SMBUS_WRITE_SINGLE 0x02
#define SMBUS_WRITE_START 0x06
#define SMBUS_WRITE_CONT 0x07
#define SMBUS_READ_START 0x03
#define SMBUS_READ_CONT 0x09
#define SMBUS_DATA_SIZE 32
#ifdef SSIF_DEBUG
static void
dump_buffer(device_t dev, const char *msg, u_char *bytes, int len)
{
int i;
device_printf(dev, "%s:", msg);
for (i = 0; i < len; i++)
printf(" %02x", bytes[i]);
printf("\n");
}
#endif
static int
ssif_polled_request(struct ipmi_softc *sc, struct ipmi_request *req)
{
u_char ssif_buf[SMBUS_DATA_SIZE];
device_t dev = sc->ipmi_dev;
device_t smbus = sc->ipmi_ssif_smbus;
u_char *cp, block, count, offset;
size_t len;
int error;
/* Acquire the bus while we send the request. */
if (smbus_request_bus(smbus, dev, SMB_WAIT) != 0)
return (0);
/*
* First, send out the request. Begin by filling out the first
* packet which includes the NetFn/LUN and command.
*/
ssif_buf[0] = req->ir_addr;
ssif_buf[1] = req->ir_command;
if (req->ir_requestlen > 0)
bcopy(req->ir_request, &ssif_buf[2],
min(req->ir_requestlen, SMBUS_DATA_SIZE - 2));
/* Small requests are sent with a single command. */
if (req->ir_requestlen <= 30) {
#ifdef SSIF_DEBUG
dump_buffer(dev, "WRITE_SINGLE", ssif_buf,
req->ir_requestlen + 2);
#endif
error = smbus_error(smbus_bwrite(smbus,
sc->ipmi_ssif_smbus_address, SMBUS_WRITE_SINGLE,
req->ir_requestlen + 2, ssif_buf));
if (error) {
#ifdef SSIF_ERROR_DEBUG
device_printf(dev, "SSIF: WRITE_SINGLE error %d\n",
error);
#endif
goto fail;
}
} else {
/* Longer requests are sent out in 32-byte messages. */
#ifdef SSIF_DEBUG
dump_buffer(dev, "WRITE_START", ssif_buf, SMBUS_DATA_SIZE);
#endif
error = smbus_error(smbus_bwrite(smbus,
sc->ipmi_ssif_smbus_address, SMBUS_WRITE_START,
SMBUS_DATA_SIZE, ssif_buf));
if (error) {
#ifdef SSIF_ERROR_DEBUG
device_printf(dev, "SSIF: WRITE_START error %d\n",
error);
#endif
goto fail;
}
len = req->ir_requestlen - (SMBUS_DATA_SIZE - 2);
cp = req->ir_request + (SMBUS_DATA_SIZE - 2);
while (len > 0) {
#ifdef SSIF_DEBUG
dump_buffer(dev, "WRITE_CONT", cp,
min(len, SMBUS_DATA_SIZE));
#endif
error = smbus_error(smbus_bwrite(smbus,
sc->ipmi_ssif_smbus_address, SMBUS_WRITE_CONT,
min(len, SMBUS_DATA_SIZE), cp));
if (error) {
#ifdef SSIF_ERROR_DEBUG
device_printf(dev, "SSIF: WRITE_CONT error %d\n",
error);
#endif
goto fail;
}
cp += SMBUS_DATA_SIZE;
len -= SMBUS_DATA_SIZE;
}
/*
* The final WRITE_CONT transaction has to have a non-zero
* length that is also not SMBUS_DATA_SIZE. If our last
* WRITE_CONT transaction in the loop sent SMBUS_DATA_SIZE
* bytes, then len will be 0, and we send an extra 0x00 byte
* to terminate the transaction.
*/
if (len == 0) {
char c = 0;
#ifdef SSIF_DEBUG
dump_buffer(dev, "WRITE_CONT", &c, 1);
#endif
error = smbus_error(smbus_bwrite(smbus,
sc->ipmi_ssif_smbus_address, SMBUS_WRITE_CONT,
1, &c));
if (error) {
#ifdef SSIF_ERROR_DEBUG
device_printf(dev, "SSIF: WRITE_CONT error %d\n",
error);
#endif
goto fail;
}
}
}
/* Release the bus. */
smbus_release_bus(smbus, dev);
/* Give the BMC 100ms to chew on the request. */
tsleep(&error, 0, "ssifwt", hz / 10);
/* Try to read the first packet. */
read_start:
if (smbus_request_bus(smbus, dev, SMB_WAIT) != 0)
return (0);
count = SMBUS_DATA_SIZE;
error = smbus_error(smbus_bread(smbus,
sc->ipmi_ssif_smbus_address, SMBUS_READ_START, &count, ssif_buf));
if (error == ENXIO || error == EBUSY) {
smbus_release_bus(smbus, dev);
#ifdef SSIF_DEBUG
device_printf(dev, "SSIF: READ_START retry\n");
#endif
/* Give the BMC another 10ms. */
tsleep(&error, 0, "ssifwt", hz / 100);
goto read_start;
}
if (error) {
#ifdef SSIF_ERROR_DEBUG
device_printf(dev, "SSIF: READ_START failed: %d\n", error);
#endif
goto fail;
}
#ifdef SSIF_DEBUG
device_printf("SSIF: READ_START: ok\n");
#endif
/*
* If this is the first part of a multi-part read, then we need to
* skip the first two bytes.
*/
if (count == SMBUS_DATA_SIZE && ssif_buf[0] == 0 && ssif_buf[1] == 1)
offset = 2;
else
offset = 0;
/* We had better get the reply header. */
if (count < 3) {
device_printf(dev, "SSIF: Short reply packet\n");
goto fail;
}
/* Verify the NetFn/LUN. */
if (ssif_buf[offset] != IPMI_REPLY_ADDR(req->ir_addr)) {
device_printf(dev, "SSIF: Reply address mismatch\n");
goto fail;
}
/* Verify the command. */
if (ssif_buf[offset + 1] != req->ir_command) {
device_printf(dev, "SMIC: Command mismatch\n");
goto fail;
}
/* Read the completion code. */
req->ir_compcode = ssif_buf[offset + 2];
/* If this is a single read, just copy the data and return. */
if (offset == 0) {
#ifdef SSIF_DEBUG
dump_buffer(dev, "READ_SINGLE", ssif_buf, count);
#endif
len = count - 3;
bcopy(&ssif_buf[3], req->ir_reply,
min(req->ir_replybuflen, len));
goto done;
}
/*
* This is the first part of a multi-read transaction, so copy
* out the payload and start looping.
*/
#ifdef SSIF_DEBUG
dump_buffer(dev, "READ_START", ssif_buf + 2, count - 2);
#endif
bcopy(&ssif_buf[5], req->ir_reply, min(req->ir_replybuflen, count - 5));
len = count - 5;
block = 1;
for (;;) {
/* Read another packet via READ_CONT. */
count = SMBUS_DATA_SIZE;
error = smbus_error(smbus_bread(smbus,
sc->ipmi_ssif_smbus_address, SMBUS_READ_CONT, &count,
ssif_buf));
if (error) {
#ifdef SSIF_ERROR_DEBUG
printf("SSIF: READ_CONT failed: %d\n", error);
#endif
goto fail;
}
#ifdef SSIF_DEBUG
device_printf(dev, "SSIF: READ_CONT... ok\n");
#endif
/* Verify the block number. 0xff marks the last block. */
if (ssif_buf[0] != 0xff && ssif_buf[0] != block) {
device_printf(dev, "SSIF: Read wrong block %d %d\n",
ssif_buf[0], block);
goto fail;
}
if (ssif_buf[0] != 0xff && count < SMBUS_DATA_SIZE) {
device_printf(dev,
"SSIF: Read short middle block, length %d\n",
count);
goto fail;
}
#ifdef SSIF_DEBUG
if (ssif_buf[0] == 0xff)
dump_buffer(dev, "READ_END", ssif_buf + 1, count - 1);
else
dump_buffer(dev, "READ_CONT", ssif_buf + 1, count - 1);
#endif
if (len < req->ir_replybuflen)
bcopy(&ssif_buf[1], &req->ir_reply[len],
min(req->ir_replybuflen - len, count - 1));
len += count - 1;
/* If this was the last block we are done. */
if (ssif_buf[0] != 0xff)
break;
block++;
}
done:
/* Save the total length and return success. */
req->ir_replylen = len;
smbus_release_bus(smbus, dev);
return (1);
fail:
smbus_release_bus(smbus, dev);
return (0);
}
static void
ssif_loop(void *arg)
{
struct ipmi_softc *sc = arg;
struct ipmi_request *req;
int i, ok;
IPMI_LOCK(sc);
while ((req = ipmi_dequeue_request(sc)) != NULL) {
IPMI_UNLOCK(sc);
ok = 0;
for (i = 0; i < 5; i++) {
ok = ssif_polled_request(sc, req);
if (ok)
break;
/* Wait 60 ms between retries. */
tsleep(&ok, 0, "retry", 60 * hz / 1000);
#ifdef SSIF_RETRY_DEBUG
device_printf(sc->ipmi_dev,
"SSIF: Retrying request (%d)\n", i + 1);
#endif
}
if (ok)
req->ir_error = 0;
else
req->ir_error = EIO;
IPMI_LOCK(sc);
ipmi_complete_request(sc, req);
IPMI_UNLOCK(sc);
/* Enforce 10ms between requests. */
tsleep(&ok, 0, "delay", hz / 100);
IPMI_LOCK(sc);
}
IPMI_UNLOCK(sc);
kthread_exit(0);
}
static int
ssif_startup(struct ipmi_softc *sc)
{
return (kthread_create(ssif_loop, sc, &sc->ipmi_kthread, 0, 0,
"%s: ssif", device_get_nameunit(sc->ipmi_dev)));
}
int
ipmi_ssif_attach(struct ipmi_softc *sc, device_t smbus, int smbus_address)
{
/* Setup smbus address. */
sc->ipmi_ssif_smbus = smbus;
sc->ipmi_ssif_smbus_address = smbus_address;
/* Setup function pointers. */
sc->ipmi_startup = ssif_startup;
sc->ipmi_enqueue_request = ipmi_polled_enqueue_request;
return (0);
}

View File

@ -26,50 +26,116 @@
* $FreeBSD$
*/
#ifndef __IPMIVARS_H__
#define __IPMIVARS_H__
struct ipmi_get_info {
int kcs_mode;
int smic_mode;
int iface_type;
uint64_t address;
int offset;
int io_mode;
int irq;
};
struct ipmi_device;
struct ipmi_request {
TAILQ_ENTRY(ipmi_request) ir_link;
struct ipmi_device *ir_owner; /* Driver uses NULL. */
u_char *ir_request; /* Request is data to send to BMC. */
size_t ir_requestlen;
u_char *ir_reply; /* Reply is data read from BMC. */
size_t ir_replybuflen; /* Length of ir_reply[] buffer. */
int ir_replylen; /* Length of reply from BMC. */
int ir_error;
long ir_msgid;
uint8_t ir_addr;
uint8_t ir_command;
uint8_t ir_compcode;
};
#define MAX_RES 3
#define KCS_DATA 0
#define KCS_CTL_STS 1
#define SMIC_DATA 0
#define SMIC_CTL_STS 1
#define SMIC_FLAGS 2
struct ipmi_softc;
/* Per struct cdev data. */
struct ipmi_device {
TAILQ_ENTRY(ipmi_device) ipmi_link;
TAILQ_HEAD(,ipmi_request) ipmi_completed_requests;
struct selinfo ipmi_select;
struct ipmi_softc *ipmi_softc;
struct cdev *ipmi_cdev;
int ipmi_open;
int ipmi_closing;
int ipmi_requests;
u_char ipmi_address; /* IPMB address. */
u_char ipmi_lun;
};
struct ipmi_kcs {
};
struct ipmi_smic {
};
struct ipmi_ssif {
device_t smbus;
int smbus_address;
};
struct ipmi_softc {
device_t ipmi_dev;
device_t ipmi_smbios_dev;
struct cdev *ipmi_dev_t;
int ipmi_refcnt;
struct smbios_table_entry *ipmi_smbios;
struct ipmi_get_info ipmi_bios_info;
int ipmi_kcs_status_reg;
int ipmi_kcs_command_reg;
int ipmi_kcs_data_out_reg;
int ipmi_kcs_data_in_reg;
int ipmi_smic_data;
int ipmi_smic_ctl_sts;
int ipmi_smic_flags;
union {
struct ipmi_kcs kcs;
struct ipmi_smic smic;
struct ipmi_ssif ssif;
} _iface;
int ipmi_io_rid;
struct resource * ipmi_io_res;
int ipmi_mem_rid;
struct resource * ipmi_mem_res;
int ipmi_io_type;
struct resource *ipmi_io_res[MAX_RES];
int ipmi_io_spacing;
int ipmi_irq_rid;
struct resource * ipmi_irq_res;
struct resource *ipmi_irq_res;
void *ipmi_irq;
u_char ipmi_address;
u_char ipmi_lun;
int ipmi_busy;
struct selinfo ipmi_select;
int ipmi_timestamp;
int ipmi_requests;
struct callout_handle ipmi_timeout_handle;
TAILQ_HEAD(,ipmi_done_list) ipmi_done;
eventhandler_tag ipmi_ev_tag;
int ipmi_detaching;
#ifdef CLONING
int ipmi_cloning;
u_int ipmi_cdev_mask;
TAILQ_HEAD(,ipmi_device) ipmi_cdevs;
#else
struct ipmi_device ipmi_idev;
#endif
TAILQ_HEAD(,ipmi_request) ipmi_pending_requests;
#ifdef CLONING
eventhandler_tag ipmi_clone_tag;
#endif
eventhandler_tag ipmi_watchdog_tag;
struct intr_config_hook ipmi_ich;
struct mtx ipmi_lock;
struct cv ipmi_request_added;
struct proc *ipmi_kthread;
driver_intr_t *ipmi_intr;
int (*ipmi_startup)(struct ipmi_softc *);
int (*ipmi_enqueue_request)(struct ipmi_softc *, struct ipmi_request *);
};
#define ipmi_ssif_smbus_address _iface.ssif.smbus_address
#define ipmi_ssif_smbus _iface.ssif.smbus
struct ipmi_ipmb {
u_char foo;
};
#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 */
#define KCS_STATUS_IBF 0x02 /* Data In from System */
@ -84,11 +150,13 @@ struct ipmi_ipmb {
#define KCS_STATUS_STATE_READ 0x1
#define KCS_STATUS_STATE_WRITE 0x2
#define KCS_STATUS_STATE_ERROR 0x3
#define KCS_IFACE_STATUS_OK 0x00
#define KCS_IFACE_STATUS_ABORT 0x01
#define KCS_IFACE_STATUS_ILLEGAL 0x02
#define KCS_IFACE_STATUS_LENGTH_ERR 0x06
#define KCS_IFACE_STATUS_UNKNOWN_ERR 0xff
/* KCD control codes */
/* KCS control codes */
#define KCS_CONTROL_GET_STATUS_ABORT 0x60
#define KCS_CONTROL_WRITE_START 0x61
#define KCS_CONTROL_WRITE_END 0x62
@ -98,9 +166,10 @@ struct ipmi_ipmb {
#define SMIC_STATUS_BUSY 0x01 /* System set and BMC clears it */
#define SMIC_STATUS_SMS_ATN 0x04 /* BMC has a message */
#define SMIC_STATUS_EVT_ATN 0x08 /* Event has been RX */
#define SMIC_STATUS_SMI 0x08 /* asserted SMI */
#define SMIC_STATUS_SMI 0x10 /* asserted SMI */
#define SMIC_STATUS_TX_RDY 0x40 /* Ready to accept WRITE */
#define SMIC_STATUS_RX_RDY 0x80 /* Ready to read */
#define SMIC_STATUS_RESERVED 0x22
/* SMIC control codes */
#define SMIC_CC_SMS_GET_STATUS 0x40
@ -120,19 +189,78 @@ struct ipmi_ipmb {
#define SMIC_SC_SMS_RD_NEXT 0xc5
#define SMIC_SC_SMS_RD_END 0xc6
#define RES(x) (x)->ipmi_io_res ? (x)->ipmi_io_res : (x)->ipmi_mem_res
#define INB(sc, x) bus_space_read_1(rman_get_bustag(RES(sc)), \
rman_get_bushandle(RES(sc)), (x))
#define OUTB(sc, x, value) bus_space_write_1(rman_get_bustag(RES(sc)), \
rman_get_bushandle(RES(sc)), (x), value)
#define IPMI_ADDR(netfn, lun) ((netfn) << 2 | (lun))
#define IPMI_REPLY_ADDR(addr) ((addr) + 0x4)
int ipmi_attach(device_t);
int ipmi_detach(device_t);
int ipmi_smbios_query(device_t);
int ipmi_smbios_probe(device_t);
int ipmi_read(device_t, u_char *, int);
void ipmi_intr(void *);
#define IPMI_LOCK(sc) mtx_lock(&(sc)->ipmi_lock)
#define IPMI_UNLOCK(sc) mtx_unlock(&(sc)->ipmi_lock)
#define IPMI_LOCK_ASSERT(sc) mtx_assert(&(sc)->ipmi_lock, MA_OWNED)
#define ipmi_alloc_driver_request(addr, cmd, reqlen, replylen) \
ipmi_alloc_request(NULL, 0, (addr), (cmd), (reqlen), (replylen))
#if __FreeBSD_version < 601105
#define bus_read_1(r, o) \
bus_space_read_1(rman_get_bustag(r), rman_get_bushandle(r), (o))
#define bus_write_1(r, o, v) \
bus_space_write_1(rman_get_bustag(r), rman_get_bushandle(r), (o), (v))
#endif
/* I/O to a single I/O resource. */
#define INB_SINGLE(sc, x) \
bus_read_1((sc)->ipmi_io_res[0], (sc)->ipmi_io_spacing * (x))
#define OUTB_SINGLE(sc, x, value) \
bus_write_1((sc)->ipmi_io_res[0], (sc)->ipmi_io_spacing * (x), value)
/* I/O with each register in its in I/O resource. */
#define INB_MULTIPLE(sc, x) \
bus_read_1((sc)->ipmi_io_res[(x)], 0)
#define OUTB_MULTIPLE(sc, x, value) \
bus_write_1((sc)->ipmi_io_res[(x)], 0, value)
/*
* Determine I/O method based on whether or not we have more than one I/O
* resource.
*/
#define INB(sc, x) \
((sc)->ipmi_io_res[1] != NULL ? INB_MULTIPLE(sc, x) : INB_SINGLE(sc, x))
#define OUTB(sc, x, value) \
((sc)->ipmi_io_res[1] != NULL ? OUTB_MULTIPLE(sc, x, value) : \
OUTB_SINGLE(sc, x, value))
#define MAX_TIMEOUT 3 * hz
int ipmi_attach(device_t);
int ipmi_detach(device_t);
void ipmi_release_resources(device_t);
/* Manage requests. */
struct ipmi_request *ipmi_alloc_request(struct ipmi_device *, long, uint8_t,
uint8_t, size_t, size_t);
void ipmi_complete_request(struct ipmi_softc *, struct ipmi_request *);
struct ipmi_request *ipmi_dequeue_request(struct ipmi_softc *);
void ipmi_free_request(struct ipmi_request *);
int ipmi_polled_enqueue_request(struct ipmi_softc *, struct ipmi_request *);
int ipmi_submit_driver_request(struct ipmi_softc *, struct ipmi_request *,
int);
/* Identify BMC interface via SMBIOS. */
int ipmi_smbios_identify(struct ipmi_get_info *);
/* Match BMC PCI device listed in SMBIOS. */
const char *ipmi_pci_match(uint16_t, uint16_t);
/* Interface attach routines. */
int ipmi_kcs_attach(struct ipmi_softc *);
int ipmi_kcs_probe_align(struct ipmi_softc *);
int ipmi_smic_attach(struct ipmi_softc *);
int ipmi_ssif_attach(struct ipmi_softc *, device_t, int);
#ifdef IPMB
int ipmi_handle_attn(struct ipmi_softc *);
#endif
device_t ipmi_smbios_identify (driver_t *driver, device_t parent);
extern devclass_t ipmi_devclass;
extern int ipmi_attached;
#endif /* !__IPMIVARS_H__ */

View File

@ -698,6 +698,7 @@ options SAFE_RNDTEST # enable rndtest support
# Miscellaneous hardware:
#
# apm: Laptop Advanced Power Management (experimental)
# ipmi: Intelligent Platform Management Interface
# pmtimer: Timer device driver for power management events (APM or ACPI)
# smapi: System Management Application Program Interface driver
# smbios: DMI/SMBIOS entry point
@ -730,6 +731,7 @@ options SAFE_RNDTEST # enable rndtest support
device apm
hint.apm.0.flags="0x20"
device ipmi
device smapi
device smbios
device vpd

View File

@ -2,8 +2,12 @@
.PATH: ${.CURDIR}/../../dev/ipmi
# XXX - ipmi_smbus and ipmi_ssif depend on smbus
# XXX - ipmi_acpi depends on acpi
KMOD= ipmi
SRCS= ipmi.c ipmi_pci.c ipmi_smbios.c
SRCS+= bus_if.h device_if.h pci_if.h vnode_if.h
SRCS= ipmi.c ipmi_kcs.c ipmi_smic.c ipmi_smbios.c ipmi_ssif.c
SRCS+= ipmi_acpi.c ipmi_isa.c ipmi_pci.c ipmi_smbus.c
SRCS+= opt_acpi.h
SRCS+= acpi_if.h bus_if.h device_if.h isa_if.h pci_if.h smbus_if.h
.include <bsd.kmod.mk>

View File

@ -26,6 +26,9 @@
* $FreeBSD$
*/
#ifndef __SYS_IPMI_H__
#define __SYS_IPMI_H__
#define IPMI_MAX_ADDR_SIZE 0x20
#define IPMI_MAX_RX 1024
#define IPMI_BMC_SLAVE_ADDR 0x20 /* Linux Default slave address */
@ -117,3 +120,36 @@ struct ipmi_ipmb_addr {
unsigned char slave_addr;
unsigned char lun;
};
#if defined(__amd64__)
/* Compatiblity with 32-bit binaries. */
#define IPMICTL_RECEIVE_MSG_TRUNC_32 _IOWR(IPMI_IOC_MAGIC, 11, struct ipmi_recv32)
#define IPMICTL_RECEIVE_MSG_32 _IOWR(IPMI_IOC_MAGIC, 12, struct ipmi_recv32)
#define IPMICTL_SEND_COMMAND_32 _IOW(IPMI_IOC_MAGIC, 13, struct ipmi_req32)
struct ipmi_msg32 {
unsigned char netfn;
unsigned char cmd;
unsigned short data_len;
uint32_t data;
};
struct ipmi_req32 {
uint32_t addr;
unsigned int addr_len;
int32_t msgid;
struct ipmi_msg32 msg;
};
struct ipmi_recv32 {
int recv_type;
uint32_t addr;
unsigned int addr_len;
int32_t msgid;
struct ipmi_msg32 msg;
};
#endif
#endif /* !__SYS_IPMI_H__ */