#include <sys/types.h>
#include <x86/mptable.h>
#include <err.h>
#include <fcntl.h>
#include <inttypes.h>
#include <paths.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define MPFPS_SIG "_MP_"
#define MPCTH_SIG "PCMP"
#define PTOV(pa) ((off_t)(pa))
static mpfps_t biosmptable_find_mpfps(void);
static mpfps_t biosmptable_search_mpfps(off_t base, int length);
static mpcth_t biosmptable_check_mpcth(off_t addr);
static int memopen(void);
static void memclose(void);
int biosmptable_detect(void);
int
biosmptable_detect(void)
{
mpfps_t mpfps;
mpcth_t mpcth;
char *entry_type_p;
proc_entry_ptr proc;
int ncpu, i;
if (!memopen())
return -1;
mpfps = biosmptable_find_mpfps();
mpcth = NULL;
if (mpfps == NULL) {
ncpu = 0;
} else if (mpfps->config_type != 0) {
ncpu = 2;
} else {
ncpu = 0;
mpcth = biosmptable_check_mpcth(PTOV(mpfps->pap));
if (mpcth != NULL) {
entry_type_p = (char *)(mpcth + 1);
for (i = 0; i < mpcth->entry_count; i++) {
switch (*entry_type_p) {
case 0:
entry_type_p += sizeof(struct PROCENTRY);
proc = (proc_entry_ptr) entry_type_p;
warnx("MPTable: Found CPU APIC ID %d %s",
proc->apic_id,
proc->cpu_flags & PROCENTRY_FLAG_EN ?
"enabled" : "disabled");
if (proc->cpu_flags & PROCENTRY_FLAG_EN)
ncpu++;
break;
case 1:
entry_type_p += sizeof(struct BUSENTRY);
break;
case 2:
entry_type_p += sizeof(struct IOAPICENTRY);
break;
case 3:
case 4:
entry_type_p += sizeof(struct INTENTRY);
break;
default:
warnx("unknown mptable entry type (%d)", *entry_type_p);
goto done;
}
}
done:
;
}
}
memclose();
if (mpcth != NULL)
free(mpcth);
if (mpfps != NULL)
free(mpfps);
return ncpu;
}
static int pfd = -1;
static int
memopen(void)
{
if (pfd < 0) {
pfd = open(_PATH_MEM, O_RDONLY);
if (pfd < 0)
warn("%s: cannot open", _PATH_MEM);
}
return pfd >= 0;
}
static void
memclose(void)
{
if (pfd >= 0) {
close(pfd);
pfd = -1;
}
}
static int
memread(off_t addr, void* entry, size_t size)
{
if ((size_t)pread(pfd, entry, size, addr) != size) {
warn("pread (%zu @ 0x%jx)", size, (intmax_t)addr);
return 0;
}
return 1;
}
static mpfps_t
biosmptable_find_mpfps(void)
{
mpfps_t mpfps;
uint16_t addr;
if (!memread(PTOV(0x40E), &addr, sizeof(addr)))
return (NULL);
mpfps = biosmptable_search_mpfps(PTOV(addr << 4), 0x400);
if (mpfps != NULL)
return (mpfps);
mpfps = biosmptable_search_mpfps(PTOV(0xf0000), 0x10000);
if (mpfps != NULL)
return (mpfps);
return (NULL);
}
static mpfps_t
biosmptable_search_mpfps(off_t base, int length)
{
mpfps_t mpfps;
u_int8_t *cp, sum;
int ofs, idx;
mpfps = malloc(sizeof(*mpfps));
if (mpfps == NULL) {
warnx("unable to malloc space for MP Floating Pointer Structure");
return (NULL);
}
for (ofs = 0; ofs < length; ofs += 16) {
if (!memread(base + ofs, mpfps, sizeof(*mpfps)))
break;
if (!strncmp(mpfps->signature, MPFPS_SIG, strlen(MPFPS_SIG))) {
cp = (u_int8_t *)mpfps;
sum = 0;
if (mpfps->length != 1) {
warnx("bad mpfps length (%d)", mpfps->length);
continue;
}
for (idx = 0; idx < mpfps->length * 16; idx++)
sum += *(cp + idx);
if (sum != 0) {
warnx("bad mpfps checksum (%d)\n", sum);
continue;
}
return (mpfps);
}
}
free(mpfps);
return (NULL);
}
static mpcth_t
biosmptable_check_mpcth(off_t addr)
{
mpcth_t mpcth;
u_int8_t *cp, sum;
int idx, table_length;
if ((u_int32_t)addr >= 1024 * 1024) {
warnx("bad mpcth address (0x%jx)\n", (intmax_t)addr);
return (NULL);
}
mpcth = malloc(sizeof(*mpcth));
if (mpcth == NULL) {
warnx("unable to malloc space for MP Configuration Table Header");
return (NULL);
}
if (!memread(addr, mpcth, sizeof(*mpcth)))
goto bad;
if (strncmp(mpcth->signature, MPCTH_SIG, strlen(MPCTH_SIG)) != 0) {
warnx("bad mpcth signature");
goto bad;
}
table_length = mpcth->base_table_length;
mpcth = realloc(mpcth, table_length);
if (mpcth == NULL) {
warnx("unable to realloc space for mpcth (len %u)", table_length);
return (NULL);
}
if (!memread(addr, mpcth, table_length))
goto bad;
cp = (u_int8_t *)mpcth;
sum = 0;
for (idx = 0; idx < mpcth->base_table_length; idx++)
sum += *(cp + idx);
if (sum != 0) {
warnx("bad mpcth checksum (%d)", sum);
goto bad;
}
return mpcth;
bad:
free(mpcth);
return (NULL);
}