/* $NetBSD: efidisk.c,v 1.11 2024/01/06 21:26:43 mlelstv Exp $ */
/*-
* Copyright (c) 2016 Kimihiro Nonaka <
[email protected]>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#define FSTYPENAMES /* for sys/disklabel.h */
#include "efiboot.h"
#include <sys/param.h> /* for howmany, required by <dev/raidframe/raidframevar.h> */
#include <sys/disklabel.h>
#include <sys/disklabel_gpt.h>
#include "biosdisk.h"
#include "biosdisk_ll.h"
#include "devopen.h"
#include "efidisk.h"
#include "bootinfo.h"
static struct efidiskinfo_lh efi_disklist;
static int nefidisks;
static struct btinfo_biosgeom *bibg;
static size_t bibg_len;
#define MAXDEVNAME 39 /* "NAME=" + 34 char part_name */
#include <dev/raidframe/raidframevar.h>
#define RF_COMPONENT_INFO_OFFSET 16384 /* from sys/dev/raidframe/rf_netbsdkintf.c */
#define RF_COMPONENT_LABEL_VERSION 2 /* from <dev/raidframe/rf_raid.h> */
#define RAIDFRAME_NDEV 16 /* abitrary limit to 15 raidframe devices */
struct efi_raidframe {
int last_unit;
int serial;
const struct efidiskinfo *edi;
int parent_part;
char parent_name[MAXDEVNAME + 1];
daddr_t offset;
daddr_t size;
};
static void
dealloc_biosdisk_part(struct biosdisk_partition *part, int nparts)
{
int i;
for (i = 0; i < nparts; i++) {
if (part[i].part_name != NULL) {
dealloc(part[i].part_name, BIOSDISK_PART_NAME_LEN);
part[i].part_name = NULL;
}
}
dealloc(part, sizeof(*part) * nparts);
return;
}
void
efi_disk_probe(void)
{
EFI_STATUS status;
UINTN i, nhandles;
EFI_HANDLE *handles;
EFI_BLOCK_IO *bio;
EFI_BLOCK_IO_MEDIA *media;
EFI_DEVICE_PATH *dp;
struct efidiskinfo *edi;
int dev, depth = -1;
TAILQ_INIT(&efi_disklist);
status = LibLocateHandle(ByProtocol, &BlockIoProtocol, NULL,
&nhandles, &handles);
if (EFI_ERROR(status))
return;
if (efi_bootdp != NULL)
depth = efi_device_path_depth(efi_bootdp, MEDIA_DEVICE_PATH);
/*
* U-Boot incorrectly represents devices with a single
* MEDIA_DEVICE_PATH component. In that case include that
* component into the matching, otherwise we'll blindly select
* the first device.
*/
if (depth == 0)
depth = 1;
for (i = 0; i < nhandles; i++) {
status = uefi_call_wrapper(BS->HandleProtocol, 3, handles[i],
&BlockIoProtocol, (void **)&bio);
if (EFI_ERROR(status))
continue;
media = bio->Media;
if (media->LogicalPartition || !media->MediaPresent)
continue;
edi = alloc(sizeof(struct efidiskinfo));
memset(edi, 0, sizeof(*edi));
edi->type = BIOSDISK_TYPE_HD;
edi->bio = bio;
edi->media_id = media->MediaId;
if (efi_bootdp != NULL && depth > 0) {
status = uefi_call_wrapper(BS->HandleProtocol, 3,
handles[i], &DevicePathProtocol, (void **)&dp);
if (EFI_ERROR(status))
goto next;
if (efi_device_path_ncmp(efi_bootdp, dp, depth) == 0) {
edi->bootdev = true;
TAILQ_INSERT_HEAD(&efi_disklist, edi,
list);
continue;
}
}
next:
TAILQ_INSERT_TAIL(&efi_disklist, edi, list);
}
FreePool(handles);
if (efi_bootdp_type == BOOT_DEVICE_TYPE_CD) {
edi = TAILQ_FIRST(&efi_disklist);
if (edi != NULL && edi->bootdev) {
edi->type = BIOSDISK_TYPE_CD;
TAILQ_REMOVE(&efi_disklist, edi, list);
TAILQ_INSERT_TAIL(&efi_disklist, edi, list);
}
}
dev = 0x80;
TAILQ_FOREACH(edi, &efi_disklist, list) {
edi->dev = dev++;
if (edi->type == BIOSDISK_TYPE_HD)
nefidisks++;
if (edi->bootdev)
boot_biosdev = edi->dev;
}
bibg_len = sizeof(*bibg) + nefidisks * sizeof(struct bi_biosgeom_entry);
bibg = alloc(bibg_len);
if (bibg == NULL)
return;
bibg->num = nefidisks;
i = 0;
TAILQ_FOREACH(edi, &efi_disklist, list) {
if (edi->type == BIOSDISK_TYPE_HD) {
memset(&bibg->disk[i], 0, sizeof(bibg->disk[i]));
bibg->disk[i].dev = edi->dev;
bibg->disk[i].flags = BI_GEOM_INVALID;
}
++i;
}
}
static void
efi_raidframe_probe(struct efi_raidframe *raidframe, int *raidframe_count,
const struct efidiskinfo *edi,
struct biosdisk_partition *part, int parent_part)
{
int i = *raidframe_count;
struct RF_ComponentLabel_s label;
if (i + 1 > RAIDFRAME_NDEV)
return;
if (biosdisk_read_raidframe(edi->dev, part->offset, &label) != 0)
return;
if (label.version != RF_COMPONENT_LABEL_VERSION)
return;
raidframe[i].last_unit = label.last_unit;
raidframe[i].serial = label.serial_number;
raidframe[i].edi = edi;
raidframe[i].parent_part = parent_part;
if (part->part_name)
strlcpy(raidframe[i].parent_name, part->part_name, MAXDEVNAME);
else
raidframe[i].parent_name[0] = '\0';
raidframe[i].offset = part->offset;
raidframe[i].size = label.__numBlocks;
(*raidframe_count)++;
return;
}
void
efi_disk_show(void)
{
const struct efidiskinfo *edi;
struct efi_raidframe raidframe[RAIDFRAME_NDEV];
int raidframe_count = 0;
EFI_BLOCK_IO_MEDIA *media;
struct biosdisk_partition *part;
uint64_t size;
int i, j, nparts;
bool first;
TAILQ_FOREACH(edi, &efi_disklist, list) {
media = edi->bio->Media;
first = true;
printf("disk ");
switch (edi->type) {
case BIOSDISK_TYPE_CD:
printf("cd0");
printf(" mediaId %u", media->MediaId);
if (edi->media_id != media->MediaId)
printf("(%u)", edi->media_id);
printf("\n");
printf(" cd0a\n");
break;
case BIOSDISK_TYPE_HD:
printf("hd%d", edi->dev & 0x7f);
printf(" mediaId %u", media->MediaId);
if (edi->media_id != media->MediaId)
printf("(%u)", edi->media_id);
printf(" size ");
size = (media->LastBlock + 1) * media->BlockSize;
if (size >= (10ULL * 1024 * 1024 * 1024))
printf("%"PRIu64" GB", size / (1024 * 1024 * 1024));
else
printf("%"PRIu64" MB", size / (1024 * 1024));
printf("\n");
break;
}
if (edi->type != BIOSDISK_TYPE_HD)
continue;
if (biosdisk_readpartition(edi->dev, 0, 0, &part, &nparts))
continue;
for (i = 0; i < nparts; i++) {
if (part[i].size == 0)
continue;
if (part[i].fstype == FS_UNUSED)
continue;
if (part[i].fstype == FS_RAID) {
efi_raidframe_probe(raidframe, &raidframe_count,
edi, &part[i], i);
}
if (first) {
printf(" ");
first = false;
}
if (part[i].part_name && part[i].part_name[0])
printf(" NAME=%s(", part[i].part_name);
else
printf(" hd%d%c(", edi->dev & 0x7f, i + 'a');
if (part[i].guid != NULL)
printf("%s", part[i].guid->name);
else if (part[i].fstype < FSMAXTYPES)
printf("%s", fstypenames[part[i].fstype]);
else
printf("%d", part[i].fstype);
printf(")");
}
if (!first)
printf("\n");
dealloc_biosdisk_part(part, nparts);
}
for (i = 0; i < raidframe_count; i++) {
size_t secsize = raidframe[i].edi->bio->Media->BlockSize;
printf("raidframe raid%d serial %d in ",
raidframe[i].last_unit, raidframe[i].serial);
if (raidframe[i].parent_name[0])
printf("NAME=%s size ", raidframe[i].parent_name);
else
printf("hd%d%c size ",
raidframe[i].edi->dev & 0x7f,
raidframe[i].parent_part + 'a');
if (raidframe[i].size >= (10ULL * 1024 * 1024 * 1024 / secsize))
printf("%"PRIu64" GB",
raidframe[i].size / (1024 * 1024 * 1024 / secsize));
else
printf("%"PRIu64" MB",
raidframe[i].size / (1024 * 1024 / secsize));
printf("\n");
if (biosdisk_readpartition(raidframe[i].edi->dev,
raidframe[i].offset + RF_PROTECTED_SECTORS,
raidframe[i].size,
&part, &nparts))
continue;
first = 1;
for (j = 0; j < nparts; j++) {
bool bootme = part[j].attr & GPT_ENT_ATTR_BOOTME;
if (part[j].size == 0)
continue;
if (part[j].fstype == FS_UNUSED)
continue;
if (part[j].fstype == FS_RAID) /* raid in raid? */
continue;
if (first) {
printf(" ");
first = 0;
}
if (part[j].part_name && part[j].part_name[0])
printf(" NAME=%s(", part[j].part_name);
else
printf(" raid%d%c(",
raidframe[i].last_unit, j + 'a');
if (part[j].guid != NULL)
printf("%s", part[j].guid->name);
else if (part[j].fstype < FSMAXTYPES)
printf("%s",
fstypenames[part[j].fstype]);
else
printf("%d", part[j].fstype);
printf("%s)", bootme ? ", bootme" : "");
}
if (first == 0)
printf("\n");
dealloc_biosdisk_part(part, nparts);
}
}
const struct efidiskinfo *
efidisk_getinfo(int dev)
{
const struct efidiskinfo *edi;
TAILQ_FOREACH(edi, &efi_disklist, list) {
if (dev == edi->dev)
return edi;
}
return NULL;
}
/*
* Return the number of hard disk drives.
*/
int
get_harddrives(void)
{
return nefidisks;
}
int
efidisk_get_efi_system_partition(int dev, int *partition)
{
extern const struct uuid GET_efi;
const struct efidiskinfo *edi;
struct biosdisk_partition *part;
int i, nparts;
edi = efidisk_getinfo(dev);
if (edi == NULL)
return ENXIO;
if (edi->type != BIOSDISK_TYPE_HD)
return ENOTSUP;
if (biosdisk_readpartition(edi->dev, 0, 0, &part, &nparts))
return EIO;
for (i = 0; i < nparts; i++) {
if (part[i].size == 0)
continue;
if (part[i].fstype == FS_UNUSED)
continue;
if (guid_is_equal(part[i].guid->guid, &GET_efi))
break;
}
dealloc_biosdisk_part(part, nparts);
if (i == nparts)
return ENOENT;
*partition = i;
return 0;
}
void
efidisk_getbiosgeom()
{
BI_ADD(bibg, BTINFO_BIOSGEOM, bibg_len);
}