#include <sys/param.h>
#include <sys/reboot.h>
#include <sys/disklabel.h>
#include <lib/libz/zlib.h>
#include <isofs/cd9660/iso.h>
#include "libsa.h"
#include "disk.h"
#ifdef SOFTRAID
#include <dev/softraidvar.h>
#include <lib/libsa/softraid.h>
#include "softraid_amd64.h"
#endif
#include <efi.h>
extern int debug;
extern EFI_BOOT_SERVICES *BS;
#include "efidev.h"
#include "biosdev.h"
#define EFI_BLKSPERSEC(_ed) ((_ed)->blkio->Media->BlockSize / DEV_BSIZE)
#define EFI_SECTOBLK(_ed, _n) ((_n) * EFI_BLKSPERSEC(_ed))
struct efi_diskinfo {
EFI_BLOCK_IO *blkio;
UINT32 mediaid;
};
int bios_bootdev;
static EFI_STATUS
efid_io(int, efi_diskinfo_t, u_int, int, void *);
static int efid_diskio(int, struct diskinfo *, u_int, int, void *);
static int efi_getdisklabel_cd9660(efi_diskinfo_t, struct disklabel *);
static u_int findopenbsd(efi_diskinfo_t, const char **);
static u_int findopenbsd_gpt(efi_diskinfo_t, const char **);
static int gpt_chk_mbr(struct dos_partition *, u_int64_t);
void
efid_init(struct diskinfo *dip, void *handle)
{
EFI_BLOCK_IO *blkio = handle;
memset(dip, 0, sizeof(struct diskinfo));
dip->efi_info = alloc(sizeof(struct efi_diskinfo));
dip->efi_info->blkio = blkio;
dip->efi_info->mediaid = blkio->Media->MediaId;
dip->diskio = efid_diskio;
dip->strategy = efistrategy;
}
static EFI_STATUS
efid_io(int rw, efi_diskinfo_t ed, u_int off, int nsect, void *buf)
{
u_int blks, start, end;
EFI_PHYSICAL_ADDRESS addr;
EFI_STATUS status;
caddr_t data;
size_t size;
blks = EFI_BLKSPERSEC(ed);
if (blks == 0)
return (EFI_UNSUPPORTED);
start = off / blks;
end = (off + nsect + blks - 1) / blks;
size = (end - start) * ed->blkio->Media->BlockSize;
status = BS->AllocatePages(AllocateAnyPages, EfiLoaderData,
EFI_SIZE_TO_PAGES(size), &addr);
if (EFI_ERROR(status))
goto on_eio;
data = (caddr_t)(uintptr_t)addr;
switch (rw) {
case F_READ:
status = ed->blkio->ReadBlocks(ed->blkio, ed->mediaid, start,
size, data);
if (EFI_ERROR(status))
goto on_eio;
memcpy(buf, data + DEV_BSIZE * (off - start * blks),
DEV_BSIZE * nsect);
break;
case F_WRITE:
if (ed->blkio->Media->ReadOnly)
goto on_eio;
if (off % blks != 0 || nsect % blks != 0) {
status = ed->blkio->ReadBlocks(ed->blkio, ed->mediaid,
start, size, data);
if (EFI_ERROR(status))
goto on_eio;
}
memcpy(data + DEV_BSIZE * (off - start * blks), buf,
DEV_BSIZE * nsect);
status = ed->blkio->WriteBlocks(ed->blkio, ed->mediaid, start,
size, data);
if (EFI_ERROR(status))
goto on_eio;
break;
}
on_eio:
BS->FreePages(addr, EFI_SIZE_TO_PAGES(size));
return (status);
}
static int
efid_diskio(int rw, struct diskinfo *dip, u_int off, int nsect, void *buf)
{
EFI_STATUS status;
status = efid_io(rw, dip->efi_info, off, nsect, buf);
return ((EFI_ERROR(status))? -1 : 0);
}
static int
gpt_chk_mbr(struct dos_partition *dp, u_int64_t dsize)
{
struct dos_partition *dp2;
int efi, found, i;
u_int32_t psize;
found = efi = 0;
for (dp2=dp, i=0; i < NDOSPART; i++, dp2++) {
if (dp2->dp_typ == DOSPTYP_UNUSED)
continue;
found++;
if (dp2->dp_typ != DOSPTYP_EFI)
continue;
if (letoh32(dp2->dp_start) != GPTSECTOR)
continue;
psize = letoh32(dp2->dp_size);
if (psize <= (dsize - GPTSECTOR) || psize == UINT32_MAX)
efi++;
}
if (found == 1 && efi == 1)
return (0);
return (1);
}
static u_int
findopenbsd(efi_diskinfo_t ed, const char **err)
{
EFI_STATUS status;
struct dos_mbr mbr;
struct dos_partition *dp;
u_int mbroff = DOSBBSECTOR;
u_int mbr_eoff = DOSBBSECTOR;
int i, maxebr = DOS_MAXEBR, nextebr;
again:
if (!maxebr--) {
*err = "too many extended partitions";
return (-1);
}
bzero(&mbr, sizeof(mbr));
status = efid_io(F_READ, ed, mbroff, 1, &mbr);
if (EFI_ERROR(status)) {
*err = "Disk I/O Error";
return (-1);
}
nextebr = 0;
for (i = 0; i < NDOSPART; i++) {
dp = &mbr.dmbr_parts[i];
if (!dp->dp_size)
continue;
#ifdef BIOS_DEBUG
if (debug)
printf("found partition %u: "
"type %u (0x%x) offset %u (0x%x)\n",
(int)(dp - mbr.dmbr_parts),
dp->dp_typ, dp->dp_typ,
dp->dp_start, dp->dp_start);
#endif
if (dp->dp_typ == DOSPTYP_OPENBSD) {
if (dp->dp_start > (dp->dp_start + mbroff))
continue;
return (dp->dp_start + mbroff);
}
if (!nextebr && (dp->dp_typ == DOSPTYP_EXTEND ||
dp->dp_typ == DOSPTYP_EXTENDL)) {
nextebr = dp->dp_start + mbr_eoff;
if (nextebr < dp->dp_start)
nextebr = (u_int)-1;
if (mbr_eoff == DOSBBSECTOR)
mbr_eoff = dp->dp_start;
}
}
if (nextebr && nextebr != (u_int)-1) {
mbroff = nextebr;
goto again;
}
return (-1);
}
static u_int
findopenbsd_gpt(efi_diskinfo_t ed, const char **err)
{
EFI_STATUS status;
struct gpt_header gh;
int i, part, found;
uint64_t lba;
uint32_t orig_csum, new_csum;
uint32_t ghsize, ghpartsize, ghpartnum, ghpartspersec;
uint32_t gpsectors;
const char openbsd_uuid_code[] = GPT_UUID_OPENBSD;
struct gpt_partition gp;
static struct uuid *openbsd_uuid = NULL, openbsd_uuid_space;
static u_char buf[4096];
if (openbsd_uuid == NULL) {
memcpy(&openbsd_uuid_space, openbsd_uuid_code,
sizeof(openbsd_uuid_space));
openbsd_uuid_space.time_low =
betoh32(openbsd_uuid_space.time_low);
openbsd_uuid_space.time_mid =
betoh16(openbsd_uuid_space.time_mid);
openbsd_uuid_space.time_hi_and_version =
betoh16(openbsd_uuid_space.time_hi_and_version);
openbsd_uuid = &openbsd_uuid_space;
}
if (EFI_BLKSPERSEC(ed) > 8) {
*err = "disk sector > 4096 bytes\n";
return (-1);
}
lba = GPTSECTOR;
status = efid_io(F_READ, ed, EFI_SECTOBLK(ed, lba), EFI_BLKSPERSEC(ed),
buf);
if (EFI_ERROR(status)) {
*err = "Disk I/O Error";
return (-1);
}
memcpy(&gh, buf, sizeof(gh));
if (letoh64(gh.gh_sig) != GPTSIGNATURE) {
*err = "bad GPT signature\n";
return (-1);
}
if (letoh32(gh.gh_rev) != GPTREVISION) {
*err = "bad GPT revision\n";
return (-1);
}
ghsize = letoh32(gh.gh_size);
if (ghsize < GPTMINHDRSIZE || ghsize > sizeof(struct gpt_header)) {
*err = "bad GPT header size\n";
return (-1);
}
orig_csum = gh.gh_csum;
gh.gh_csum = 0;
new_csum = crc32(0, (unsigned char *)&gh, ghsize);
gh.gh_csum = orig_csum;
if (letoh32(orig_csum) != new_csum) {
*err = "bad GPT header checksum\n";
return (-1);
}
lba = letoh64(gh.gh_part_lba);
ghpartsize = letoh32(gh.gh_part_size);
ghpartspersec = ed->blkio->Media->BlockSize / ghpartsize;
ghpartnum = letoh32(gh.gh_part_num);
gpsectors = (ghpartnum + ghpartspersec - 1) / ghpartspersec;
new_csum = crc32(0L, Z_NULL, 0);
found = 0;
for (i = 0; i < gpsectors; i++, lba++) {
status = efid_io(F_READ, ed, EFI_SECTOBLK(ed, lba),
EFI_BLKSPERSEC(ed), buf);
if (EFI_ERROR(status)) {
*err = "Disk I/O Error";
return (-1);
}
for (part = 0; part < ghpartspersec; part++) {
if (ghpartnum == 0)
break;
new_csum = crc32(new_csum, buf + part * sizeof(gp),
sizeof(gp));
ghpartnum--;
if (found)
continue;
memcpy(&gp, buf + part * sizeof(gp), sizeof(gp));
if (memcmp(&gp.gp_type, openbsd_uuid,
sizeof(struct uuid)) == 0)
found = 1;
}
}
if (new_csum != letoh32(gh.gh_part_csum)) {
*err = "bad GPT entries checksum\n";
return (-1);
}
if (found) {
lba = letoh64(gp.gp_lba_start);
if (lba > UINT_MAX || EFI_SECTOBLK(ed, lba) > UINT_MAX) {
*err = "OpenBSD Partition LBA > 2**32 - 1";
return (-1);
}
return (u_int)lba;
}
return (-1);
}
const char *
efi_getdisklabel(efi_diskinfo_t ed, struct disklabel *label)
{
u_int start = 0;
uint8_t buf[DEV_BSIZE];
struct dos_partition dosparts[NDOSPART];
EFI_STATUS status;
const char *err = NULL;
int error;
memset(buf, 0, sizeof(buf));
status = efid_io(F_READ, ed, DOSBBSECTOR, 1, buf);
if (EFI_ERROR(status))
return ("Disk I/O Error");
if (buf[510] != 0x55 || buf[511] != 0xaa) {
if (efi_getdisklabel_cd9660(ed, label) == 0)
return (NULL);
return ("invalid MBR signature");
}
memcpy(dosparts, buf+DOSPARTOFF, sizeof(dosparts));
if (gpt_chk_mbr(dosparts, ed->blkio->Media->LastBlock + 1) == 0) {
start = findopenbsd_gpt(ed, &err);
if (start == (u_int)-1) {
if (err != NULL)
return (err);
return ("no OpenBSD GPT partition");
}
} else {
start = findopenbsd(ed, &err);
if (start == (u_int)-1) {
if (err != NULL)
return (err);
return "no OpenBSD MBR partition\n";
}
}
#ifdef BIOS_DEBUG
if (debug)
printf("loading disklabel @ %u\n", start + DOS_LABELSECTOR);
#endif
error = efid_io(F_READ, ed, EFI_SECTOBLK(ed, start) + DOS_LABELSECTOR,
1, buf);
if (error)
return "failed to read disklabel";
return (getdisklabel(buf, label));
}
static int
efi_getdisklabel_cd9660(efi_diskinfo_t ed, struct disklabel *label)
{
uint8_t buf[DEV_BSIZE];
EFI_STATUS status;
status = efid_io(F_READ, ed, 64, 1, buf);
if (EFI_ERROR(status))
return -1;
if (buf[0] != ISO_VD_PRIMARY || bcmp(buf + 1, ISO_STANDARD_ID, 5) != 0)
return -1;
label->d_secsize = 2048;
label->d_ntracks = 1;
label->d_nsectors = 100;
label->d_ncylinders = 1;
label->d_secpercyl = label->d_ntracks * label->d_nsectors;
strncpy(label->d_typename, "ATAPI CD-ROM", sizeof(label->d_typename));
label->d_type = DTYPE_ATAPI;
strncpy(label->d_packname, "fictitious", sizeof(label->d_packname));
DL_SETDSIZE(label, 100);
DL_SETPOFFSET(&label->d_partitions[0], 0);
DL_SETPSIZE(&label->d_partitions[0], 100);
label->d_partitions[0].p_fstype = FS_UNUSED;
DL_SETPOFFSET(&label->d_partitions[RAW_PART], 0);
DL_SETPSIZE(&label->d_partitions[RAW_PART], 100);
label->d_partitions[RAW_PART].p_fstype = FS_UNUSED;
label->d_npartitions = MAXPARTITIONS;
label->d_magic = DISKMAGIC;
label->d_magic2 = DISKMAGIC;
label->d_checksum = dkcksum(label);
return (0);
}
int
efiopen(struct open_file *f, ...)
{
#ifdef SOFTRAID
struct sr_boot_volume *bv;
#endif
register char *cp, **file;
dev_t maj, unit, part;
struct diskinfo *dip;
int biosdev, devlen;
#if 0
const char *st;
#endif
va_list ap;
char *dev;
va_start(ap, f);
cp = *(file = va_arg(ap, char **));
va_end(ap);
#ifdef EFI_DEBUG
if (debug)
printf("%s\n", cp);
#endif
f->f_devdata = NULL;
dev = cp;
if (cp[4] == ':')
devlen = 2;
else if (cp[5] == ':')
devlen = 3;
else
return ENOENT;
cp += devlen;
if ('0' <= *cp && *cp <= '9')
unit = *cp++ - '0';
else {
printf("Bad unit number\n");
return EUNIT;
}
part = DL_PARTNAME2NUM(*cp++);
if (part == -1) {
printf("Bad partition\n");
return EPART;
}
cp++;
if (*cp != 0)
*file = cp;
else
f->f_flags |= F_RAW;
#ifdef SOFTRAID
if (strncmp("sr", dev, 2) == 0) {
f->f_flags |= F_NOWRITE;
SLIST_FOREACH(bv, &sr_volumes, sbv_link)
if (bv->sbv_unit == unit)
break;
if (bv == NULL) {
printf("Unknown device: sr%d\n", unit);
return EADAPT;
}
if ((bv->sbv_level == 'C' || bv->sbv_level == 0x1C) &&
bv->sbv_keys == NULL) {
if (sr_crypto_unlock_volume(bv) != 0)
return EPERM;
}
if (bv->sbv_diskinfo == NULL) {
dip = alloc(sizeof(struct diskinfo));
bzero(dip, sizeof(*dip));
dip->diskio = efid_diskio;
dip->strategy = efistrategy;
bv->sbv_diskinfo = dip;
dip->sr_vol = bv;
dip->bios_info.flags |= BDI_BADLABEL;
}
dip = bv->sbv_diskinfo;
if (dip->bios_info.flags & BDI_BADLABEL) {
bv->sbv_part = 'c';
if (sr_getdisklabel(bv, &dip->disklabel))
return ERDLAB;
dip->bios_info.flags &= ~BDI_BADLABEL;
check_hibernate(dip);
}
bv->sbv_part = DL_PARTNUM2NAME(part);
bootdev_dip = dip;
f->f_devdata = dip;
return 0;
}
#endif
for (maj = 0; maj < nbdevs &&
strncmp(dev, bdevs[maj], devlen); maj++);
if (maj >= nbdevs) {
printf("Unknown device: ");
for (cp = *file; *cp != ':'; cp++)
putchar(*cp);
putchar('\n');
return EADAPT;
}
biosdev = unit;
switch (maj) {
case 0:
case 4:
case 17:
biosdev |= 0x80;
break;
case 2:
break;
case 6:
biosdev |= 0xe0;
break;
default:
return ENXIO;
}
dip = dklookup(biosdev);
if (dip == NULL)
return ENXIO;
bootdev_dip = dip;
{ dev_t bsd_dev;
bsd_dev = dip->bios_info.bsd_dev;
dip->bsddev = MAKEBOOTDEV(B_TYPE(bsd_dev), B_ADAPTOR(bsd_dev),
B_CONTROLLER(bsd_dev), unit, part);
dip->bootdev = MAKEBOOTDEV(B_TYPE(bsd_dev), B_ADAPTOR(bsd_dev),
B_CONTROLLER(bsd_dev), B_UNIT(bsd_dev), part);
}
#if 0
dip->bios_info.bsd_dev = dip->bootdev;
bootdev = dip->bootdev;
#endif
#ifdef EFI_DEBUG
if (debug) {
printf("BIOS geometry: heads=%u, s/t=%u; EDD=%d\n",
dip->bios_info.bios_heads, dip->bios_info.bios_sectors,
dip->bios_info.bios_edd);
}
#endif
#if 0
if (dip->bios_info.flags & BDI_BADLABEL) {
st = efi_getdisklabel(dip->efi_info, &dip->disklabel);
#ifdef EFI_DEBUG
if (debug && st)
printf("%s\n", st);
#endif
if (!st) {
dip->bios_info.flags &= ~BDI_BADLABEL;
dip->bios_info.flags |= BDI_GOODLABEL;
} else
return ERDLAB;
}
#endif
f->f_devdata = dip;
return 0;
}
int
efistrategy(void *devdata, int rw, daddr_t blk, size_t size, void *buf,
size_t *rsize)
{
struct diskinfo *dip = (struct diskinfo *)devdata;
u_int8_t error = 0;
size_t nsect;
#ifdef SOFTRAID
if (dip->sr_vol)
return sr_strategy(dip->sr_vol, rw, blk, size, buf, rsize);
#endif
nsect = (size + DEV_BSIZE - 1) / DEV_BSIZE;
blk += DL_SECTOBLK(&dip->disklabel,
dip->disklabel.d_partitions[B_PARTITION(dip->bsddev)].p_offset);
if (blk < 0)
error = EINVAL;
else
error = dip->diskio(rw, dip, blk, nsect, buf);
#ifdef EFI_DEBUG
if (debug) {
if (error != 0)
printf("=0x%x(%s)", error, error);
putchar('\n');
}
#endif
if (rsize != NULL)
*rsize = nsect * DEV_BSIZE;
return (error);
}
int
eficlose(struct open_file *f)
{
f->f_devdata = NULL;
return 0;
}
int
efiioctl(struct open_file *f, u_long cmd, void *data)
{
return 0;
}
void
efi_dump_diskinfo(void)
{
efi_diskinfo_t ed;
struct diskinfo *dip;
bios_diskinfo_t *bdi;
uint64_t siz;
const char *sizu;
printf("Disk\tBlkSiz\tIoAlign\tSize\tFlags\tChecksum\n");
TAILQ_FOREACH(dip, &disklist, list) {
bdi = &dip->bios_info;
ed = dip->efi_info;
siz = (ed->blkio->Media->LastBlock + 1) *
ed->blkio->Media->BlockSize;
siz /= 1024 * 1024;
if (siz < 10000)
sizu = "MB";
else {
siz /= 1024;
sizu = "GB";
}
printf("%cd%d\t%u\t%u\t%u%s\t0x%x\t0x%x\t%s\n",
(B_TYPE(bdi->bsd_dev) == 6)? 'c' : 'h',
(bdi->bios_number & 0x1f),
ed->blkio->Media->BlockSize,
ed->blkio->Media->IoAlign, (unsigned)siz, sizu,
bdi->flags, bdi->checksum,
(ed->blkio->Media->RemovableMedia)? "Removable" : "");
}
}
static EFI_GUID lip_guid = LOADED_IMAGE_PROTOCOL;
static EFI_GUID sfsp_guid = SIMPLE_FILE_SYSTEM_PROTOCOL;
static EFI_GUID fi_guid = EFI_FILE_INFO_ID;
int
esp_open(char *path, struct open_file *f)
{
extern EFI_HANDLE IH;
extern EFI_BOOT_SERVICES *BS;
EFI_LOADED_IMAGE *li = NULL;
EFI_FILE_IO_INTERFACE *ESPVolume;
CHAR16 *fname;
EFI_FILE_HANDLE VH, FH;
UINTN pathlen, i;
EFI_STATUS status;
if (strcmp("ESP", f->f_dev->dv_name) != 0)
return ENXIO;
if (IH == NULL)
return ENXIO;
status = BS->HandleProtocol(IH, &lip_guid, (void **)&li);
if (status != EFI_SUCCESS)
return ENXIO;
status = BS->HandleProtocol(li->DeviceHandle, &sfsp_guid,
(void *)&ESPVolume);
if (status != EFI_SUCCESS)
return ENXIO;
status = ESPVolume->OpenVolume(ESPVolume, &VH);
if (status != EFI_SUCCESS)
return ENOENT;
pathlen = strlen(path) + 1;
fname = alloc(pathlen * sizeof(*fname));
if (fname == NULL)
return ENOMEM;
for (i = 0; i < pathlen; i++)
fname[i] = path[i];
status = VH->Open(VH, &FH, fname, EFI_FILE_MODE_READ,
EFI_FILE_READ_ONLY | EFI_FILE_SYSTEM);
free(fname, pathlen * sizeof(*fname));
if (status != EFI_SUCCESS)
return ENOENT;
f->f_fsdata = FH;
return (0);
}
int
esp_close(struct open_file *f)
{
EFI_FILE_HANDLE FH = f->f_fsdata;
FH->Close(FH);
return 0;
}
int
esp_read(struct open_file *f, void *addr, size_t size, size_t *resid)
{
EFI_FILE_HANDLE FH = f->f_fsdata;
UINTN readlen = size;
EFI_STATUS status;
status = FH->Read(FH, &readlen, addr);
if (status != EFI_SUCCESS)
return (EIO);
*resid = size - readlen;
return (0);
}
int
esp_write(struct open_file *f, void *start, size_t size, size_t *resid)
{
return (EROFS);
}
off_t
esp_seek(struct open_file *f, off_t offset, int where)
{
EFI_FILE_HANDLE FH = f->f_fsdata;
UINT64 position;
EFI_STATUS status;
switch(where) {
case SEEK_CUR:
status = FH->GetPosition(FH, &position);
if (status != EFI_SUCCESS) {
errno = EIO;
return ((off_t)-1);
}
position += offset;
break;
case SEEK_SET:
position = offset;
break;
case SEEK_END:
position = 0xFFFFFFFFFFFFFFFF;
break;
default:
errno = EINVAL;
return ((off_t)-1);
}
status = FH->SetPosition(FH, position);
if (status != EFI_SUCCESS) {
errno = EIO;
return ((off_t)-1);
}
return (0);
}
int
esp_stat(struct open_file *f, struct stat *sb)
{
EFI_FILE_HANDLE FH = f->f_fsdata;
EFI_FILE_INFO fi;
EFI_FILE_INFO *fip = &fi;
UINTN filen = sizeof(fi);
EFI_STATUS status;
ssize_t rv = -1;
sb->st_mode = 0444;
sb->st_nlink = 1;
sb->st_uid = 0;
sb->st_gid = 0;
status = FH->GetInfo(FH, &fi_guid, &filen, fip);
switch (status) {
case EFI_SUCCESS:
sb->st_size = fip->FileSize;
return (0);
case EFI_BUFFER_TOO_SMALL:
break;
default:
return (EIO);
}
fip = alloc(filen);
if (fip == NULL)
return (ENOMEM);
status = FH->GetInfo(FH, &fi_guid, &filen, fip);
if (status != EFI_SUCCESS)
goto done;
sb->st_size = fip->FileSize;
done:
free(fip, filen);
return (rv);
}
int
esp_readdir(struct open_file *f, char *name)
{
return EOPNOTSUPP;
}
int
espopen(struct open_file *f, ...)
{
static const char espdev[] = "esp:";
static const char esp0adev[] = "esp0a:";
size_t esplen;
char *fname, **fnamep;
va_list ap;
va_start(ap, f);
fnamep = va_arg(ap, char **);
va_end(ap);
fname = *fnamep;
esplen = sizeof(espdev) - 1;
if (strncmp(fname, espdev, esplen) != 0) {
esplen = sizeof(esp0adev) - 1;
if (strncmp(fname, esp0adev, esplen) != 0)
return 1;
}
*fnamep = fname + esplen;
return 0;
}
int
espclose(struct open_file *f)
{
return 0;
}
int
espioctl(struct open_file *f, u_long cmd, void *data)
{
return EOPNOTSUPP;
}
int
espstrategy(void *devdata, int rw, daddr_t blk, size_t size, void *buf,
size_t *rsize)
{
return EOPNOTSUPP;
}