1/*-
2 * Copyright (c) 2005 Sandvine Incorporated.  All righs reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
17 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23 * SUCH DAMAGE.
24 *
25 * Author: Ed Maste <emaste@FreeBSD.org>
26 */
27
28/*
29 * This module detects Intel Multiprocessor spec info (mptable) and returns
30 * the number of cpu's identified.
31 */
32
33#include <sys/types.h>
34#include <x86/mptable.h>
35
36#include <err.h>
37#include <fcntl.h>
38#include <inttypes.h>
39#include <paths.h>
40#include <stdlib.h>
41#include <string.h>
42#include <unistd.h>
43
44#define MPFPS_SIG "_MP_"
45#define MPCTH_SIG "PCMP"
46
47#define	PTOV(pa)	((off_t)(pa))
48
49static mpfps_t biosmptable_find_mpfps(void);
50static mpfps_t biosmptable_search_mpfps(off_t base, int length);
51static mpcth_t biosmptable_check_mpcth(off_t addr);
52
53static int memopen(void);
54static void memclose(void);
55
56int biosmptable_detect(void);
57
58int
59biosmptable_detect(void)
60{
61    mpfps_t mpfps;
62    mpcth_t mpcth;
63    char *entry_type_p;
64    proc_entry_ptr proc;
65    int ncpu, i;
66
67    if (!memopen())
68	return -1;		/* XXX 0? */
69    /* locate and validate the mpfps */
70    mpfps = biosmptable_find_mpfps();
71    mpcth = NULL;
72    if (mpfps == NULL) {
73	ncpu = 0;
74    } else if (mpfps->config_type != 0) {
75	/*
76	 * If thie config_type is nonzero then this is a default configuration
77	 * from Chapter 5 in the MP spec.  Report 2 cpus and 1 I/O APIC.
78	 */
79    	ncpu = 2;
80    } else {
81	ncpu = 0;
82	mpcth = biosmptable_check_mpcth(PTOV(mpfps->pap));
83	if (mpcth != NULL) {
84	    entry_type_p = (char *)(mpcth + 1);
85	    for (i = 0; i < mpcth->entry_count; i++) {
86		switch (*entry_type_p) {
87		case 0:
88		    entry_type_p += sizeof(struct PROCENTRY);
89		    proc = (proc_entry_ptr) entry_type_p;
90		    warnx("MPTable: Found CPU APIC ID %d %s",
91			proc->apic_id,
92			proc->cpu_flags & PROCENTRY_FLAG_EN ?
93				"enabled" : "disabled");
94		    if (proc->cpu_flags & PROCENTRY_FLAG_EN)
95			ncpu++;
96		    break;
97		case 1:
98		    entry_type_p += sizeof(struct BUSENTRY);
99		    break;
100		case 2:
101		    entry_type_p += sizeof(struct IOAPICENTRY);
102		    break;
103		case 3:
104		case 4:
105		    entry_type_p += sizeof(struct INTENTRY);
106		    break;
107		default:
108		    warnx("unknown mptable entry type (%d)", *entry_type_p);
109		    goto done;		/* XXX error return? */
110		}
111	    }
112	done:
113	    ;
114	}
115    }
116    memclose();
117    if (mpcth != NULL)
118	free(mpcth);
119    if (mpfps != NULL)
120	free(mpfps);
121
122    return ncpu;
123}
124
125static int pfd = -1;
126
127static int
128memopen(void)
129{
130    if (pfd < 0) {
131	pfd = open(_PATH_MEM, O_RDONLY);
132	if (pfd < 0)
133		warn("%s: cannot open", _PATH_MEM);
134    }
135    return pfd >= 0;
136}
137
138static void
139memclose(void)
140{
141    if (pfd >= 0) {
142	close(pfd);
143	pfd = -1;
144    }
145}
146
147static int
148memread(off_t addr, void* entry, size_t size)
149{
150    if ((size_t)pread(pfd, entry, size, addr) != size) {
151	warn("pread (%zu @ 0x%jx)", size, (intmax_t)addr);
152	return 0;
153    }
154    return 1;
155}
156
157
158/*
159 * Find the MP Floating Pointer Structure.  See the MP spec section 4.1.
160 */
161static mpfps_t
162biosmptable_find_mpfps(void)
163{
164    mpfps_t mpfps;
165    uint16_t addr;
166
167    /* EBDA is the 1 KB addressed by the 16 bit pointer at 0x40E. */
168    if (!memread(PTOV(0x40E), &addr, sizeof(addr)))
169	return (NULL);
170    mpfps = biosmptable_search_mpfps(PTOV(addr << 4), 0x400);
171    if (mpfps != NULL)
172	return (mpfps);
173
174    /* Check the BIOS. */
175    mpfps = biosmptable_search_mpfps(PTOV(0xf0000), 0x10000);
176    if (mpfps != NULL)
177	return (mpfps);
178
179    return (NULL);
180}
181
182static mpfps_t
183biosmptable_search_mpfps(off_t base, int length)
184{
185    mpfps_t mpfps;
186    u_int8_t *cp, sum;
187    int ofs, idx;
188
189    mpfps = malloc(sizeof(*mpfps));
190    if (mpfps == NULL) {
191	warnx("unable to malloc space for MP Floating Pointer Structure");
192	return (NULL);
193    }
194    /* search on 16-byte boundaries */
195    for (ofs = 0; ofs < length; ofs += 16) {
196	if (!memread(base + ofs, mpfps, sizeof(*mpfps)))
197	    break;
198
199	/* compare signature, validate checksum */
200	if (!strncmp(mpfps->signature, MPFPS_SIG, strlen(MPFPS_SIG))) {
201	    cp = (u_int8_t *)mpfps;
202	    sum = 0;
203	    /* mpfps is 16 bytes, or one "paragraph" */
204	    if (mpfps->length != 1) {
205	    	warnx("bad mpfps length (%d)", mpfps->length);
206		continue;
207	    }
208	    for (idx = 0; idx < mpfps->length * 16; idx++)
209		sum += *(cp + idx);
210	    if (sum != 0) {
211		warnx("bad mpfps checksum (%d)\n", sum);
212		continue;
213	    }
214	    return (mpfps);
215	}
216    }
217    free(mpfps);
218    return (NULL);
219}
220
221static mpcth_t
222biosmptable_check_mpcth(off_t addr)
223{
224    mpcth_t mpcth;
225    u_int8_t *cp, sum;
226    int idx, table_length;
227
228    /* mpcth must be in the first 1MB */
229    if ((u_int32_t)addr >= 1024 * 1024) {
230	warnx("bad mpcth address (0x%jx)\n", (intmax_t)addr);
231	return (NULL);
232    }
233
234    mpcth = malloc(sizeof(*mpcth));
235    if (mpcth == NULL) {
236	warnx("unable to malloc space for MP Configuration Table Header");
237	return (NULL);
238    }
239    if (!memread(addr, mpcth, sizeof(*mpcth)))
240	goto bad;
241    /* Compare signature and validate checksum. */
242    if (strncmp(mpcth->signature, MPCTH_SIG, strlen(MPCTH_SIG)) != 0) {
243        warnx("bad mpcth signature");
244	goto bad;
245    }
246    table_length = mpcth->base_table_length;
247    mpcth = realloc(mpcth, table_length);
248    if (mpcth == NULL) {
249	warnx("unable to realloc space for mpcth (len %u)", table_length);
250	return  (NULL);
251    }
252    if (!memread(addr, mpcth, table_length))
253	goto bad;
254    cp = (u_int8_t *)mpcth;
255    sum = 0;
256    for (idx = 0; idx < mpcth->base_table_length; idx++)
257	sum += *(cp + idx);
258    if (sum != 0) {
259	warnx("bad mpcth checksum (%d)", sum);
260	goto bad;
261    }
262
263    return mpcth;
264bad:
265    free(mpcth);
266    return (NULL);
267}
268