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/cdefs.h>
34__FBSDID("$FreeBSD$");
35
36#include <sys/types.h>
37#include <x86/mptable.h>
38
39#include <err.h>
40#include <fcntl.h>
41#include <inttypes.h>
42#include <paths.h>
43#include <stdlib.h>
44#include <string.h>
45#include <unistd.h>
46
47#define MPFPS_SIG "_MP_"
48#define MPCTH_SIG "PCMP"
49
50#define	PTOV(pa)	((off_t)(pa))
51
52static mpfps_t biosmptable_find_mpfps(void);
53static mpfps_t biosmptable_search_mpfps(off_t base, int length);
54static mpcth_t biosmptable_check_mpcth(off_t addr);
55
56static int memopen(void);
57static void memclose(void);
58
59int biosmptable_detect(void);
60
61int
62biosmptable_detect(void)
63{
64    mpfps_t mpfps;
65    mpcth_t mpcth;
66    char *entry_type_p;
67    proc_entry_ptr proc;
68    int ncpu, i;
69
70    if (!memopen())
71	return -1;		/* XXX 0? */
72    /* locate and validate the mpfps */
73    mpfps = biosmptable_find_mpfps();
74    mpcth = NULL;
75    if (mpfps == NULL) {
76	ncpu = 0;
77    } else if (mpfps->config_type != 0) {
78	/*
79	 * If thie config_type is nonzero then this is a default configuration
80	 * from Chapter 5 in the MP spec.  Report 2 cpus and 1 I/O APIC.
81	 */
82    	ncpu = 2;
83    } else {
84	ncpu = 0;
85	mpcth = biosmptable_check_mpcth(PTOV(mpfps->pap));
86	if (mpcth != NULL) {
87	    entry_type_p = (char *)(mpcth + 1);
88	    for (i = 0; i < mpcth->entry_count; i++) {
89		switch (*entry_type_p) {
90		case 0:
91		    entry_type_p += sizeof(struct PROCENTRY);
92		    proc = (proc_entry_ptr) entry_type_p;
93		    warnx("MPTable: Found CPU APIC ID %d %s",
94			proc->apic_id,
95			proc->cpu_flags & PROCENTRY_FLAG_EN ?
96				"enabled" : "disabled");
97		    if (proc->cpu_flags & PROCENTRY_FLAG_EN)
98			ncpu++;
99		    break;
100		case 1:
101		    entry_type_p += sizeof(struct BUSENTRY);
102		    break;
103		case 2:
104		    entry_type_p += sizeof(struct IOAPICENTRY);
105		    break;
106		case 3:
107		case 4:
108		    entry_type_p += sizeof(struct INTENTRY);
109		    break;
110		default:
111		    warnx("unknown mptable entry type (%d)", *entry_type_p);
112		    goto done;		/* XXX error return? */
113		}
114	    }
115	done:
116	    ;
117	}
118    }
119    memclose();
120    if (mpcth != NULL)
121	free(mpcth);
122    if (mpfps != NULL)
123	free(mpfps);
124
125    return ncpu;
126}
127
128static int pfd = -1;
129
130static int
131memopen(void)
132{
133    if (pfd < 0) {
134	pfd = open(_PATH_MEM, O_RDONLY);
135	if (pfd < 0)
136		warn("%s: cannot open", _PATH_MEM);
137    }
138    return pfd >= 0;
139}
140
141static void
142memclose(void)
143{
144    if (pfd >= 0) {
145	close(pfd);
146	pfd = -1;
147    }
148}
149
150static int
151memread(off_t addr, void* entry, size_t size)
152{
153    if ((size_t)pread(pfd, entry, size, addr) != size) {
154	warn("pread (%zu @ 0x%jx)", size, (intmax_t)addr);
155	return 0;
156    }
157    return 1;
158}
159
160
161/*
162 * Find the MP Floating Pointer Structure.  See the MP spec section 4.1.
163 */
164static mpfps_t
165biosmptable_find_mpfps(void)
166{
167    mpfps_t mpfps;
168    uint16_t addr;
169
170    /* EBDA is the 1 KB addressed by the 16 bit pointer at 0x40E. */
171    if (!memread(PTOV(0x40E), &addr, sizeof(addr)))
172	return (NULL);
173    mpfps = biosmptable_search_mpfps(PTOV(addr << 4), 0x400);
174    if (mpfps != NULL)
175	return (mpfps);
176
177    /* Check the BIOS. */
178    mpfps = biosmptable_search_mpfps(PTOV(0xf0000), 0x10000);
179    if (mpfps != NULL)
180	return (mpfps);
181
182    return (NULL);
183}
184
185static mpfps_t
186biosmptable_search_mpfps(off_t base, int length)
187{
188    mpfps_t mpfps;
189    u_int8_t *cp, sum;
190    int ofs, idx;
191
192    mpfps = malloc(sizeof(*mpfps));
193    if (mpfps == NULL) {
194	warnx("unable to malloc space for MP Floating Pointer Structure");
195	return (NULL);
196    }
197    /* search on 16-byte boundaries */
198    for (ofs = 0; ofs < length; ofs += 16) {
199	if (!memread(base + ofs, mpfps, sizeof(*mpfps)))
200	    break;
201
202	/* compare signature, validate checksum */
203	if (!strncmp(mpfps->signature, MPFPS_SIG, strlen(MPFPS_SIG))) {
204	    cp = (u_int8_t *)mpfps;
205	    sum = 0;
206	    /* mpfps is 16 bytes, or one "paragraph" */
207	    if (mpfps->length != 1) {
208	    	warnx("bad mpfps length (%d)", mpfps->length);
209		continue;
210	    }
211	    for (idx = 0; idx < mpfps->length * 16; idx++)
212		sum += *(cp + idx);
213	    if (sum != 0) {
214		warnx("bad mpfps checksum (%d)\n", sum);
215		continue;
216	    }
217	    return (mpfps);
218	}
219    }
220    free(mpfps);
221    return (NULL);
222}
223
224static mpcth_t
225biosmptable_check_mpcth(off_t addr)
226{
227    mpcth_t mpcth;
228    u_int8_t *cp, sum;
229    int idx, table_length;
230
231    /* mpcth must be in the first 1MB */
232    if ((u_int32_t)addr >= 1024 * 1024) {
233	warnx("bad mpcth address (0x%jx)\n", (intmax_t)addr);
234	return (NULL);
235    }
236
237    mpcth = malloc(sizeof(*mpcth));
238    if (mpcth == NULL) {
239	warnx("unable to malloc space for MP Configuration Table Header");
240	return (NULL);
241    }
242    if (!memread(addr, mpcth, sizeof(*mpcth)))
243	goto bad;
244    /* Compare signature and validate checksum. */
245    if (strncmp(mpcth->signature, MPCTH_SIG, strlen(MPCTH_SIG)) != 0) {
246        warnx("bad mpcth signature");
247	goto bad;
248    }
249    table_length = mpcth->base_table_length;
250    mpcth = realloc(mpcth, table_length);
251    if (mpcth == NULL) {
252	warnx("unable to realloc space for mpcth (len %u)", table_length);
253	return  (NULL);
254    }
255    if (!memread(addr, mpcth, table_length))
256	goto bad;
257    cp = (u_int8_t *)mpcth;
258    sum = 0;
259    for (idx = 0; idx < mpcth->base_table_length; idx++)
260	sum += *(cp + idx);
261    if (sum != 0) {
262	warnx("bad mpcth checksum (%d)", sum);
263	goto bad;
264    }
265
266    return mpcth;
267bad:
268    free(mpcth);
269    return (NULL);
270}
271