1/*-
2 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3 *
4 * Copyright (c) 2006 IronPort Systems Inc. <ambrisko@ironport.com>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29#include <sys/cdefs.h>
30__FBSDID("$FreeBSD$");
31
32#include <sys/param.h>
33#include <sys/systm.h>
34#include <sys/bus.h>
35#include <sys/condvar.h>
36#include <sys/eventhandler.h>
37#include <sys/kernel.h>
38#include <sys/selinfo.h>
39
40#include <vm/vm.h>
41#include <vm/pmap.h>
42#include <machine/pc/bios.h>
43
44#ifdef LOCAL_MODULE
45#include <ipmi.h>
46#include <ipmivars.h>
47#else
48#include <sys/ipmi.h>
49#include <dev/ipmi/ipmivars.h>
50#endif
51
52struct ipmi_entry {
53	uint8_t		type;
54	uint8_t		length;
55	uint16_t	handle;
56	uint8_t		interface_type;
57	uint8_t		spec_revision;
58	uint8_t		i2c_slave_address;
59	uint8_t		NV_storage_device_address;
60	uint64_t	base_address;
61	uint8_t		base_address_modifier;
62	uint8_t		interrupt_number;
63};
64
65/* Fields in the base_address field of an IPMI entry. */
66#define	IPMI_BAR_MODE(ba)	((ba) & 0x0000000000000001)
67#define	IPMI_BAR_ADDR(ba)	((ba) & 0xfffffffffffffffe)
68
69/* Fields in the base_address_modifier field of an IPMI entry. */
70#define	IPMI_BAM_IRQ_TRIGGER	0x01
71#define	IPMI_BAM_IRQ_POLARITY	0x02
72#define	IPMI_BAM_IRQ_VALID	0x08
73#define	IPMI_BAM_ADDR_LSB(bam)	(((bam) & 0x10) >> 4)
74#define	IPMI_BAM_REG_SPACING(bam) (((bam) & 0xc0) >> 6)
75#define	SPACING_8		0x0
76#define	SPACING_32		0x1
77#define	SPACING_16		0x2
78
79typedef void (*smbios_callback_t)(struct smbios_structure_header *, void *);
80
81static struct ipmi_get_info ipmi_info;
82static int ipmi_probed;
83static struct mtx ipmi_info_mtx;
84MTX_SYSINIT(ipmi_info, &ipmi_info_mtx, "ipmi info", MTX_DEF);
85
86static void	ipmi_smbios_probe(struct ipmi_get_info *);
87static int	smbios_cksum(struct smbios_eps *);
88static void	smbios_walk_table(uint8_t *, int, smbios_callback_t,
89		    void *);
90static void	smbios_ipmi_info(struct smbios_structure_header *, void *);
91
92static void
93smbios_ipmi_info(struct smbios_structure_header *h, void *arg)
94{
95	struct ipmi_get_info *info;
96	struct ipmi_entry *s;
97
98	if (h->type != 38 || h->length <
99	    offsetof(struct ipmi_entry, interrupt_number))
100		return;
101	s = (struct ipmi_entry *)h;
102	info = arg;
103	bzero(info, sizeof(struct ipmi_get_info));
104	switch (s->interface_type) {
105	case KCS_MODE:
106	case SMIC_MODE:
107		info->address = IPMI_BAR_ADDR(s->base_address) |
108		    IPMI_BAM_ADDR_LSB(s->base_address_modifier);
109		info->io_mode = IPMI_BAR_MODE(s->base_address);
110		switch (IPMI_BAM_REG_SPACING(s->base_address_modifier)) {
111		case SPACING_8:
112			info->offset = 1;
113			break;
114		case SPACING_32:
115			info->offset = 4;
116			break;
117		case SPACING_16:
118			info->offset = 2;
119			break;
120		default:
121			printf("SMBIOS: Invalid register spacing\n");
122			return;
123		}
124		break;
125	case SSIF_MODE:
126		if ((s->base_address & 0xffffffffffffff00) != 0) {
127			printf("SMBIOS: Invalid SSIF SMBus address, using BMC I2C slave address instead\n");
128			info->address = s->i2c_slave_address;
129			break;
130		}
131		info->address = IPMI_BAR_ADDR(s->base_address);
132		break;
133	default:
134		return;
135	}
136	if (s->length > offsetof(struct ipmi_entry, interrupt_number)) {
137		if (s->interrupt_number > 15)
138			printf("SMBIOS: Non-ISA IRQ %d for IPMI\n",
139			    s->interrupt_number);
140		else
141			info->irq = s->interrupt_number;
142	}
143	info->iface_type = s->interface_type;
144}
145
146static void
147smbios_walk_table(uint8_t *p, int entries, smbios_callback_t cb, void *arg)
148{
149	struct smbios_structure_header *s;
150
151	while (entries--) {
152		s = (struct smbios_structure_header *)p;
153		cb(s, arg);
154
155		/*
156		 * Look for a double-nul after the end of the
157		 * formatted area of this structure.
158		 */
159		p += s->length;
160		while (!(p[0] == 0 && p[1] == 0))
161			p++;
162
163		/*
164		 * Skip over the double-nul to the start of the next
165		 * structure.
166		 */
167		p += 2;
168	}
169}
170
171/*
172 * Walk the SMBIOS table looking for an IPMI (type 38) entry.  If we find
173 * one, return the parsed data in the passed in ipmi_get_info structure and
174 * return true.  If we don't find one, return false.
175 */
176static void
177ipmi_smbios_probe(struct ipmi_get_info *info)
178{
179	struct smbios_eps *header;
180	void *table;
181	u_int32_t addr;
182
183	bzero(info, sizeof(struct ipmi_get_info));
184
185	/* Find the SMBIOS table header. */
186	addr = bios_sigsearch(SMBIOS_START, SMBIOS_SIG, SMBIOS_LEN,
187			      SMBIOS_STEP, SMBIOS_OFF);
188	if (addr == 0)
189		return;
190
191	/*
192	 * Map the header.  We first map a fixed size to get the actual
193	 * length and then map it a second time with the actual length so
194	 * we can verify the checksum.
195	 */
196	header = pmap_mapbios(addr, sizeof(struct smbios_eps));
197	table = pmap_mapbios(addr, header->length);
198	pmap_unmapbios((vm_offset_t)header, sizeof(struct smbios_eps));
199	header = table;
200	if (smbios_cksum(header) != 0) {
201		pmap_unmapbios((vm_offset_t)header, header->length);
202		return;
203	}
204
205	/* Now map the actual table and walk it looking for an IPMI entry. */
206	table = pmap_mapbios(header->structure_table_address,
207	    header->structure_table_length);
208	smbios_walk_table(table, header->number_structures, smbios_ipmi_info,
209	    info);
210
211	/* Unmap everything. */
212	pmap_unmapbios((vm_offset_t)table, header->structure_table_length);
213	pmap_unmapbios((vm_offset_t)header, header->length);
214}
215
216/*
217 * Return the SMBIOS IPMI table entry info to the caller.  If we haven't
218 * searched the IPMI table yet, search it.  Otherwise, return a cached
219 * copy of the data.
220 */
221int
222ipmi_smbios_identify(struct ipmi_get_info *info)
223{
224
225	mtx_lock(&ipmi_info_mtx);
226	switch (ipmi_probed) {
227	case 0:
228		/* Need to probe the SMBIOS table. */
229		ipmi_probed++;
230		mtx_unlock(&ipmi_info_mtx);
231		ipmi_smbios_probe(&ipmi_info);
232		mtx_lock(&ipmi_info_mtx);
233		ipmi_probed++;
234		wakeup(&ipmi_info);
235		break;
236	case 1:
237		/* Another thread is currently probing the table, so wait. */
238		while (ipmi_probed == 1)
239			msleep(&ipmi_info, &ipmi_info_mtx, 0, "ipmi info", 0);
240		break;
241	default:
242		/* The cached data is available. */
243		break;
244	}
245
246	bcopy(&ipmi_info, info, sizeof(ipmi_info));
247	mtx_unlock(&ipmi_info_mtx);
248
249	return (info->iface_type != 0);
250}
251
252static int
253smbios_cksum(struct smbios_eps *e)
254{
255	u_int8_t *ptr;
256	u_int8_t cksum;
257	int i;
258
259	ptr = (u_int8_t *)e;
260	cksum = 0;
261	for (i = 0; i < e->length; i++) {
262		cksum += ptr[i];
263	}
264
265	return (cksum);
266}
267