#include <sys/param.h>
#include <sys/systm.h>
#include <sys/conf.h>
#include <sys/endian.h>
#include <sys/diskslice.h>
#include <sys/diskmbr.h>
#include <sys/disk.h>
#include <sys/buf.h>
#include <sys/malloc.h>
#include <sys/syslog.h>
#include <sys/bus.h>
#include <sys/device.h>
#include <sys/gpt.h>
#include <sys/uuid.h>
#define MAX_GPT_ENTRIES 128
static void gpt_setslice(const char *sname, struct disk_info *info,
struct diskslice *sp, const struct gpt_ent *sent);
int
gptinit(cdev_t dev, struct disk_info *info, struct diskslices **sspp)
{
struct buf *bp1 = NULL;
struct buf *bp2 = NULL;
struct gpt_hdr *gpt;
struct gpt_ent *ent;
struct diskslice *sp;
struct diskslices *ssp;
cdev_t wdev;
int error;
uint32_t len;
uint32_t entries;
uint32_t entsz;
uint32_t crc;
uint32_t table_lba;
uint32_t table_blocks;
int i;
const char *dname;
error = 0;
wdev = dev;
dname = dev_dname(wdev);
bp1 = getpbuf_mem(NULL);
KKASSERT(info->d_media_blksize <= bp1->b_bufsize);
bp1->b_bio1.bio_offset = info->d_media_blksize;
bp1->b_bio1.bio_done = biodone_sync;
bp1->b_bio1.bio_flags |= BIO_SYNC;
bp1->b_bcount = info->d_media_blksize;
bp1->b_cmd = BUF_CMD_READ;
bp1->b_flags |= B_FAILONDIS;
dev_dstrategy(wdev, &bp1->b_bio1);
if (biowait(&bp1->b_bio1, "gptrd") != 0) {
kprintf("%s: reading GPT @ block 1: error %d\n",
dname, bp1->b_error);
error = EIO;
goto done;
}
gpt = (void *)bp1->b_data;
len = le32toh(gpt->hdr_size);
if (len < GPT_MIN_HDR_SIZE || len > info->d_media_blksize) {
kprintf("%s: Illegal GPT header size %d\n", dname, len);
error = EINVAL;
goto done;
}
crc = le32toh(gpt->hdr_crc_self);
gpt->hdr_crc_self = 0;
if (crc32(gpt, len) != crc) {
kprintf("%s: GPT CRC32 did not match\n", dname);
error = EINVAL;
goto done;
}
entries = le32toh(gpt->hdr_entries);
entsz = le32toh(gpt->hdr_entsz);
table_lba = le32toh(gpt->hdr_lba_table);
table_blocks = (entries * entsz + info->d_media_blksize - 1) /
info->d_media_blksize;
if (entries < 1 || entries > MAX_GPT_ENTRIES ||
entsz < sizeof(struct gpt_ent) || (entsz & 7) ||
entsz > MAXBSIZE / entries ||
table_lba < 2 || table_lba + table_blocks > info->d_media_blocks)
{
kprintf("%s: GPT partition table is out of bounds\n", dname);
error = EINVAL;
goto done;
}
bp2 = getpbuf_mem(NULL);
KKASSERT((int)(table_blocks * info->d_media_blksize) <= bp2->b_bufsize);
bp2->b_bio1.bio_offset = (off_t)table_lba * info->d_media_blksize;
bp2->b_bio1.bio_done = biodone_sync;
bp2->b_bio1.bio_flags |= BIO_SYNC;
bp2->b_bcount = table_blocks * info->d_media_blksize;
bp2->b_cmd = BUF_CMD_READ;
bp2->b_flags |= B_FAILONDIS;
dev_dstrategy(wdev, &bp2->b_bio1);
if (biowait(&bp2->b_bio1, "gptrd") != 0) {
kprintf("%s: reading GPT partition table @ %lld: error %d\n",
dname,
(long long)bp2->b_bio1.bio_offset,
bp2->b_error);
error = EIO;
goto done;
}
kfree(*sspp, M_DEVBUF);
ssp = *sspp = dsmakeslicestruct(BASE_SLICE + MAX_GPT_ENTRIES, info);
for (i = 0; i < (int)entries && i < MAX_GPT_ENTRIES; ++i) {
struct gpt_ent sent;
char partname[2];
char *sname;
size_t j;
ent = (void *)((char *)bp2->b_data + i * entsz);
le_uuid_dec(&ent->ent_type, &sent.ent_type);
le_uuid_dec(&ent->ent_uuid, &sent.ent_uuid);
sent.ent_lba_start = le64toh(ent->ent_lba_start);
sent.ent_lba_end = le64toh(ent->ent_lba_end);
sent.ent_attr = le64toh(ent->ent_attr);
for (j = 0; j < NELEM(ent->ent_name); ++j)
sent.ent_name[j] = le16toh(ent->ent_name[j]);
if (i == 0)
sp = &ssp->dss_slices[COMPATIBILITY_SLICE];
else
sp = &ssp->dss_slices[BASE_SLICE+i-1];
sname = dsname(dev, dkunit(dev), WHOLE_DISK_SLICE,
WHOLE_SLICE_PART, partname);
if (kuuid_is_nil(&sent.ent_type))
continue;
if (sent.ent_lba_start < table_lba + table_blocks ||
sent.ent_lba_end >= info->d_media_blocks ||
sent.ent_lba_start > sent.ent_lba_end) {
kprintf("%s part %d: bad lba_start=%ju or "
"lba_end=%ju\n",
sname, i, (uintmax_t)sent.ent_lba_start,
(uintmax_t)sent.ent_lba_end);
} else {
gpt_setslice(sname, info, sp, &sent);
}
}
ssp->dss_nslices = BASE_SLICE + i;
done:
if (bp1) {
bp1->b_flags |= B_INVAL | B_AGE;
relpbuf(bp1, NULL);
}
if (bp2) {
bp2->b_flags |= B_INVAL | B_AGE;
relpbuf(bp2, NULL);
}
return (error);
}
static void
gpt_setslice(const char *sname, struct disk_info *info, struct diskslice *sp,
const struct gpt_ent *sent)
{
static const struct {
struct uuid uuid;
int type;
} slice_types[] = {
{ GPT_ENT_TYPE_DRAGONFLY_LABEL32, DOSPTYP_DFLYBSD },
{ GPT_ENT_TYPE_DRAGONFLY_LABEL64, DOSPTYP_DFLYBSD },
{ GPT_ENT_TYPE_DRAGONFLY_LEGACY, DOSPTYP_DFLYBSD },
{ GPT_ENT_TYPE_FREEBSD, DOSPTYP_386BSD },
};
size_t i;
for (i = 0; i < NELEM(slice_types); i++) {
if (kuuid_compare(&slice_types[i].uuid, &sent->ent_type) == 0) {
sp->ds_type = slice_types[i].type;
break;
}
}
sp->ds_offset = sent->ent_lba_start;
sp->ds_size = sent->ent_lba_end + 1 - sent->ent_lba_start;
sp->ds_type_uuid = sent->ent_type;
sp->ds_stor_uuid = sent->ent_uuid;
sp->ds_reserved = 0;
}