From 53a374c18f69009d375e6a59247b55bfe03f59e2 Mon Sep 17 00:00:00 2001 From: Sascha Wildner Date: Sun, 14 Dec 2014 19:55:28 +0100 Subject: [PATCH] kernel: Import the IPMI driver from FreeBSD. Many thanks to Markus Pfeiffer for testing, bug hunting and fixing the watchdog (this will be pushed in a separate commit). --- share/man/man4/Makefile | 1 + share/man/man4/ipmi.4 | 203 ++++++ sys/conf/files | 10 + sys/config/LINT | 2 + sys/config/LINT64 | 2 + sys/dev/misc/Makefile | 3 +- sys/dev/misc/ipmi/Makefile | 16 + sys/dev/misc/ipmi/ipmi.c | 972 ++++++++++++++++++++++++++ sys/dev/misc/ipmi/ipmi_acpi.c | 210 ++++++ sys/dev/misc/ipmi/ipmi_isa.c | 285 ++++++++ sys/dev/misc/ipmi/ipmi_kcs.c | 607 ++++++++++++++++ sys/dev/misc/ipmi/ipmi_linux.c | 114 +++ sys/dev/misc/ipmi/ipmi_linux/Makefile | 8 + sys/dev/misc/ipmi/ipmi_pci.c | 291 ++++++++ sys/dev/misc/ipmi/ipmi_smbios.c | 268 +++++++ sys/dev/misc/ipmi/ipmi_smbus.c | 130 ++++ sys/dev/misc/ipmi/ipmi_smic.c | 407 +++++++++++ sys/dev/misc/ipmi/ipmi_ssif.c | 374 ++++++++++ sys/dev/misc/ipmi/ipmivars.h | 249 +++++++ sys/platform/pc32/include/pc/bios.h | 37 + sys/platform/pc64/include/pc/bios.h | 31 + sys/sys/ipmi.h | 157 +++++ 22 files changed, 4376 insertions(+), 1 deletion(-) create mode 100644 share/man/man4/ipmi.4 create mode 100644 sys/dev/misc/ipmi/Makefile create mode 100644 sys/dev/misc/ipmi/ipmi.c create mode 100644 sys/dev/misc/ipmi/ipmi_acpi.c create mode 100644 sys/dev/misc/ipmi/ipmi_isa.c create mode 100644 sys/dev/misc/ipmi/ipmi_kcs.c create mode 100644 sys/dev/misc/ipmi/ipmi_linux.c create mode 100644 sys/dev/misc/ipmi/ipmi_linux/Makefile create mode 100644 sys/dev/misc/ipmi/ipmi_pci.c create mode 100644 sys/dev/misc/ipmi/ipmi_smbios.c create mode 100644 sys/dev/misc/ipmi/ipmi_smbus.c create mode 100644 sys/dev/misc/ipmi/ipmi_smic.c create mode 100644 sys/dev/misc/ipmi/ipmi_ssif.c create mode 100644 sys/dev/misc/ipmi/ipmivars.h create mode 100644 sys/sys/ipmi.h diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile index f6607a4501..cfe399d411 100644 --- a/share/man/man4/Makefile +++ b/share/man/man4/Makefile @@ -133,6 +133,7 @@ MAN= aac.4 \ ip6.4 \ ipfirewall.4 \ ipheth.4 \ + ipmi.4 \ ips.4 \ ipsec.4 \ isa.4 \ diff --git a/share/man/man4/ipmi.4 b/share/man/man4/ipmi.4 new file mode 100644 index 0000000000..8e50d64a0f --- /dev/null +++ b/share/man/man4/ipmi.4 @@ -0,0 +1,203 @@ +.\" +.\" Copyright (c) 2006 Tom Rhodes +.\" 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. +.\" +.\" $FreeBSD: head/share/man/man4/ipmi.4 267938 2014-06-26 21:46:14Z bapt $ +.\" +.Dd December 14, 2014 +.Dt IPMI 4 +.Os +.Sh NAME +.Nm ipmi +.Nd "OpenIPMI compatible IPMI interface driver" +.Sh SYNOPSIS +.Cd "device ipmi" +.\".Pp +.\"To manually specify I/O attachment in +.\".Pa /boot/device.hints : +.\".Cd hint.ipmi.0.at="isa" +.\".Cd hint.ipmi.0.port="0xCA2" +.\".Cd hint.ipmi.0.spacing="8" +.\".Cd hint.ipmi.0.mode="KCS" +.\".Pp +.\"To manually specify memory attachment in +.\".Pa /boot/device.hints : +.\".Cd hint.ipmi.0.at="isa" +.\".Cd hint.ipmi.0.maddr="0xf0000000" +.\".Cd hint.ipmi.0.spacing="8" +.\".Cd hint.ipmi.0.mode="SMIC" +.\".Pp +.\"Meaning of +.\".Ar spacing : +.\".Bl -tag -offset indent -compact -width 0x0 +.\".It 8 +.\"8 bit alignment +.\".It 16 +.\"16 bit alignment +.\".It 32 +.\"32 bit alignment +.\".El +.\".Pp +.\"If the +.\".Ar port +.\"and +.\".Ar spacing +.\"are not specified the interface type default will be used. Only specify +.\"either the +.\".Ar port +.\"for I/O access or +.\".Ar maddr +.\"for memory access. +.Sh DESCRIPTION +The +.Tn IPMI +(Intelligent Platform Management Interface) is a standard for +monitoring system hardware by permitting generic code to detect +and monitor the sensors in a system. +The +.Tn IPMI +standard offers watchdog support, an FRU database, and other +support extensions. +It is currently being adopted by the makers of many +single board and embedded system manufacturers. +.Pp +The +.Nm +driver in +.Dx +is heavily adopted from the standard and +.Tn Linux +driver; however, not all features described in the +standard are supported. +.Sh IOCTLS +Sending and receiving messages through the +.Nm +driver requires the use of +.Xr ioctl 2 . +The ioctls are used due to the complexity of +data sent to and from the device. +The +.Xr ioctl 2 +command codes below are defined in +.In sys/ipmi.h . +The third argument to +.Xr ioctl 2 +should be a pointer to the type indicated. +.Pp +Currently the following ioctls are supported: +.Bl -tag -width indent +.It Dv IPMICTL_RECEIVE_MSG Pq Vt "struct ipmi_recv" +Receive a message. +Possible error values: +.Bl -tag -width Er +.It Bq Er EAGAIN +No messages are in the process queue. +.It Bq Er EFAULT +An address supplied was invalid. +.It Bq Er EMSGSIZE +The address could not fit in the message buffer and +will remain in the buffer. +.El +.It Dv IPMICTL_RECEIVE_MSG_TRUNC Pq Vt "struct ipmi_recv" +Like +.Dv IPMICTL_RECEIVE_MSG +but if the message cannot fit into the buffer, it +will truncate the contents instead of leaving the data +in the buffer. +.It Dv IPMICTL_SEND_COMMAND Pq Vt "struct ipmi_req" +Send a message to the interface. +Possible error values: +.Bl -tag -width Er +.It Bq Er EFAULT +An address supplied was invalid. +.It Bq Er ENOMEM +Buffers could not be allowed for the command, out of memory. +.El +.It Dv IPMICTL_SET_MY_ADDRESS_CMD Pq Vt "unsigned int" +Set the slave address for source messages. +.It Dv IPMICTL_GET_MY_ADDRESS_CMD Pq Vt "unsigned int" +Get the slave address for source messages. +.It Dv IPMICTL_SET_MY_LUN_CMD Pq Vt "unsigned int" +Set the slave LUN for source messages. +.It Dv IPMICTL_GET_MY_LUN_CMD Pq Vt "unsigned int" +Get the slave LUN for source messages. +.El +.Ss Unimplemented Ioctls +.Bl -tag -width indent +.It Dv IPMICTL_REGISTER_FOR_CMD Pq Vt "struct ipmi_cmdspec" +Register to receive a specific command. +Possible error values: +.Bl -tag -width Er +.It Bq Er EFAULT +An supplied address was invalid. +.It Bq Er EBUSY +The network function/command is already in use. +.It Bq Er ENOMEM +Could not allocate memory. +.El +.It Dv IPMICTL_UNREGISTER_FOR_CMD Pq Vt "struct ipmi_cmdspec" +Unregister to receive a specific command. +Possible error values: +.Bl -tag -width Er +.It Bq Er EFAULT +An address supplied was invalid. +.It Bq Er ENOENT +The network function/command was not found. +.El +.El +.Ss Stub Only Ioctl +.Bl -tag -width indent +.It Dv IPMICTL_SET_GETS_EVENTS_CMD Pq Vt int +Set whether this interface receives events. +Possible error values: +.Bl -tag -width Er +.It Bq Er EFAULT +An address supplied was invalid. +.El +.El +.Sh SEE ALSO +.Xr ioctl 2 , +.Xr watchdog 4 , +.\".Xr watchdog 8 , +.Xr watchdogd 8 , +.Xr wdog 9 +.Sh HISTORY +The +.Nm +driver first appeared in +.Fx 6.2 . +It was imported to +.Dx 4.1 . +.Sh AUTHORS +.An -nosplit +The +.Nm +driver was written by +.An Doug Ambrisko Aq Mt ambrisko@FreeBSD.org . +This manual page was written by +.An Tom Rhodes Aq Mt trhodes@FreeBSD.org . +.Sh BUGS +Not all features of the MontaVista driver are supported. +.Pp +Currently, IPMB and BT modes are not implemented. diff --git a/sys/conf/files b/sys/conf/files index d77e34e522..e788ef2d6c 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -665,6 +665,16 @@ rt2860.fw optional ralfw \ no-obj no-implicit-rule \ clean "rt2860.fw" dev/netif/lge/if_lge.c optional lge +dev/misc/ipmi/ipmi.c optional ipmi +dev/misc/ipmi/ipmi_acpi.c optional ipmi acpi +dev/misc/ipmi/ipmi_isa.c optional ipmi isa +dev/misc/ipmi/ipmi_kcs.c optional ipmi +dev/misc/ipmi/ipmi_smic.c optional ipmi +dev/misc/ipmi/ipmi_smbus.c optional ipmi smbus +dev/misc/ipmi/ipmi_smbios.c optional ipmi +dev/misc/ipmi/ipmi_ssif.c optional ipmi smbus +dev/misc/ipmi/ipmi_pci.c optional ipmi pci +#dev/misc/ipmi/ipmi_linux.c optional ipmi compat_linux dev/misc/led/led.c standard dev/disk/md/md.c optional md dev/raid/mfi/mfi.c optional mfi diff --git a/sys/config/LINT b/sys/config/LINT index 96b6563480..6a2d47c96c 100644 --- a/sys/config/LINT +++ b/sys/config/LINT @@ -1377,6 +1377,7 @@ device "snd_vibes" # cy: Cyclades serial driver # digi: DigiBoard intelligent serial cards # ecc: ECC memory controller +# ipmi: Intelligent Platform Management Interface # joy: joystick # nrp: Comtrol Rocketport # si: Specialix SI/XIO 4-32 port terminal multiplexor @@ -1413,6 +1414,7 @@ device stl # nullmodem terminal driver device nmdm device tpm +device ipmi # The `ahc' device provides support for the Adaptec 274X and 284X # adapters. diff --git a/sys/config/LINT64 b/sys/config/LINT64 index f3aa278b7f..9ddb32a044 100644 --- a/sys/config/LINT64 +++ b/sys/config/LINT64 @@ -1244,6 +1244,7 @@ device "snd_vibes" # # bktr: Brooktree bt848/848a/849a/878/879 video capture and TV Tuner board # ecc: ECC memory controller +# ipmi: Intelligent Platform Management Interface # joy: joystick # nrp: Comtrol Rocketport # si: Specialix SI/XIO 4-32 port terminal multiplexor @@ -1264,6 +1265,7 @@ device si # nullmodem terminal driver device nmdm device tpm +device ipmi # The `ahc' device provides support for the Adaptec 274X and 284X # adapters. diff --git a/sys/dev/misc/Makefile b/sys/dev/misc/Makefile index 81d564682a..66b5a6dc1b 100644 --- a/sys/dev/misc/Makefile +++ b/sys/dev/misc/Makefile @@ -1,3 +1,4 @@ -SUBDIR= amdsbwd cmx cpuctl dcons ecc ichwd joy kbdmux lpbb pcfclock nmdm putter syscons snp tbridge +SUBDIR= amdsbwd cmx cpuctl dcons ecc ichwd ipmi joy kbdmux lpbb \ + nmdm pcfclock putter snp syscons tbridge .include diff --git a/sys/dev/misc/ipmi/Makefile b/sys/dev/misc/ipmi/Makefile new file mode 100644 index 0000000000..4cc93a4f43 --- /dev/null +++ b/sys/dev/misc/ipmi/Makefile @@ -0,0 +1,16 @@ +# $FreeBSD: head/sys/modules/ipmi/Makefile 190445 2009-03-26 17:14:22Z ambrisko $ + +#SUBDIR+= ipmi_linux + +# XXX - ipmi_smbus and ipmi_ssif depend on smbus +# XXX - ipmi_acpi depends on acpi +KMOD= ipmi +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 + +CFLAGS+= -I${.CURDIR}/../../../contrib/dev/acpica/source/include +#CFLAGS+=-DIPMI_DEBUG -DKCS_DEBUG -DSMIC_DEBUG -DSSIF_DEBUG -DSSIF_ERROR_DEBUG -DSSIF_RETRY_DEBUG + +.include diff --git a/sys/dev/misc/ipmi/ipmi.c b/sys/dev/misc/ipmi/ipmi.c new file mode 100644 index 0000000000..cdd49afdde --- /dev/null +++ b/sys/dev/misc/ipmi/ipmi.c @@ -0,0 +1,972 @@ +/*- + * Copyright (c) 2006 IronPort Systems Inc. + * 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. + * + * $FreeBSD: head/sys/dev/ipmi/ipmi.c 257421 2013-10-31 05:13:53Z glebius $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef LOCAL_MODULE +#include +#include +#else +#include +#include +#endif + +#ifdef IPMB +static int ipmi_ipmb_checksum(u_char, int); +static int ipmi_ipmb_send_message(device_t, u_char, u_char, u_char, + u_char, u_char, int) +#endif + +static d_ioctl_t ipmi_ioctl; +static d_kqfilter_t ipmi_kqfilter; +static d_open_t ipmi_open; +static void ipmi_dtor(void *arg); + +static void ipmi_filter_detach(struct knote *); +static int ipmi_filter_read(struct knote *, long); +#if 0 /* XXX swildner: watchdog(9) (FreeBSD) -> wdog(9) (DragonFly) porting */ +static int ipmi_watchdog(void *, int); +#endif + +int ipmi_attached = 0; + +static int on = 1; +static SYSCTL_NODE(_hw, OID_AUTO, ipmi, CTLFLAG_RD, 0, + "IPMI driver parameters"); +SYSCTL_INT(_hw_ipmi, OID_AUTO, on, CTLFLAG_RW, + &on, 0, ""); + +static struct dev_ops ipmi_ops = { + { "ipmi", 0, D_MPSAFE }, + .d_open = ipmi_open, + .d_ioctl = ipmi_ioctl, + .d_kqfilter = ipmi_kqfilter, +}; + +#if 0 /* XXX swildner: watchdog(9) (FreeBSD) -> wdog(9) (DragonFly) porting */ +static struct watchdog ipmi_wdog = { + .name = "IPMI", + .wdog_fn = ipmi_watchdog, + .arg = NULL, + .period_max = (UINT16_MAX*1000) / 10, +}; +static struct ipmi_softc *ipmi_wdog_sc; +#endif + +static MALLOC_DEFINE(M_IPMI, "ipmi", "ipmi"); + +static int +ipmi_open(struct dev_open_args *ap) +{ + struct file *fp = ap->a_fp; + cdev_t cdev = ap->a_head.a_dev; + struct ipmi_device *dev; + struct ipmi_softc *sc; + int error; + + if (!on) + return (ENOENT); + + /* Initialize the per file descriptor data. */ + dev = kmalloc(sizeof(struct ipmi_device), M_IPMI, M_WAITOK | M_ZERO); + error = devfs_set_cdevpriv(fp, dev, ipmi_dtor); + if (error) { + kfree(dev, M_IPMI); + return (error); + } + + sc = cdev->si_drv1; + TAILQ_INIT(&dev->ipmi_completed_requests); + dev->ipmi_address = IPMI_BMC_SLAVE_ADDR; + dev->ipmi_lun = IPMI_BMC_SMS_LUN; + dev->ipmi_softc = sc; + IPMI_LOCK(sc); + sc->ipmi_opened++; + IPMI_UNLOCK(sc); + + return (0); +} + +static struct filterops ipmi_filterops = { + FILTEROP_ISFD | FILTEROP_MPSAFE, + NULL, + ipmi_filter_detach, + ipmi_filter_read +}; + +static int +ipmi_kqfilter(struct dev_kqfilter_args *ap) +{ + cdev_t cdev = ap->a_head.a_dev; + struct file *fp = ap->a_fp; + struct knote *kn = ap->a_kn; + struct ipmi_softc *sc = cdev->si_drv1; + struct ipmi_device *dev; + struct klist *klist; + + ap->a_result = 0; + + switch(kn->kn_filter) { + case EVFILT_READ: + if (devfs_get_cdevpriv(fp, (void **)&dev)) + return EOPNOTSUPP; + kn->kn_fop = &ipmi_filterops; + kn->kn_hook = (caddr_t)dev; + break; + default: + ap->a_result = EOPNOTSUPP; + return (0); + } + + klist = &sc->ipmi_kq.ki_note; + knote_insert(klist, kn); + + return (0); +} + +static void +ipmi_filter_detach(struct knote *kn) +{ + struct ipmi_device *dev = (struct ipmi_device *)kn->kn_hook; + struct ipmi_softc *sc = dev->ipmi_softc; + struct klist *klist; + + klist = &sc->ipmi_kq.ki_note; + knote_remove(klist, kn); +} + +static int +ipmi_filter_read(struct knote *kn, long hint) +{ + struct ipmi_device *dev = (struct ipmi_device *)kn->kn_hook; + struct ipmi_softc *sc = dev->ipmi_softc; + int ret = 0; + + IPMI_LOCK(sc); + if (!TAILQ_EMPTY(&dev->ipmi_completed_requests)) + ret = 1; + if (dev->ipmi_requests == 0) + kn->kn_flags |= EV_ERROR; + IPMI_UNLOCK(sc); + + return (ret); +} + +static void +ipmi_purge_completed_requests(struct ipmi_device *dev) +{ + struct ipmi_request *req; + + while (!TAILQ_EMPTY(&dev->ipmi_completed_requests)) { + req = TAILQ_FIRST(&dev->ipmi_completed_requests); + TAILQ_REMOVE(&dev->ipmi_completed_requests, req, ir_link); + dev->ipmi_requests--; + ipmi_free_request(req); + } +} + +static void +ipmi_dtor(void *arg) +{ + struct ipmi_request *req, *nreq; + struct ipmi_device *dev; + struct ipmi_softc *sc; + + dev = arg; + sc = dev->ipmi_softc; + + IPMI_LOCK(sc); + if (dev->ipmi_requests) { + /* Throw away any pending requests for this device. */ + TAILQ_FOREACH_MUTABLE(req, &sc->ipmi_pending_requests, ir_link, + nreq) { + if (req->ir_owner == dev) { + TAILQ_REMOVE(&sc->ipmi_pending_requests, req, + ir_link); + dev->ipmi_requests--; + ipmi_free_request(req); + } + } + + /* Throw away any pending completed requests for this device. */ + ipmi_purge_completed_requests(dev); + + /* + * If we still have outstanding requests, they must be stuck + * in an interface driver, so wait for those to drain. + */ + dev->ipmi_closing = 1; + while (dev->ipmi_requests > 0) { + lksleep(&dev->ipmi_requests, &sc->ipmi_lock, 0, + "ipmidrain", 0); + ipmi_purge_completed_requests(dev); + } + } + sc->ipmi_opened--; + IPMI_UNLOCK(sc); + + /* Cleanup. */ + kfree(dev, M_IPMI); +} + +#ifdef IPMB +static int +ipmi_ipmb_checksum(u_char *data, int len) +{ + u_char sum = 0; + + for (; len; len--) { + sum += *data++; + } + return (-sum); +} + +/* XXX: Needs work */ +static int +ipmi_ipmb_send_message(device_t dev, u_char channel, u_char netfn, + u_char command, u_char seq, u_char *data, int data_len) +{ + struct ipmi_softc *sc = device_get_softc(dev); + struct ipmi_request *req; + u_char slave_addr = 0x52; + int error; + + req = ipmi_alloc_driver_request(IPMI_ADDR(IPMI_APP_REQUEST, 0), + IPMI_SEND_MSG, data_len + 8, 0); + req->ir_request[0] = channel; + req->ir_request[1] = slave_addr; + req->ir_request[2] = IPMI_ADDR(netfn, 0); + req->ir_request[3] = ipmi_ipmb_checksum(&req->ir_request[1], 2); + req->ir_request[4] = sc->ipmi_address; + req->ir_request[5] = IPMI_ADDR(seq, sc->ipmi_lun); + req->ir_request[6] = command; + + bcopy(data, &req->ir_request[7], data_len); + temp[data_len + 7] = ipmi_ipmb_checksum(&req->ir_request[4], + data_len + 3); + + ipmi_submit_driver_request(sc, req); + error = req->ir_error; + ipmi_free_request(req); + + return (error); +} + +static int +ipmi_handle_attn(struct ipmi_softc *sc) +{ + struct ipmi_request *req; + int error; + + device_printf(sc->ipmi_dev, "BMC has a message\n"); + req = ipmi_alloc_driver_request(IPMI_ADDR(IPMI_APP_REQUEST, 0), + IPMI_GET_MSG_FLAGS, 0, 1); + + ipmi_submit_driver_request(sc, req); + + if (req->ir_error == 0 && req->ir_compcode == 0) { + if (req->ir_reply[0] & IPMI_MSG_BUFFER_FULL) { + device_printf(sc->ipmi_dev, "message buffer full"); + } + if (req->ir_reply[0] & IPMI_WDT_PRE_TIMEOUT) { + device_printf(sc->ipmi_dev, + "watchdog about to go off"); + } + if (req->ir_reply[0] & IPMI_MSG_AVAILABLE) { + ipmi_free_request(req); + + req = ipmi_alloc_driver_request( + IPMI_ADDR(IPMI_APP_REQUEST, 0), IPMI_GET_MSG, 0, + 16); + + device_printf(sc->ipmi_dev, "throw out message "); + dump_buf(temp, 16); + } + } + error = req->ir_error; + ipmi_free_request(req); + + return (error); +} +#endif + +#ifdef IPMICTL_SEND_COMMAND_32 +#define PTRIN(p) ((void *)(uintptr_t)(p)) +#define PTROUT(p) ((uintptr_t)(p)) +#endif + +static int +ipmi_ioctl(struct dev_ioctl_args *ap) +{ + struct file *fp = ap->a_fp; + cdev_t cdev = ap->a_head.a_dev; + u_long cmd = ap->a_cmd; + caddr_t data = ap->a_data; + struct ipmi_softc *sc; + struct ipmi_device *dev; + struct ipmi_request *kreq; + struct ipmi_req *req = (struct ipmi_req *)data; + struct ipmi_recv *recv = (struct ipmi_recv *)data; + struct ipmi_addr addr; +#ifdef IPMICTL_SEND_COMMAND_32 + struct ipmi_req32 *req32 = (struct ipmi_req32 *)data; + struct ipmi_recv32 *recv32 = (struct ipmi_recv32 *)data; + union { + struct ipmi_req req; + struct ipmi_recv recv; + } thunk32; +#endif + int error, len; + + error = devfs_get_cdevpriv(fp, (void **)&dev); + if (error) + return (error); + + sc = cdev->si_drv1; + +#ifdef IPMICTL_SEND_COMMAND_32 + /* Convert 32-bit structures to native. */ + switch (cmd) { + case IPMICTL_SEND_COMMAND_32: + req = &thunk32.req; + req->addr = PTRIN(req32->addr); + req->addr_len = req32->addr_len; + req->msgid = req32->msgid; + req->msg.netfn = req32->msg.netfn; + req->msg.cmd = req32->msg.cmd; + req->msg.data_len = req32->msg.data_len; + req->msg.data = PTRIN(req32->msg.data); + break; + case IPMICTL_RECEIVE_MSG_TRUNC_32: + case IPMICTL_RECEIVE_MSG_32: + recv = &thunk32.recv; + recv->addr = PTRIN(recv32->addr); + recv->addr_len = recv32->addr_len; + recv->msg.data_len = recv32->msg.data_len; + recv->msg.data = PTRIN(recv32->msg.data); + break; + } +#endif + + switch (cmd) { +#ifdef IPMICTL_SEND_COMMAND_32 + case IPMICTL_SEND_COMMAND_32: +#endif + case IPMICTL_SEND_COMMAND: + /* + * XXX: Need to add proper handling of this. + */ + error = copyin(req->addr, &addr, sizeof(addr)); + if (error) + return (error); + + IPMI_LOCK(sc); + /* clear out old stuff in queue of stuff done */ + /* XXX: This seems odd. */ + while ((kreq = TAILQ_FIRST(&dev->ipmi_completed_requests))) { + TAILQ_REMOVE(&dev->ipmi_completed_requests, kreq, + ir_link); + dev->ipmi_requests--; + ipmi_free_request(kreq); + } + IPMI_UNLOCK(sc); + + kreq = ipmi_alloc_request(dev, req->msgid, + IPMI_ADDR(req->msg.netfn, 0), req->msg.cmd, + req->msg.data_len, IPMI_MAX_RX); + error = copyin(req->msg.data, kreq->ir_request, + req->msg.data_len); + if (error) { + ipmi_free_request(kreq); + return (error); + } + IPMI_LOCK(sc); + dev->ipmi_requests++; + error = sc->ipmi_enqueue_request(sc, kreq); + IPMI_UNLOCK(sc); + if (error) + return (error); + break; +#ifdef IPMICTL_SEND_COMMAND_32 + case IPMICTL_RECEIVE_MSG_TRUNC_32: + case IPMICTL_RECEIVE_MSG_32: +#endif + case IPMICTL_RECEIVE_MSG_TRUNC: + case IPMICTL_RECEIVE_MSG: + error = copyin(recv->addr, &addr, sizeof(addr)); + if (error) + return (error); + + IPMI_LOCK(sc); + kreq = TAILQ_FIRST(&dev->ipmi_completed_requests); + if (kreq == NULL) { + IPMI_UNLOCK(sc); + return (EAGAIN); + } + addr.channel = IPMI_BMC_CHANNEL; + /* XXX */ + recv->recv_type = IPMI_RESPONSE_RECV_TYPE; + recv->msgid = kreq->ir_msgid; + recv->msg.netfn = IPMI_REPLY_ADDR(kreq->ir_addr) >> 2; + recv->msg.cmd = kreq->ir_command; + error = kreq->ir_error; + if (error) { + TAILQ_REMOVE(&dev->ipmi_completed_requests, kreq, + ir_link); + dev->ipmi_requests--; + IPMI_UNLOCK(sc); + ipmi_free_request(kreq); + return (error); + } + len = kreq->ir_replylen + 1; + if (recv->msg.data_len < len && + (cmd == IPMICTL_RECEIVE_MSG +#ifdef IPMICTL_RECEIVE_MSG_32 + || cmd == IPMICTL_RECEIVE_MSG_32 +#endif + )) { + IPMI_UNLOCK(sc); + return (EMSGSIZE); + } + TAILQ_REMOVE(&dev->ipmi_completed_requests, kreq, ir_link); + dev->ipmi_requests--; + IPMI_UNLOCK(sc); + len = min(recv->msg.data_len, len); + recv->msg.data_len = len; + error = copyout(&addr, recv->addr,sizeof(addr)); + if (error == 0) + error = copyout(&kreq->ir_compcode, recv->msg.data, 1); + if (error == 0) + error = copyout(kreq->ir_reply, recv->msg.data + 1, + len - 1); + ipmi_free_request(kreq); + if (error) + return (error); + break; + case IPMICTL_SET_MY_ADDRESS_CMD: + IPMI_LOCK(sc); + dev->ipmi_address = *(int*)data; + IPMI_UNLOCK(sc); + break; + case IPMICTL_GET_MY_ADDRESS_CMD: + IPMI_LOCK(sc); + *(int*)data = dev->ipmi_address; + IPMI_UNLOCK(sc); + break; + case IPMICTL_SET_MY_LUN_CMD: + IPMI_LOCK(sc); + dev->ipmi_lun = *(int*)data & 0x3; + IPMI_UNLOCK(sc); + break; + case IPMICTL_GET_MY_LUN_CMD: + IPMI_LOCK(sc); + *(int*)data = dev->ipmi_lun; + IPMI_UNLOCK(sc); + break; + case IPMICTL_SET_GETS_EVENTS_CMD: + /* + device_printf(sc->ipmi_dev, + "IPMICTL_SET_GETS_EVENTS_CMD NA\n"); + */ + break; + case IPMICTL_REGISTER_FOR_CMD: + case IPMICTL_UNREGISTER_FOR_CMD: + return (EOPNOTSUPP); + default: + device_printf(sc->ipmi_dev, "Unknown IOCTL %lX\n", cmd); + return (ENOIOCTL); + } + +#ifdef IPMICTL_SEND_COMMAND_32 + /* Update changed fields in 32-bit structures. */ + switch (cmd) { + case IPMICTL_RECEIVE_MSG_TRUNC_32: + case IPMICTL_RECEIVE_MSG_32: + recv32->recv_type = recv->recv_type; + recv32->msgid = recv->msgid; + recv32->msg.netfn = recv->msg.netfn; + recv32->msg.cmd = recv->msg.cmd; + recv32->msg.data_len = recv->msg.data_len; + break; + } +#endif + return (0); +} + +/* + * Request management. + */ + +/* Allocate a new request with request and reply buffers. */ +struct ipmi_request * +ipmi_alloc_request(struct ipmi_device *dev, long msgid, uint8_t addr, + uint8_t command, size_t requestlen, size_t replylen) +{ + struct ipmi_request *req; + + req = kmalloc(sizeof(struct ipmi_request) + requestlen + replylen, + M_IPMI, M_WAITOK | M_ZERO); + req->ir_owner = dev; + req->ir_msgid = msgid; + req->ir_addr = addr; + req->ir_command = command; + if (requestlen) { + req->ir_request = (char *)&req[1]; + req->ir_requestlen = requestlen; + } + if (replylen) { + req->ir_reply = (char *)&req[1] + requestlen; + req->ir_replybuflen = replylen; + } + return (req); +} + +/* Free a request no longer in use. */ +void +ipmi_free_request(struct ipmi_request *req) +{ + + kfree(req, M_IPMI); +} + +/* Store a processed request on the appropriate completion queue. */ +void +ipmi_complete_request(struct ipmi_softc *sc, struct ipmi_request *req) +{ + struct ipmi_device *dev; + + IPMI_LOCK_ASSERT(sc); + + /* + * Anonymous requests (from inside the driver) always have a + * waiter that we awaken. + */ + if (req->ir_owner == NULL) + wakeup(req); + else { + dev = req->ir_owner; + TAILQ_INSERT_TAIL(&dev->ipmi_completed_requests, req, ir_link); + KNOTE(&sc->ipmi_kq.ki_note, 0); + if (dev->ipmi_closing) + wakeup(&dev->ipmi_requests); + } +} + +/* Enqueue an internal driver request and wait until it is completed. */ +int +ipmi_submit_driver_request(struct ipmi_softc *sc, struct ipmi_request *req, + int timo) +{ + int error; + + IPMI_LOCK(sc); + error = sc->ipmi_enqueue_request(sc, req); + if (error == 0) + error = lksleep(req, &sc->ipmi_lock, 0, "ipmireq", timo); + if (error == 0) + error = req->ir_error; + IPMI_UNLOCK(sc); + return (error); +} + +/* + * Helper routine for polled system interfaces that use + * ipmi_polled_enqueue_request() to queue requests. This request + * waits until there is a pending request and then returns the first + * request. If the driver is shutting down, it returns NULL. + */ +struct ipmi_request * +ipmi_dequeue_request(struct ipmi_softc *sc) +{ + struct ipmi_request *req; + + IPMI_LOCK_ASSERT(sc); + + while (!sc->ipmi_detaching && TAILQ_EMPTY(&sc->ipmi_pending_requests)) + cv_wait(&sc->ipmi_request_added, &sc->ipmi_lock); + if (sc->ipmi_detaching) + return (NULL); + + req = TAILQ_FIRST(&sc->ipmi_pending_requests); + TAILQ_REMOVE(&sc->ipmi_pending_requests, req, ir_link); + return (req); +} + +/* Default implementation of ipmi_enqueue_request() for polled interfaces. */ +int +ipmi_polled_enqueue_request(struct ipmi_softc *sc, struct ipmi_request *req) +{ + + IPMI_LOCK_ASSERT(sc); + + TAILQ_INSERT_TAIL(&sc->ipmi_pending_requests, req, ir_link); + cv_signal(&sc->ipmi_request_added); + return (0); +} + +#if 0 /* XXX swildner: watchdog(9) (FreeBSD) -> wdog(9) (DragonFly) porting */ +/* + * Watchdog event handler. + */ + +static int +ipmi_set_watchdog(struct ipmi_softc *sc, unsigned int sec) +{ + struct ipmi_request *req; + int error; + + if (sec > 0xffff / 10) + return (EINVAL); + + req = ipmi_alloc_driver_request(IPMI_ADDR(IPMI_APP_REQUEST, 0), + IPMI_SET_WDOG, 6, 0); + + if (sec) { + req->ir_request[0] = IPMI_SET_WD_TIMER_DONT_STOP + | IPMI_SET_WD_TIMER_SMS_OS; + req->ir_request[1] = IPMI_SET_WD_ACTION_RESET; + req->ir_request[2] = 0; + req->ir_request[3] = 0; /* Timer use */ + req->ir_request[4] = (sec * 10) & 0xff; + req->ir_request[5] = (sec * 10) >> 8; + } else { + req->ir_request[0] = IPMI_SET_WD_TIMER_SMS_OS; + req->ir_request[1] = 0; + req->ir_request[2] = 0; + req->ir_request[3] = 0; /* Timer use */ + req->ir_request[4] = 0; + req->ir_request[5] = 0; + } + + error = ipmi_submit_driver_request(sc, req, 0); + if (error) + device_printf(sc->ipmi_dev, "Failed to set watchdog\n"); + else if (sec) { + ipmi_free_request(req); + + req = ipmi_alloc_driver_request(IPMI_ADDR(IPMI_APP_REQUEST, 0), + IPMI_RESET_WDOG, 0, 0); + + error = ipmi_submit_driver_request(sc, req, 0); + if (error) + device_printf(sc->ipmi_dev, + "Failed to reset watchdog\n"); + } + + ipmi_free_request(req); + return (error); + /* + dump_watchdog(sc); + */ +} + +static int +ipmi_watchdog(void *unused, int period) +{ + unsigned int timeout; + struct ipmi_softc *sc = ipmi_wdog_sc; + int e; + + timeout = (period * 1000) / 1000000000; + if (timeout == 0) + timeout = 1; + e = ipmi_set_watchdog(sc, timeout); + if (e == 0) + sc->ipmi_watchdog_active = 1; + else + ipmi_set_watchdog(sc, 0); + return (period); +} +#endif + +static void +ipmi_startup(void *arg) +{ + struct ipmi_softc *sc = arg; + struct ipmi_request *req; + device_t dev; + int error, i; + + config_intrhook_disestablish(&sc->ipmi_ich); + dev = sc->ipmi_dev; + + /* Initialize interface-independent state. */ + lockinit(&sc->ipmi_lock, device_get_nameunit(dev), 0, LK_CANRECURSE); + cv_init(&sc->ipmi_request_added, "ipmireq"); + TAILQ_INIT(&sc->ipmi_pending_requests); + + /* Initialize interface-dependent state. */ + error = sc->ipmi_startup(sc); + if (error) { + device_printf(dev, "Failed to initialize interface: %d\n", + error); + return; + } + + /* Send a GET_DEVICE_ID request. */ + req = ipmi_alloc_driver_request(IPMI_ADDR(IPMI_APP_REQUEST, 0), + IPMI_GET_DEVICE_ID, 0, 15); + + error = ipmi_submit_driver_request(sc, req, MAX_TIMEOUT); + if (error == EWOULDBLOCK) { + device_printf(dev, "Timed out waiting for GET_DEVICE_ID\n"); + ipmi_free_request(req); + return; + } else if (error) { + device_printf(dev, "Failed GET_DEVICE_ID: %d\n", error); + ipmi_free_request(req); + return; + } else if (req->ir_compcode != 0) { + device_printf(dev, + "Bad completion code for GET_DEVICE_ID: %d\n", + req->ir_compcode); + ipmi_free_request(req); + return; + } else if (req->ir_replylen < 5) { + device_printf(dev, "Short reply for GET_DEVICE_ID: %d\n", + req->ir_replylen); + ipmi_free_request(req); + return; + } + + device_printf(dev, "IPMI device rev. %d, firmware rev. %d.%d%d, " + "version %d.%d\n", + req->ir_reply[1] & 0x0f, + req->ir_reply[2] & 0x7f, req->ir_reply[3] >> 4, req->ir_reply[3] & 0x0f, + req->ir_reply[4] & 0x0f, req->ir_reply[4] >> 4); + + ipmi_free_request(req); + + req = ipmi_alloc_driver_request(IPMI_ADDR(IPMI_APP_REQUEST, 0), + IPMI_CLEAR_FLAGS, 1, 0); + + ipmi_submit_driver_request(sc, req, 0); + + /* XXX: Magic numbers */ + if (req->ir_compcode == 0xc0) { + device_printf(dev, "Clear flags is busy\n"); + } + if (req->ir_compcode == 0xc1) { + device_printf(dev, "Clear flags illegal\n"); + } + ipmi_free_request(req); + + for (i = 0; i < 8; i++) { + req = ipmi_alloc_driver_request(IPMI_ADDR(IPMI_APP_REQUEST, 0), + IPMI_GET_CHANNEL_INFO, 1, 0); + req->ir_request[0] = i; + + ipmi_submit_driver_request(sc, req, 0); + + if (req->ir_compcode != 0) { + ipmi_free_request(req); + break; + } + ipmi_free_request(req); + } + device_printf(dev, "Number of channels %d\n", i); + +#if 0 /* XXX swildner: watchdog(9) (FreeBSD) -> wdog(9) (DragonFly) porting */ + /* probe for watchdog */ + req = ipmi_alloc_driver_request(IPMI_ADDR(IPMI_APP_REQUEST, 0), + IPMI_GET_WDOG, 0, 0); + + ipmi_submit_driver_request(sc, req, 0); + + if (req->ir_compcode == 0x00) { + device_printf(dev, "Attached watchdog\n"); + /* register the watchdog event handler */ + wdog_register(&ipmi_wdog); + } + ipmi_free_request(req); +#endif + + sc->ipmi_cdev = make_dev(&ipmi_ops, device_get_unit(dev), + UID_ROOT, GID_OPERATOR, 0660, "ipmi%d", device_get_unit(dev)); + if (sc->ipmi_cdev == NULL) { + device_printf(dev, "Failed to create cdev\n"); + return; + } + sc->ipmi_cdev->si_drv1 = sc; +} + +int +ipmi_attach(device_t dev) +{ + struct ipmi_softc *sc = device_get_softc(dev); + int error; + + if (sc->ipmi_irq_res != NULL && sc->ipmi_intr != NULL) { + error = bus_setup_intr(dev, sc->ipmi_irq_res, 0, + sc->ipmi_intr, sc, &sc->ipmi_irq, NULL); + if (error) { + device_printf(dev, "can't set up interrupt\n"); + return (error); + } + } + + bzero(&sc->ipmi_ich, sizeof(struct intr_config_hook)); + sc->ipmi_ich.ich_func = ipmi_startup; + sc->ipmi_ich.ich_arg = sc; + sc->ipmi_ich.ich_desc = "ipmi"; + if (config_intrhook_establish(&sc->ipmi_ich) != 0) { + device_printf(dev, "can't establish configuration hook\n"); + return (ENOMEM); + } + + ipmi_attached = 1; + return (0); +} + +int +ipmi_detach(device_t dev) +{ + struct ipmi_softc *sc; + + sc = device_get_softc(dev); + + /* Fail if there are any open handles. */ + IPMI_LOCK(sc); + if (sc->ipmi_opened) { + IPMI_UNLOCK(sc); + return (EBUSY); + } + IPMI_UNLOCK(sc); + if (sc->ipmi_cdev) + destroy_dev(sc->ipmi_cdev); + +#if 0 /* XXX swildner: watchdog(9) (FreeBSD) -> wdog(9) (DragonFly) porting */ + /* Detach from watchdog handling and turn off watchdog. */ + wdog_unregister(&ipmi_wdog); + ipmi_set_watchdog(sc, 0); +#endif + + /* XXX: should use shutdown callout I think. */ + /* If the backend uses a kthread, shut it down. */ + IPMI_LOCK(sc); + sc->ipmi_detaching = 1; + if (sc->ipmi_kthread) { + cv_broadcast(&sc->ipmi_request_added); + lksleep(sc->ipmi_kthread, &sc->ipmi_lock, 0, "ipmi_wait", 0); + } + IPMI_UNLOCK(sc); + if (sc->ipmi_irq) + bus_teardown_intr(dev, sc->ipmi_irq_res, sc->ipmi_irq); + + ipmi_release_resources(dev); + lockuninit(&sc->ipmi_lock); + return (0); +} + +void +ipmi_release_resources(device_t dev) +{ + struct ipmi_softc *sc; + int i; + + sc = device_get_softc(dev); + if (sc->ipmi_irq) + bus_teardown_intr(dev, sc->ipmi_irq_res, sc->ipmi_irq); + if (sc->ipmi_irq_res) + bus_release_resource(dev, SYS_RES_IRQ, sc->ipmi_irq_rid, + sc->ipmi_irq_res); + for (i = 0; i < MAX_RES; i++) + if (sc->ipmi_io_res[i]) + bus_release_resource(dev, sc->ipmi_io_type, + sc->ipmi_io_rid + i, sc->ipmi_io_res[i]); +} + +devclass_t ipmi_devclass; + +/* XXX: Why? */ +static void +ipmi_unload(void *arg) +{ + device_t * devs; + int count; + int i; + + if (devclass_get_devices(ipmi_devclass, &devs, &count) != 0) + return; + for (i = 0; i < count; i++) + device_delete_child(device_get_parent(devs[i]), devs[i]); + kfree(devs, M_TEMP); +} +SYSUNINIT(ipmi_unload, SI_SUB_DRIVERS, SI_ORDER_FIRST, ipmi_unload, NULL); + +#ifdef IMPI_DEBUG +static void +dump_buf(u_char *data, int len) +{ + char buf[20]; + char line[1024]; + char temp[30]; + int count = 0; + int i=0; + + printf("Address %p len %d\n", data, len); + if (len > 256) + len = 256; + line[0] = '\000'; + for (; len > 0; len--, data++) { + sprintf(temp, "%02x ", *data); + strcat(line, temp); + if (*data >= ' ' && *data <= '~') + buf[count] = *data; + else if (*data >= 'A' && *data <= 'Z') + buf[count] = *data; + else + buf[count] = '.'; + if (++count == 16) { + buf[count] = '\000'; + count = 0; + printf(" %3x %s %s\n", i, line, buf); + i+=16; + line[0] = '\000'; + } + } + buf[count] = '\000'; + + for (; count != 16; count++) { + strcat(line, " "); + } + printf(" %3x %s %s\n", i, line, buf); +} +#endif diff --git a/sys/dev/misc/ipmi/ipmi_acpi.c b/sys/dev/misc/ipmi/ipmi_acpi.c new file mode 100644 index 0000000000..56b95707d0 --- /dev/null +++ b/sys/dev/misc/ipmi/ipmi_acpi.c @@ -0,0 +1,210 @@ +/*- + * Copyright (c) 2006 IronPort Systems Inc. + * 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. + * + * $FreeBSD: head/sys/dev/ipmi/ipmi_acpi.c 193530 2009-06-05 18:44:36Z jkim $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +/* Hooks for the ACPI CA debugging infrastructure */ +#define _COMPONENT ACPI_BUTTON +ACPI_MODULE_NAME("IPMI") + +#ifdef LOCAL_MODULE +#include +#include +#else +#include +#include +#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; + 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); diff --git a/sys/dev/misc/ipmi/ipmi_isa.c b/sys/dev/misc/ipmi/ipmi_isa.c new file mode 100644 index 0000000000..6a7b8a2360 --- /dev/null +++ b/sys/dev/misc/ipmi/ipmi_isa.c @@ -0,0 +1,285 @@ +/*- + * Copyright (c) 2006 IronPort Systems Inc. + * 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. + * + * $FreeBSD: head/sys/dev/ipmi/ipmi_isa.c 253813 2013-07-30 18:54:24Z sbruno $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#ifdef LOCAL_MODULE +#include +#include +#else +#include +#include +#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, parent, 0, "ipmi", -1); + } +} + +static int +ipmi_isa_probe(device_t dev) +{ + + /* + * 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 (ENXIO); + + /* 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_hint_identify(device_t dev, struct ipmi_get_info *info) +{ + const char *mode, *name; + int i, unit, val; + + /* We require at least a "mode" hint. */ + name = device_get_name(dev); + unit = device_get_unit(dev); + if (resource_string_value(name, unit, "mode", &mode) != 0) + return (0); + + /* Set the mode and default I/O resources for each mode. */ + bzero(info, sizeof(struct ipmi_get_info)); + if (strcasecmp(mode, "KCS") == 0) { + info->iface_type = KCS_MODE; + info->address = 0xca2; + info->io_mode = 1; + info->offset = 1; + } else if (strcasecmp(mode, "SMIC") == 0) { + info->iface_type = SMIC_MODE; + info->address = 0xca9; + info->io_mode = 1; + info->offset = 1; + } else if (strcasecmp(mode, "BT") == 0) { + info->iface_type = BT_MODE; + info->address = 0xe4; + info->io_mode = 1; + info->offset = 1; + } else { + device_printf(dev, "Invalid mode %s\n", mode); + return (0); + } + + /* + * Kill any resources that isahint.c might have setup for us + * since it will conflict with how we do resources. + */ + for (i = 0; i < 2; i++) { + bus_delete_resource(dev, SYS_RES_MEMORY, i); + bus_delete_resource(dev, SYS_RES_IOPORT, i); + } + + /* Allow the I/O address to be overriden via hints. */ + if (resource_int_value(name, unit, "port", &val) == 0 && val != 0) { + info->address = val; + info->io_mode = 1; + } else if (resource_int_value(name, unit, "maddr", &val) == 0 && + val != 0) { + info->address = val; + info->io_mode = 0; + } + + /* Allow the spacing to be overriden. */ + if (resource_int_value(name, unit, "spacing", &val) == 0) { + switch (val) { + case 8: + info->offset = 1; + break; + case 16: + info->offset = 2; + break; + case 32: + info->offset = 4; + break; + default: + device_printf(dev, "Invalid register spacing\n"); + return (0); + } + } + return (1); +} + +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; + + /* + * Pull info out of the SMBIOS table. If that doesn't work, use + * hints to enumerate a device. + */ + if (!ipmi_smbios_identify(&info) && + !ipmi_hint_identify(dev, &info)) + return (ENXIO); + + 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); diff --git a/sys/dev/misc/ipmi/ipmi_kcs.c b/sys/dev/misc/ipmi/ipmi_kcs.c new file mode 100644 index 0000000000..39432dea5e --- /dev/null +++ b/sys/dev/misc/ipmi/ipmi_kcs.c @@ -0,0 +1,607 @@ +/*- + * Copyright (c) 2006 IronPort Systems Inc. + * 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. + * + * $FreeBSD: head/sys/dev/ipmi/ipmi_kcs.c 248705 2013-03-25 14:30:34Z melifaro $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef LOCAL_MODULE +#include +#include +#else +#include +#include +#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) { + IPMI_UNLOCK(sc); + 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_LOCK(sc); + ipmi_complete_request(sc, req); + } + IPMI_UNLOCK(sc); + kthread_exit(); +} + +static int +kcs_startup(struct ipmi_softc *sc) +{ + + return (kthread_create(kcs_loop, sc, &sc->ipmi_kthread, "%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 + kprintf("failed\n"); +#endif + sc->ipmi_io_spacing <<= 1; + if (sc->ipmi_io_spacing > 4) + return (0); + goto retry; + } +#ifdef KCS_DEBUG + kprintf("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); +} diff --git a/sys/dev/misc/ipmi/ipmi_linux.c b/sys/dev/misc/ipmi/ipmi_linux.c new file mode 100644 index 0000000000..b3ded96f0b --- /dev/null +++ b/sys/dev/misc/ipmi/ipmi_linux.c @@ -0,0 +1,114 @@ +/*- + * Copyright (c) 2009 IronPort Systems Inc. + * 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. + * + * $FreeBSD: head/sys/dev/ipmi/ipmi_linux.c 263233 2014-03-16 10:55:57Z rwatson $ + */ + +/* + * Linux ioctl handler for the ipmi device driver + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef __amd64__ +#include +#include +#else +#include +#include +#endif +#include +#include + +/* There are multiple ioctl number ranges that need to be handled */ +#define IPMI_LINUX_IOCTL_MIN 0x690b +#define IPMI_LINUX_IOCTL_MAX 0x6915 + +/* Linux versions of ioctl's */ +#define L_IPMICTL_RECEIVE_MSG_TRUNC _IOWR(IPMI_IOC_MAGIC, 11, struct ipmi_recv) +#define L_IPMICTL_RECEIVE_MSG _IOWR(IPMI_IOC_MAGIC, 12, struct ipmi_recv) +#define L_IPMICTL_SEND_COMMAND _IOW(IPMI_IOC_MAGIC, 13, struct ipmi_req) +#define L_IPMICTL_REGISTER_FOR_CMD _IOW(IPMI_IOC_MAGIC, 14, struct ipmi_cmdspec) +#define L_IPMICTL_UNREGISTER_FOR_CMD _IOW(IPMI_IOC_MAGIC, 15, struct ipmi_cmdspec) +#define L_IPMICTL_SET_GETS_EVENTS_CMD _IOW(IPMI_IOC_MAGIC, 16, int) +#define L_IPMICTL_SET_MY_ADDRESS_CMD _IOW(IPMI_IOC_MAGIC, 17, unsigned int) +#define L_IPMICTL_GET_MY_ADDRESS_CMD _IOW(IPMI_IOC_MAGIC, 18, unsigned int) +#define L_IPMICTL_SET_MY_LUN_CMD _IOW(IPMI_IOC_MAGIC, 19, unsigned int) +#define L_IPMICTL_GET_MY_LUN_CMD _IOW(IPMI_IOC_MAGIC, 20, unsigned int) + +static linux_ioctl_function_t ipmi_linux_ioctl; +static struct linux_ioctl_handler ipmi_linux_handler = {ipmi_linux_ioctl, + IPMI_LINUX_IOCTL_MIN, + IPMI_LINUX_IOCTL_MAX}; + +SYSINIT (ipmi_linux_register, SI_SUB_KLD, SI_ORDER_MIDDLE, + linux_ioctl_register_handler, &ipmi_linux_handler); +SYSUNINIT(ipmi_linux_unregister, SI_SUB_KLD, SI_ORDER_MIDDLE, + linux_ioctl_unregister_handler, &ipmi_linux_handler); + +static int +ipmi_linux_modevent(module_t mod, int type, void *data) +{ + /* Do we care about any specific load/unload actions? */ + return (0); +} + +DEV_MODULE(ipmi_linux, ipmi_linux_modevent, NULL); +MODULE_DEPEND(ipmi_linux, linux, 1, 1, 1); + +static int +ipmi_linux_ioctl(struct thread *td, struct linux_ioctl_args *args) +{ + cap_rights_t rights; + struct file *fp; + u_long cmd; + int error; + + error = fget(td, args->fd, cap_rights_init(&rights, CAP_IOCTL), &fp); + if (error != 0) + return (error); + cmd = args->cmd; + + switch(cmd) { + case L_IPMICTL_GET_MY_ADDRESS_CMD: + cmd = IPMICTL_GET_MY_ADDRESS_CMD; + break; + case L_IPMICTL_GET_MY_LUN_CMD: + cmd = IPMICTL_GET_MY_LUN_CMD; + break; + } + /* + * Pass the ioctl off to our standard handler. + */ + error = (fo_ioctl(fp, cmd, (caddr_t)args->arg, td->td_ucred, td)); + fdrop(fp, td); + return (error); +} diff --git a/sys/dev/misc/ipmi/ipmi_linux/Makefile b/sys/dev/misc/ipmi/ipmi_linux/Makefile new file mode 100644 index 0000000000..c743c8bf87 --- /dev/null +++ b/sys/dev/misc/ipmi/ipmi_linux/Makefile @@ -0,0 +1,8 @@ +# $FreeBSD: head/sys/modules/ipmi/ipmi_linux/Makefile 190452 2009-03-26 19:15:31Z ambrisko $ + +.PATH: ${.CURDIR}/.. + +KMOD= ipmi_linux +SRCS= ipmi_linux.c + +.include diff --git a/sys/dev/misc/ipmi/ipmi_pci.c b/sys/dev/misc/ipmi/ipmi_pci.c new file mode 100644 index 0000000000..3a94a2abea --- /dev/null +++ b/sys/dev/misc/ipmi/ipmi_pci.c @@ -0,0 +1,291 @@ +/*- + * Copyright (c) 2006 IronPort Systems Inc. + * 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. + * + * $FreeBSD: head/sys/dev/ipmi/ipmi_pci.c 168162 2007-03-31 21:39:02Z jhb $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifdef LOCAL_MODULE +#include +#else +#include +#endif + +static int ipmi_pci_probe(device_t dev); +static int ipmi_pci_attach(device_t dev); + +static struct ipmi_ident +{ + u_int16_t vendor; + u_int16_t device; + char *desc; +} ipmi_identifiers[] = { + {0x1028, 0x000d, "Dell PE2650 SMIC interface"}, + {0, 0, 0} +}; + +const char * +ipmi_pci_match(uint16_t vendor, uint16_t device) +{ + struct ipmi_ident *m; + + for (m = ipmi_identifiers; m->vendor != 0; m++) + if (m->vendor == vendor && m->device == device) + return (m->desc); + return (NULL); +} + +static int +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); + 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); + + sc->ipmi_dev = dev; + + 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"); + 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); + } + + 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 (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_probe, ipmi_pci_probe), + DEVMETHOD(device_attach, ipmi_pci_attach), + DEVMETHOD(device_detach, ipmi_detach), + { 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) == PCIS_SERIALBUS_IPMI) { + 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 PCIP_SERIALBUS_IPMI_SMIC: + iface = SMIC_MODE; + break; + case PCIP_SERIALBUS_IPMI_KCS: + iface = KCS_MODE; + break; + case PCIP_SERIALBUS_IPMI_BT: + 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); + } + + /* Check the BAR to determine our resource type. */ + sc->ipmi_io_rid = PCIR_BAR(0); + if (PCI_BAR_IO(pci_read_config(dev, PCIR_BAR(0), 4))) + 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); diff --git a/sys/dev/misc/ipmi/ipmi_smbios.c b/sys/dev/misc/ipmi/ipmi_smbios.c new file mode 100644 index 0000000000..1d5ffeee75 --- /dev/null +++ b/sys/dev/misc/ipmi/ipmi_smbios.c @@ -0,0 +1,268 @@ +/*- + * Copyright (c) 2006 IronPort Systems Inc. + * 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. + * + * $FreeBSD: head/sys/dev/ipmi/ipmi_smbios.c 241027 2012-09-28 11:59:32Z jhb $ + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#ifdef LOCAL_MODULE +#include +#include +#else +#include +#include +#endif + +#if __FreeBSD_version < 602110 +#define pmap_mapbios pmap_mapdev +#define pmap_unmapbios pmap_unmapdev +#endif + +struct ipmi_entry { + uint8_t type; + uint8_t length; + uint16_t handle; + uint8_t interface_type; + uint8_t spec_revision; + uint8_t i2c_slave_address; + uint8_t NV_storage_device_address; + uint64_t base_address; + uint8_t base_address_modifier; + 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 + +typedef void (*smbios_callback_t)(struct smbios_structure_header *, void *); + +static struct ipmi_get_info ipmi_info; +static int ipmi_probed; +static struct lock ipmi_info_lock; +LOCK_SYSINIT(ipmi_info, &ipmi_info_lock, "ipmi info", LK_CANRECURSE); + +static void ipmi_smbios_probe(struct ipmi_get_info *); +static int smbios_cksum(struct smbios_eps *); +static void smbios_walk_table(uint8_t *, int, smbios_callback_t, + void *); +static void smbios_ipmi_info(struct smbios_structure_header *, void *); + +static void +smbios_ipmi_info(struct smbios_structure_header *h, void *arg) +{ + struct ipmi_get_info *info; + struct ipmi_entry *s; + + if (h->type != 38 || h->length < + offsetof(struct ipmi_entry, interrupt_number)) + return; + s = (struct ipmi_entry *)h; + info = arg; + bzero(info, sizeof(struct ipmi_get_info)); + 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: + kprintf("SMBIOS: Invalid register spacing\n"); + return; + } + break; + case SSIF_MODE: + if ((s->base_address & 0xffffffffffffff00) != 0) { + kprintf("SMBIOS: Invalid SSIF SMBus address, using BMC I2C slave address instead\n"); + info->address = s->i2c_slave_address; + break; + } + info->address = IPMI_BAR_ADDR(s->base_address); + break; + default: + return; + } + if (s->length > offsetof(struct ipmi_entry, interrupt_number)) { + if (s->interrupt_number > 15) + kprintf("SMBIOS: Non-ISA IRQ %d for IPMI\n", + s->interrupt_number); + else + info->irq = s->interrupt_number; + } + info->iface_type = s->interface_type; +} + +static void +smbios_walk_table(uint8_t *p, int entries, smbios_callback_t cb, void *arg) +{ + struct smbios_structure_header *s; + + while (entries--) { + s = (struct smbios_structure_header *)p; + cb(s, arg); + + /* + * Look for a double-nul after the end of the + * formatted area of this structure. + */ + p += s->length; + while (!(p[0] == 0 && p[1] == 0)) + p++; + + /* + * Skip over the double-nul to the start of the next + * structure. + */ + p += 2; + } +} + +/* + * 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) +{ + struct smbios_eps *header; + void *table; + u_int32_t addr; + + 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) + return; + + /* + * 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_eps)); + table = pmap_mapbios(addr, header->length); + pmap_unmapbios((vm_offset_t)header, sizeof(struct smbios_eps)); + header = table; + if (smbios_cksum(header) != 0) { + pmap_unmapbios((vm_offset_t)header, header->length); + return; + } + + /* Now map the actual table and walk it looking for an IPMI entry. */ + table = pmap_mapbios(header->structure_table_address, + header->structure_table_length); + smbios_walk_table(table, header->number_structures, smbios_ipmi_info, + info); + + /* Unmap everything. */ + pmap_unmapbios((vm_offset_t)table, header->structure_table_length); + pmap_unmapbios((vm_offset_t)header, header->length); +} + +/* + * 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) +{ + + lockmgr(&ipmi_info_lock, LK_EXCLUSIVE); + switch (ipmi_probed) { + case 0: + /* Need to probe the SMBIOS table. */ + ipmi_probed++; + lockmgr(&ipmi_info_lock, LK_RELEASE); + ipmi_smbios_probe(&ipmi_info); + lockmgr(&ipmi_info_lock, LK_EXCLUSIVE); + ipmi_probed++; + wakeup(&ipmi_info); + break; + case 1: + /* Another thread is currently probing the table, so wait. */ + while (ipmi_probed == 1) + lksleep(&ipmi_info, &ipmi_info_lock, 0, "ipmi info", 0); + break; + default: + /* The cached data is available. */ + break; + } + + bcopy(&ipmi_info, info, sizeof(ipmi_info)); + lockmgr(&ipmi_info_lock, LK_RELEASE); + + return (info->iface_type != 0); +} + +static int +smbios_cksum(struct smbios_eps *e) +{ + u_int8_t *ptr; + u_int8_t cksum; + int i; + + ptr = (u_int8_t *)e; + cksum = 0; + for (i = 0; i < e->length; i++) { + cksum += ptr[i]; + } + + return (cksum); +} diff --git a/sys/dev/misc/ipmi/ipmi_smbus.c b/sys/dev/misc/ipmi/ipmi_smbus.c new file mode 100644 index 0000000000..248404d511 --- /dev/null +++ b/sys/dev/misc/ipmi/ipmi_smbus.c @@ -0,0 +1,130 @@ +/*- + * Copyright (c) 2006 IronPort Systems Inc. + * 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. + * + * $FreeBSD: head/sys/dev/ipmi/ipmi_smbus.c 162562 2006-09-22 22:11:29Z jhb $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "smbus_if.h" + +#ifdef LOCAL_MODULE +#include +#else +#include +#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, 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); diff --git a/sys/dev/misc/ipmi/ipmi_smic.c b/sys/dev/misc/ipmi/ipmi_smic.c new file mode 100644 index 0000000000..8c6e4e8729 --- /dev/null +++ b/sys/dev/misc/ipmi/ipmi_smic.c @@ -0,0 +1,407 @@ +/*- + * Copyright (c) 2006 IronPort Systems Inc. + * 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. + * + * $FreeBSD: head/sys/dev/ipmi/ipmi_smic.c 248705 2013-03-25 14:30:34Z melifaro $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef LOCAL_MODULE +#include +#include +#else +#include +#include +#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; + + smic_wait_for_tx_okay(sc); + OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_WR_NEXT); + 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; + + smic_wait_for_tx_okay(sc); + OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_WR_END); + 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); + + smic_wait_for_rx_okay(sc); + OUTB(sc, SMIC_CTL_STS, SMIC_CC_SMS_RD_START); + 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; + + smic_wait_for_rx_okay(sc); + OUTB(sc, SMIC_CTL_STS, SMIC_SC_SMS_RD_NEXT); + 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); +#ifdef SMIC_DEBUG + device_printf(sc->ipmi_dev, "SMIC: WRITE_START address: %02x\n", + req->ir_addr); +#endif + + if (req->ir_requestlen == 0) { + /* Send the command as the last byte. */ + if (!smic_write_last(sc, req->ir_command)) + return (0); +#ifdef SMIC_DEBUG + device_printf(sc->ipmi_dev, "SMIC: Wrote command: %02x\n", + req->ir_command); +#endif + } else { + /* Send the command. */ + if (!smic_write_next(sc, req->ir_command)) + return (0); +#ifdef SMIC_DEBUG + device_printf(sc->ipmi_dev, "SMIC: Wrote command: %02x\n", + req->ir_command); +#endif + + /* Send the payload. */ + cp = req->ir_request; + for (i = 0; i < req->ir_requestlen - 1; i++) { + if (!smic_write_next(sc, *cp++)) + return (0); +#ifdef SMIC_DEBUG + device_printf(sc->ipmi_dev, "SMIC: Wrote data: %02x\n", + cp[-1]); +#endif + } + if (!smic_write_last(sc, *cp)) + return (0); +#ifdef SMIC_DEBUG + device_printf(sc->ipmi_dev, "SMIC: Write last data: %02x\n", + *cp); +#endif + } + + /* Start the read phase by reading the NetFn/LUN. */ + if (smic_start_read(sc, &data) != 1) + return (0); +#ifdef SMIC_DEBUG + device_printf(sc->ipmi_dev, "SMIC: Read address: %02x\n", data); +#endif + 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); +#ifdef SMIC_DEBUG + device_printf(sc->ipmi_dev, "SMIC: Read command: %02x\n", data); +#endif + 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); +#ifdef SMIC_DEBUG + device_printf(sc->ipmi_dev, "SMIC: Read completion code: %02x\n", + req->ir_compcode); +#endif + + /* Finally, read the reply from the BMC. */ + i = 0; + while (state == 1) { + state = smic_read_byte(sc, &data); + if (state == 0) + return (0); + if (i < req->ir_replybuflen) { + req->ir_reply[i] = data; +#ifdef SMIC_DEBUG + device_printf(sc->ipmi_dev, "SMIC: Read data: %02x\n", + data); + } else { + device_printf(sc->ipmi_dev, + "SMIC: Read short %02x byte %d\n", data, i + 1); +#endif + } + i++; + } + + /* Terminate the transfer. */ + if (!smic_read_end(sc)) + return (0); + req->ir_replylen = i; +#ifdef SMIC_DEBUG + device_printf(sc->ipmi_dev, "SMIC: 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, + "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) { + IPMI_UNLOCK(sc); + 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_LOCK(sc); + ipmi_complete_request(sc, req); + } + IPMI_UNLOCK(sc); + kthread_exit(); +} + +static int +smic_startup(struct ipmi_softc *sc) +{ + + return (kthread_create(smic_loop, sc, &sc->ipmi_kthread, + "%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); + } + +#ifdef SMIC_DEBUG + device_printf(sc->ipmi_dev, "SMIC: initial state: %02x\n", flags); +#endif + + return (0); +} diff --git a/sys/dev/misc/ipmi/ipmi_ssif.c b/sys/dev/misc/ipmi/ipmi_ssif.c new file mode 100644 index 0000000000..66bdde55e9 --- /dev/null +++ b/sys/dev/misc/ipmi/ipmi_ssif.c @@ -0,0 +1,374 @@ +/*- + * Copyright (c) 2006 IronPort Systems Inc. + * 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. + * + * $FreeBSD: head/sys/dev/ipmi/ipmi_ssif.c 172836 2007-10-20 23:23:23Z julian $ + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "smbus_if.h" + +#ifdef LOCAL_MODULE +#include +#else +#include +#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++) + kprintf(" %02x", bytes[i]); + kprintf("\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(ssif_polled_request, 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(ssif_polled_request, 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(dev, "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 + kprintf("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(ssif_loop, 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(ssif_loop, 0, "delay", hz / 100); + + IPMI_LOCK(sc); + } + IPMI_UNLOCK(sc); + kthread_exit(); +} + +static int +ssif_startup(struct ipmi_softc *sc) +{ + + return (kthread_create(ssif_loop, sc, &sc->ipmi_kthread, + "%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); +} diff --git a/sys/dev/misc/ipmi/ipmivars.h b/sys/dev/misc/ipmi/ipmivars.h new file mode 100644 index 0000000000..613fc0cd74 --- /dev/null +++ b/sys/dev/misc/ipmi/ipmivars.h @@ -0,0 +1,249 @@ +/*- + * Copyright (c) 2006 IronPort Systems Inc. + * 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. + * + * $FreeBSD: head/sys/dev/ipmi/ipmivars.h 253812 2013-07-30 18:44:29Z sbruno $ + */ + +#ifndef __IPMIVARS_H__ +#define __IPMIVARS_H__ + +struct ipmi_get_info { + 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 file descriptor data. */ +struct ipmi_device { + TAILQ_ENTRY(ipmi_device) ipmi_link; + TAILQ_HEAD(,ipmi_request) ipmi_completed_requests; + struct ipmi_softc *ipmi_softc; + 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; + union { + struct ipmi_kcs kcs; + struct ipmi_smic smic; + struct ipmi_ssif ssif; + } _iface; + int ipmi_io_rid; + int ipmi_io_type; + struct resource *ipmi_io_res[MAX_RES]; + int ipmi_io_spacing; + int ipmi_irq_rid; + struct resource *ipmi_irq_res; + void *ipmi_irq; + int ipmi_detaching; + int ipmi_opened; + struct cdev *ipmi_cdev; + TAILQ_HEAD(,ipmi_request) ipmi_pending_requests; + int ipmi_watchdog_active; + struct intr_config_hook ipmi_ich; + struct lock ipmi_lock; + struct cv ipmi_request_added; + struct thread *ipmi_kthread; + driver_intr_t *ipmi_intr; + struct kqinfo ipmi_kq; + 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 */ +#define KCS_STATUS_SMS_ATN 0x04 /* Ready in RX queue */ +#define KCS_STATUS_C_D 0x08 /* Command/Data register write*/ +#define KCS_STATUS_OEM1 0x10 +#define KCS_STATUS_OEM2 0x20 +#define KCS_STATUS_S0 0x40 +#define KCS_STATUS_S1 0x80 + #define KCS_STATUS_STATE(x) ((x)>>6) + #define KCS_STATUS_STATE_IDLE 0x0 + #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 + +/* KCS control codes */ +#define KCS_CONTROL_GET_STATUS_ABORT 0x60 +#define KCS_CONTROL_WRITE_START 0x61 +#define KCS_CONTROL_WRITE_END 0x62 +#define KCS_DATA_IN_READ 0x68 + +/* SMIC status flags */ +#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 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 +#define SMIC_CC_SMS_WR_START 0x41 +#define SMIC_CC_SMS_WR_NEXT 0x42 +#define SMIC_CC_SMS_WR_END 0x43 +#define SMIC_CC_SMS_RD_START 0x44 +#define SMIC_CC_SMS_RD_NEXT 0x45 +#define SMIC_CC_SMS_RD_END 0x46 + +/* SMIC status codes */ +#define SMIC_SC_SMS_RDY 0xc0 +#define SMIC_SC_SMS_WR_START 0xc1 +#define SMIC_SC_SMS_WR_NEXT 0xc2 +#define SMIC_SC_SMS_WR_END 0xc3 +#define SMIC_SC_SMS_RD_START 0xc4 +#define SMIC_SC_SMS_RD_NEXT 0xc5 +#define SMIC_SC_SMS_RD_END 0xc6 + +#define IPMI_ADDR(netfn, lun) ((netfn) << 2 | (lun)) +#define IPMI_REPLY_ADDR(addr) ((addr) + 0x4) + +#define IPMI_LOCK(sc) lockmgr(&(sc)->ipmi_lock, LK_EXCLUSIVE) +#define IPMI_UNLOCK(sc) lockmgr(&(sc)->ipmi_lock, LK_RELEASE) +#define IPMI_LOCK_ASSERT(sc) KKASSERT(lockowned(&(sc)->ipmi_lock)) + +#define ipmi_alloc_driver_request(addr, cmd, reqlen, replylen) \ + ipmi_alloc_request(NULL, 0, (addr), (cmd), (reqlen), (replylen)) + +/* 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 6 * 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 + +extern devclass_t ipmi_devclass; +extern int ipmi_attached; + +#endif /* !__IPMIVARS_H__ */ diff --git a/sys/platform/pc32/include/pc/bios.h b/sys/platform/pc32/include/pc/bios.h index 9f7d6df133..a83acfac38 100644 --- a/sys/platform/pc32/include/pc/bios.h +++ b/sys/platform/pc32/include/pc/bios.h @@ -27,6 +27,9 @@ * $FreeBSD: src/sys/i386/include/pc/bios.h,v 1.7.2.3 2001/10/21 03:16:56 yokota Exp $ */ +#ifndef _MACHINE_PC_BIOS_H_ +#define _MACHINE_PC_BIOS_H_ + /* * Signature structure for the BIOS32 Service Directory header */ @@ -316,3 +319,37 @@ struct bios_oem { extern int bios_oem_strings(struct bios_oem *oem, u_char *buffer, size_t maxlen); + +/* + * System Management BIOS + */ +#define SMBIOS_START 0xf0000 +#define SMBIOS_STEP 0x10 +#define SMBIOS_OFF 0 +#define SMBIOS_LEN 4 +#define SMBIOS_SIG "_SM_" + +struct smbios_eps { + uint8_t anchor_string[4]; /* '_SM_' */ + uint8_t checksum; + uint8_t length; + uint8_t major_version; + uint8_t minor_version; + uint16_t maximum_structure_size; + uint8_t entry_point_revision; + uint8_t formatted_area[5]; + uint8_t intermediate_anchor_string[5]; /* '_DMI_' */ + uint8_t intermediate_checksum; + uint16_t structure_table_length; + uint32_t structure_table_address; + uint16_t number_structures; + uint8_t BCD_revision; +}; + +struct smbios_structure_header { + uint8_t type; + uint8_t length; + uint16_t handle; +}; + +#endif /* _MACHINE_PC_BIOS_H_ */ diff --git a/sys/platform/pc64/include/pc/bios.h b/sys/platform/pc64/include/pc/bios.h index 86801857b8..22e1244bcc 100644 --- a/sys/platform/pc64/include/pc/bios.h +++ b/sys/platform/pc64/include/pc/bios.h @@ -73,5 +73,36 @@ struct bios_oem { extern int bios_oem_strings(struct bios_oem *oem, u_char *buffer, size_t maxlen); +/* + * System Management BIOS + */ +#define SMBIOS_START 0xf0000 +#define SMBIOS_STEP 0x10 +#define SMBIOS_OFF 0 +#define SMBIOS_LEN 4 +#define SMBIOS_SIG "_SM_" + +struct smbios_eps { + uint8_t anchor_string[4]; /* '_SM_' */ + uint8_t checksum; + uint8_t length; + uint8_t major_version; + uint8_t minor_version; + uint16_t maximum_structure_size; + uint8_t entry_point_revision; + uint8_t formatted_area[5]; + uint8_t intermediate_anchor_string[5]; /* '_DMI_' */ + uint8_t intermediate_checksum; + uint16_t structure_table_length; + uint32_t structure_table_address; + uint16_t number_structures; + uint8_t BCD_revision; +}; + +struct smbios_structure_header { + uint8_t type; + uint8_t length; + uint16_t handle; +}; #endif /* _MACHINE_PC_BIOS_H_ */ diff --git a/sys/sys/ipmi.h b/sys/sys/ipmi.h new file mode 100644 index 0000000000..036d275d74 --- /dev/null +++ b/sys/sys/ipmi.h @@ -0,0 +1,157 @@ +/*- + * Copyright (c) 2006 IronPort Systems Inc. + * 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. + * + * $FreeBSD: head/sys/sys/ipmi.h 162562 2006-09-22 22:11:29Z jhb $ + */ + +#ifndef __SYS_IPMI_H__ +#define __SYS_IPMI_H__ + +#include + +#define IPMI_MAX_ADDR_SIZE 0x20 +#define IPMI_MAX_RX 1024 +#define IPMI_BMC_SLAVE_ADDR 0x20 /* Linux Default slave address */ +#define IPMI_BMC_CHANNEL 0x0f /* Linux BMC channel */ + +#define IPMI_BMC_SMS_LUN 0x02 + +#define IPMI_SYSTEM_INTERFACE_ADDR_TYPE 0x0c +#define IPMI_IPMB_ADDR_TYPE 0x01 +#define IPMI_IPMB_BROADCAST_ADDR_TYPE 0x41 + +#define IPMI_IOC_MAGIC 'i' +#define IPMICTL_RECEIVE_MSG_TRUNC _IOWR(IPMI_IOC_MAGIC, 11, struct ipmi_recv) +#define IPMICTL_RECEIVE_MSG _IOWR(IPMI_IOC_MAGIC, 12, struct ipmi_recv) +#define IPMICTL_SEND_COMMAND _IOW(IPMI_IOC_MAGIC, 13, struct ipmi_req) +#define IPMICTL_REGISTER_FOR_CMD _IOW(IPMI_IOC_MAGIC, 14, struct ipmi_cmdspec) +#define IPMICTL_UNREGISTER_FOR_CMD _IOW(IPMI_IOC_MAGIC, 15, struct ipmi_cmdspec) +#define IPMICTL_SET_GETS_EVENTS_CMD _IOW(IPMI_IOC_MAGIC, 16, int) +#define IPMICTL_SET_MY_ADDRESS_CMD _IOW(IPMI_IOC_MAGIC, 17, unsigned int) +#define IPMICTL_GET_MY_ADDRESS_CMD _IOR(IPMI_IOC_MAGIC, 18, unsigned int) +#define IPMICTL_SET_MY_LUN_CMD _IOW(IPMI_IOC_MAGIC, 19, unsigned int) +#define IPMICTL_GET_MY_LUN_CMD _IOR(IPMI_IOC_MAGIC, 20, unsigned int) + +#define IPMI_RESPONSE_RECV_TYPE 1 +#define IPMI_ASYNC_EVENT_RECV_TYPE 2 +#define IPMI_CMD_RECV_TYPE 3 + +#define IPMI_APP_REQUEST 0x06 +#define IPMI_GET_DEVICE_ID 0x01 +#define IPMI_CLEAR_FLAGS 0x30 +#define IPMI_GET_MSG_FLAGS 0x31 +# define IPMI_MSG_AVAILABLE 0x01 +# define IPMI_MSG_BUFFER_FULL 0x02 +# define IPMI_WDT_PRE_TIMEOUT 0x08 +#define IPMI_GET_MSG 0x33 +#define IPMI_SEND_MSG 0x34 +#define IPMI_GET_CHANNEL_INFO 0x42 +#define IPMI_RESET_WDOG 0x22 +#define IPMI_SET_WDOG 0x24 +#define IPMI_GET_WDOG 0x25 + +#define IPMI_SET_WD_TIMER_SMS_OS 0x04 +#define IPMI_SET_WD_TIMER_DONT_STOP 0x40 +#define IPMI_SET_WD_ACTION_RESET 0x01 + +struct ipmi_msg { + unsigned char netfn; + unsigned char cmd; + unsigned short data_len; + unsigned char *data; +}; + +struct ipmi_req { + unsigned char *addr; + unsigned int addr_len; + long msgid; + struct ipmi_msg msg; +}; + +struct ipmi_recv { + int recv_type; + unsigned char *addr; + unsigned int addr_len; + long msgid; + struct ipmi_msg msg; +}; + +struct ipmi_cmdspec { + unsigned char netfn; + unsigned char cmd; +}; + + +struct ipmi_addr { + int addr_type; + short channel; + unsigned char data[IPMI_MAX_ADDR_SIZE]; +}; + +struct ipmi_system_interface_addr { + int addr_type; + short channel; + unsigned char lun; +}; + +struct ipmi_ipmb_addr { + int addr_type; + short channel; + 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__ */ -- 2.41.0