1155517Sambrisko/*- 2155517Sambrisko * Copyright (c) 2006 IronPort Systems Inc. <ambrisko@ironport.com> 3155517Sambrisko * All rights reserved. 4155517Sambrisko * 5155517Sambrisko * Redistribution and use in source and binary forms, with or without 6155517Sambrisko * modification, are permitted provided that the following conditions 7155517Sambrisko * are met: 8155517Sambrisko * 1. Redistributions of source code must retain the above copyright 9155517Sambrisko * notice, this list of conditions and the following disclaimer. 10155517Sambrisko * 2. Redistributions in binary form must reproduce the above copyright 11155517Sambrisko * notice, this list of conditions and the following disclaimer in the 12155517Sambrisko * documentation and/or other materials provided with the distribution. 13155517Sambrisko * 14155517Sambrisko * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15155517Sambrisko * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16155517Sambrisko * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17155517Sambrisko * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18155517Sambrisko * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19155517Sambrisko * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20155517Sambrisko * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21155517Sambrisko * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22155517Sambrisko * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23155517Sambrisko * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24155517Sambrisko * SUCH DAMAGE. 25155517Sambrisko */ 26155517Sambrisko 27155517Sambrisko#include <sys/cdefs.h> 28155517Sambrisko__FBSDID("$FreeBSD$"); 29155517Sambrisko 30155517Sambrisko#include <sys/param.h> 31155517Sambrisko#include <sys/systm.h> 32162562Sjhb#include <sys/bus.h> 33162562Sjhb#include <sys/condvar.h> 34162562Sjhb#include <sys/eventhandler.h> 35155517Sambrisko#include <sys/kernel.h> 36155517Sambrisko#include <sys/selinfo.h> 37155517Sambrisko 38155517Sambrisko#include <vm/vm.h> 39155517Sambrisko#include <vm/pmap.h> 40155517Sambrisko#include <machine/pc/bios.h> 41155517Sambrisko 42155517Sambrisko#ifdef LOCAL_MODULE 43155517Sambrisko#include <ipmi.h> 44155517Sambrisko#include <ipmivars.h> 45155517Sambrisko#else 46155517Sambrisko#include <sys/ipmi.h> 47155517Sambrisko#include <dev/ipmi/ipmivars.h> 48155517Sambrisko#endif 49155517Sambrisko 50162562Sjhbstruct ipmi_entry { 51155517Sambrisko uint8_t type; 52155517Sambrisko uint8_t length; 53155517Sambrisko uint16_t handle; 54155517Sambrisko uint8_t interface_type; 55155517Sambrisko uint8_t spec_revision; 56155517Sambrisko uint8_t i2c_slave_address; 57155517Sambrisko uint8_t NV_storage_device_address; 58155517Sambrisko uint64_t base_address; 59155517Sambrisko uint8_t base_address_modifier; 60155517Sambrisko uint8_t interrupt_number; 61155517Sambrisko}; 62155517Sambrisko 63162562Sjhb/* Fields in the base_address field of an IPMI entry. */ 64162562Sjhb#define IPMI_BAR_MODE(ba) ((ba) & 0x0000000000000001) 65162562Sjhb#define IPMI_BAR_ADDR(ba) ((ba) & 0xfffffffffffffffe) 66162562Sjhb 67162562Sjhb/* Fields in the base_address_modifier field of an IPMI entry. */ 68162562Sjhb#define IPMI_BAM_IRQ_TRIGGER 0x01 69162562Sjhb#define IPMI_BAM_IRQ_POLARITY 0x02 70162562Sjhb#define IPMI_BAM_IRQ_VALID 0x08 71162562Sjhb#define IPMI_BAM_ADDR_LSB(bam) (((bam) & 0x10) >> 4) 72162562Sjhb#define IPMI_BAM_REG_SPACING(bam) (((bam) & 0xc0) >> 6) 73162562Sjhb#define SPACING_8 0x0 74162562Sjhb#define SPACING_32 0x1 75162562Sjhb#define SPACING_16 0x2 76162562Sjhb 77241027Sjhbtypedef void (*smbios_callback_t)(struct smbios_structure_header *, void *); 78155517Sambrisko 79162562Sjhbstatic struct ipmi_get_info ipmi_info; 80162562Sjhbstatic int ipmi_probed; 81162562Sjhbstatic struct mtx ipmi_info_mtx; 82162562SjhbMTX_SYSINIT(ipmi_info, &ipmi_info_mtx, "ipmi info", MTX_DEF); 83155517Sambrisko 84162562Sjhbstatic void ipmi_smbios_probe(struct ipmi_get_info *); 85241027Sjhbstatic int smbios_cksum(struct smbios_eps *); 86210066Sjhbstatic void smbios_walk_table(uint8_t *, int, smbios_callback_t, 87210066Sjhb void *); 88241027Sjhbstatic void smbios_ipmi_info(struct smbios_structure_header *, void *); 89162562Sjhb 90155517Sambriskostatic void 91241027Sjhbsmbios_ipmi_info(struct smbios_structure_header *h, void *arg) 92155517Sambrisko{ 93210066Sjhb struct ipmi_get_info *info; 94210066Sjhb struct ipmi_entry *s; 95155517Sambrisko 96210066Sjhb if (h->type != 38 || h->length < 97210066Sjhb offsetof(struct ipmi_entry, interrupt_number)) 98210066Sjhb return; 99210066Sjhb s = (struct ipmi_entry *)h; 100210066Sjhb info = arg; 101155517Sambrisko bzero(info, sizeof(struct ipmi_get_info)); 102162562Sjhb switch (s->interface_type) { 103162562Sjhb case KCS_MODE: 104162562Sjhb case SMIC_MODE: 105162562Sjhb info->address = IPMI_BAR_ADDR(s->base_address) | 106162562Sjhb IPMI_BAM_ADDR_LSB(s->base_address_modifier); 107162562Sjhb info->io_mode = IPMI_BAR_MODE(s->base_address); 108162562Sjhb switch (IPMI_BAM_REG_SPACING(s->base_address_modifier)) { 109162562Sjhb case SPACING_8: 110162562Sjhb info->offset = 1; 111162562Sjhb break; 112162562Sjhb case SPACING_32: 113162562Sjhb info->offset = 4; 114162562Sjhb break; 115162562Sjhb case SPACING_16: 116162562Sjhb info->offset = 2; 117162562Sjhb break; 118162562Sjhb default: 119162562Sjhb printf("SMBIOS: Invalid register spacing\n"); 120162562Sjhb return; 121162562Sjhb } 122155517Sambrisko break; 123162562Sjhb case SSIF_MODE: 124162562Sjhb if ((s->base_address & 0xffffffffffffff00) != 0) { 125162562Sjhb printf("SMBIOS: Invalid SSIF SMBus address, using BMC I2C slave address instead\n"); 126188077Sjhb info->address = s->i2c_slave_address; 127162562Sjhb break; 128162562Sjhb } 129188077Sjhb info->address = IPMI_BAR_ADDR(s->base_address); 130155517Sambrisko break; 131162562Sjhb default: 132162562Sjhb return; 133155517Sambrisko } 134162562Sjhb if (s->length > offsetof(struct ipmi_entry, interrupt_number)) { 135162562Sjhb if (s->interrupt_number > 15) 136162562Sjhb printf("SMBIOS: Non-ISA IRQ %d for IPMI\n", 137162562Sjhb s->interrupt_number); 138162562Sjhb else 139162562Sjhb info->irq = s->interrupt_number; 140162562Sjhb } 141162562Sjhb info->iface_type = s->interface_type; 142155517Sambrisko} 143155517Sambrisko 144155517Sambriskostatic void 145210066Sjhbsmbios_walk_table(uint8_t *p, int entries, smbios_callback_t cb, void *arg) 146155517Sambrisko{ 147241027Sjhb struct smbios_structure_header *s; 148155517Sambrisko 149210066Sjhb while (entries--) { 150241027Sjhb s = (struct smbios_structure_header *)p; 151210066Sjhb cb(s, arg); 152155517Sambrisko 153155517Sambrisko /* 154210066Sjhb * Look for a double-nul after the end of the 155210066Sjhb * formatted area of this structure. 156155517Sambrisko */ 157210066Sjhb p += s->length; 158210604Sjhb while (!(p[0] == 0 && p[1] == 0)) 159210066Sjhb p++; 160155517Sambrisko 161210066Sjhb /* 162210066Sjhb * Skip over the double-nul to the start of the next 163210066Sjhb * structure. 164210066Sjhb */ 165210066Sjhb p += 2; 166155517Sambrisko } 167155517Sambrisko} 168155517Sambrisko 169162562Sjhb/* 170162562Sjhb * Walk the SMBIOS table looking for an IPMI (type 38) entry. If we find 171162562Sjhb * one, return the parsed data in the passed in ipmi_get_info structure and 172162562Sjhb * return true. If we don't find one, return false. 173162562Sjhb */ 174162562Sjhbstatic void 175162562Sjhbipmi_smbios_probe(struct ipmi_get_info *info) 176155517Sambrisko{ 177241027Sjhb struct smbios_eps *header; 178162562Sjhb void *table; 179155517Sambrisko u_int32_t addr; 180155517Sambrisko 181162562Sjhb bzero(info, sizeof(struct ipmi_get_info)); 182162562Sjhb 183162562Sjhb /* Find the SMBIOS table header. */ 184155517Sambrisko addr = bios_sigsearch(SMBIOS_START, SMBIOS_SIG, SMBIOS_LEN, 185155517Sambrisko SMBIOS_STEP, SMBIOS_OFF); 186162562Sjhb if (addr == 0) 187162562Sjhb return; 188155517Sambrisko 189162562Sjhb /* 190162562Sjhb * Map the header. We first map a fixed size to get the actual 191162562Sjhb * length and then map it a second time with the actual length so 192162562Sjhb * we can verify the checksum. 193162562Sjhb */ 194241027Sjhb header = pmap_mapbios(addr, sizeof(struct smbios_eps)); 195162562Sjhb table = pmap_mapbios(addr, header->length); 196241027Sjhb pmap_unmapbios((vm_offset_t)header, sizeof(struct smbios_eps)); 197162562Sjhb header = table; 198162562Sjhb if (smbios_cksum(header) != 0) { 199162562Sjhb pmap_unmapbios((vm_offset_t)header, header->length); 200162562Sjhb return; 201155517Sambrisko } 202155517Sambrisko 203162562Sjhb /* Now map the actual table and walk it looking for an IPMI entry. */ 204162562Sjhb table = pmap_mapbios(header->structure_table_address, 205162562Sjhb header->structure_table_length); 206210066Sjhb smbios_walk_table(table, header->number_structures, smbios_ipmi_info, 207162562Sjhb info); 208155517Sambrisko 209162562Sjhb /* Unmap everything. */ 210162562Sjhb pmap_unmapbios((vm_offset_t)table, header->structure_table_length); 211162562Sjhb pmap_unmapbios((vm_offset_t)header, header->length); 212155517Sambrisko} 213155517Sambrisko 214162562Sjhb/* 215162562Sjhb * Return the SMBIOS IPMI table entry info to the caller. If we haven't 216162562Sjhb * searched the IPMI table yet, search it. Otherwise, return a cached 217162562Sjhb * copy of the data. 218162562Sjhb */ 219155517Sambriskoint 220162562Sjhbipmi_smbios_identify(struct ipmi_get_info *info) 221155517Sambrisko{ 222155517Sambrisko 223162562Sjhb mtx_lock(&ipmi_info_mtx); 224162562Sjhb switch (ipmi_probed) { 225162562Sjhb case 0: 226162562Sjhb /* Need to probe the SMBIOS table. */ 227162562Sjhb ipmi_probed++; 228162562Sjhb mtx_unlock(&ipmi_info_mtx); 229162562Sjhb ipmi_smbios_probe(&ipmi_info); 230162562Sjhb mtx_lock(&ipmi_info_mtx); 231162562Sjhb ipmi_probed++; 232162562Sjhb wakeup(&ipmi_info); 233162562Sjhb break; 234162562Sjhb case 1: 235162562Sjhb /* Another thread is currently probing the table, so wait. */ 236162562Sjhb while (ipmi_probed == 1) 237162562Sjhb msleep(&ipmi_info, &ipmi_info_mtx, 0, "ipmi info", 0); 238162562Sjhb break; 239162562Sjhb default: 240162562Sjhb /* The cached data is available. */ 241162562Sjhb break; 242155517Sambrisko } 243155517Sambrisko 244162562Sjhb bcopy(&ipmi_info, info, sizeof(ipmi_info)); 245162562Sjhb mtx_unlock(&ipmi_info_mtx); 246155517Sambrisko 247162562Sjhb return (info->iface_type != 0); 248155517Sambrisko} 249155517Sambrisko 250155517Sambriskostatic int 251241027Sjhbsmbios_cksum(struct smbios_eps *e) 252155517Sambrisko{ 253155517Sambrisko u_int8_t *ptr; 254155517Sambrisko u_int8_t cksum; 255155517Sambrisko int i; 256155517Sambrisko 257155517Sambrisko ptr = (u_int8_t *)e; 258155517Sambrisko cksum = 0; 259155517Sambrisko for (i = 0; i < e->length; i++) { 260155517Sambrisko cksum += ptr[i]; 261155517Sambrisko } 262155517Sambrisko 263162562Sjhb return (cksum); 264155517Sambrisko} 265