blob: 5822762b8604bca4cddeddc3b847cb1df71461c7 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+
/*
* Implements boot devices, typically MMC/NVMe, used to hold the kernel
*
* Copyright 2018 Google LLC
*/
#define LOG_CATEGORY LOGC_VBOOT
#include <common.h>
#include <bootstage.h>
#include <dm.h>
#include <log.h>
#include <part.h>
#include <usb.h>
#include <cros/cros_common.h>
#include <cros/vboot.h>
#include <dm/device-internal.h>
/* Maximum number of devices we can support */
enum {
MAX_DISK_INFO = 10,
};
/**
* Add a device to info array if it matches the supplied disk_flags
*
* @name: Peripheral name
* @dev: Device to check
* @req_flags: Requested flags which must be present for each device
* @info: Output array of matching devices
* @return 0 if added, -ENOENT if not
*/
static int add_matching_device(struct udevice *dev, u32 req_flags,
VbDiskInfo *info)
{
struct blk_desc *bdev = dev_get_uclass_plat(dev);
u32 flags;
/* Ignore zero-length devices */
if (!bdev->lba) {
log_debug("Ignoring %s: zero-length\n", dev->name);
return -ENOENT;
}
/*
* Only add this storage device if the properties of req_flags is a
* subset of the properties of flags.
*/
flags = bdev->removable ? VB_DISK_FLAG_REMOVABLE : VB_DISK_FLAG_FIXED;
if ((flags & req_flags) != req_flags) {
log_debug("Ignoring %s: flags=%x, req_flags=%x\n", dev->name,
flags, req_flags);
return -ENOENT;
}
info->handle = (VbExDiskHandle_t)dev;
info->bytes_per_lba = bdev->blksz;
info->lba_count = bdev->lba;
info->flags = flags | VB_DISK_FLAG_EXTERNAL_GPT;
info->name = dev->name;
return 0;
}
/**
* boot_device_usb_start() - Start up USB and (re)scan the bus
*
* This sets vboot->usb_is_enumerated to true if the enumeration succeeds
*
* @vboot: vboot info
* @return 0 (always)
*/
static int boot_device_usb_start(struct vboot_info *vboot)
{
bool enumerate = true;
/*
* if the USB devices have already been enumerated, redo it
* only if something has been plugged on unplugged.
*/
if (vboot->usb_is_enumerated)
enumerate = usb_detect_change();
if (enumerate) {
/*
* We should stop all USB devices first. Otherwise we can't
* detect any new devices.
*/
usb_stop();
if (usb_init() >= 0)
vboot->usb_is_enumerated = true;
}
return 0;
}
vb2_error_t VbExDiskGetInfo(VbDiskInfo **infos_ptr, u32 *count_ptr,
u32 disk_flags)
{
struct vboot_info *vboot = vboot_get();
VbDiskInfo *infos;
u32 max_count; /* maximum number of devices to scan for */
u32 count = 0; /* number of matching devices found */
struct udevice *dev;
struct uclass *uc;
/* We return as many disk infos as possible */
max_count = MAX_DISK_INFO;
infos = calloc(max_count, sizeof(VbDiskInfo));
bootstage_start(BOOTSTAGE_ACCUM_VBOOT_BOOT_DEVICE_INFO,
"boot_device_info");
/* If we are looking for removable disks, scan USB */
if (IS_ENABLED(CONFIG_USB) && (disk_flags & VB_DISK_FLAG_REMOVABLE))
boot_device_usb_start(vboot);
/* Scan through all the interfaces looking for devices */
uclass_id_foreach_dev(UCLASS_BLK, dev, uc) {
int ret;
ret = device_probe(dev);
if (ret)
continue;
/* Now record the devices that have the required flags */
if (!add_matching_device(dev, disk_flags, infos + count))
count++;
if (count == max_count)
log_warning("Reached maximum device count\n");
}
if (count) {
*infos_ptr = infos;
*count_ptr = count;
} else {
*infos_ptr = NULL;
*count_ptr = 0;
free(infos);
}
bootstage_accum(BOOTSTAGE_ACCUM_VBOOT_BOOT_DEVICE_INFO);
log_debug("Found %u disks\n", count);
/* The operation itself succeeds, despite scan failure all about */
return VB2_SUCCESS;
}
vb2_error_t VbExDiskFreeInfo(VbDiskInfo *infos, VbExDiskHandle_t preserve_handle)
{
/* We do nothing for preserve_handle as we keep all the devices on */
free(infos);
return VB2_SUCCESS;
}
vb2_error_t VbExDiskRead(VbExDiskHandle_t handle, u64 lba_start, u64 lba_count,
void *buffer)
{
struct udevice *dev = (struct udevice *)handle;
struct blk_desc *bdev = dev_get_uclass_plat(dev);
u64 blks_read;
log_debug("lba_start=%x, lba_count=%x, buffer=%p\n", (uint)lba_start,
(uint)lba_count, buffer);
if (lba_start >= bdev->lba || lba_start + lba_count > bdev->lba)
return VB2_ERROR_UNKNOWN;
/* Keep track of the total time spent reading */
bootstage_start(BOOTSTAGE_ACCUM_VBOOT_BOOT_DEVICE_READ,
"boot_device_read");
blks_read = blk_dread(bdev, lba_start, lba_count, buffer);
bootstage_accum(BOOTSTAGE_ACCUM_VBOOT_BOOT_DEVICE_READ);
if (blks_read != lba_count)
return VB2_ERROR_UNKNOWN;
return VB2_SUCCESS;
}
vb2_error_t VbExDiskWrite(VbExDiskHandle_t handle, u64 lba_start,
u64 lba_count, const void *buffer)
{
struct udevice *dev = (struct udevice *)handle;
struct blk_desc *bdev = dev_get_uclass_plat(dev);
if (lba_start >= bdev->lba || lba_start + lba_count > bdev->lba)
return VB2_ERROR_UNKNOWN;
if (blk_dwrite(bdev, lba_start, lba_count, buffer) != lba_count)
return VB2_ERROR_UNKNOWN;
return VB2_SUCCESS;
}
/*
* Simple implementation of new streaming APIs. This turns them into calls to
* the sector-based disk read/write functions above. This will be replaced
* imminently with fancier streaming. In the meantime, this will allow the
* vboot_reference change which uses the streaming APIs to commit.
*/
/* The stub implementation assumes 512-byte disk sectors */
#define LBA_BYTES 512
/**
* struct disk_stream - struct for simulating a stream for sector-based disks
*
* @handle: Disk handle, as passed to VbExDiskRead()
* @sector: Next sector to read
* @sectors_left: Number of sectors left in partition
*/
struct disk_stream {
VbExDiskHandle_t handle;
u64 sector;
u64 sectors_left;
};
vb2_error_t VbExStreamOpen(VbExDiskHandle_t handle, u64 lba_start,
u64 lba_count, VbExStream_t *stream)
{
struct disk_stream *s;
*stream = NULL;
if (!handle)
return VB2_ERROR_UNKNOWN;
s = malloc(sizeof(*s));
if (!s)
return VB2_ERROR_UNKNOWN;
s->handle = handle;
s->sector = lba_start;
s->sectors_left = lba_count;
*stream = (void *)s;
return VB2_SUCCESS;
}
vb2_error_t VbExStreamRead(VbExStream_t stream, u32 bytes, void *buffer)
{
struct disk_stream *s = (struct disk_stream *)stream;
u64 sectors;
vb2_error_t rv;
if (!s)
return VB2_ERROR_UNKNOWN;
/* For now, require reads to be a multiple of the LBA size */
if (bytes % LBA_BYTES)
return VB2_ERROR_UNKNOWN;
/* Fail on overflow */
sectors = bytes / LBA_BYTES;
if (sectors > s->sectors_left)
return VB2_ERROR_UNKNOWN;
rv = VbExDiskRead(s->handle, s->sector, sectors, buffer);
if (rv != VB2_SUCCESS)
return rv;
s->sector += sectors;
s->sectors_left -= sectors;
return VB2_SUCCESS;
}
void VbExStreamClose(VbExStream_t stream)
{
struct disk_stream *s = (struct disk_stream *)stream;
free(s);
}