diff options
Diffstat (limited to 'roms/u-boot/drivers/block')
-rw-r--r-- | roms/u-boot/drivers/block/Kconfig | 73 | ||||
-rw-r--r-- | roms/u-boot/drivers/block/Makefile | 16 | ||||
-rw-r--r-- | roms/u-boot/drivers/block/blk-uclass.c | 664 | ||||
-rw-r--r-- | roms/u-boot/drivers/block/blk_legacy.c | 266 | ||||
-rw-r--r-- | roms/u-boot/drivers/block/blkcache.c | 190 | ||||
-rw-r--r-- | roms/u-boot/drivers/block/ide.c | 1182 | ||||
-rw-r--r-- | roms/u-boot/drivers/block/sandbox.c | 265 |
7 files changed, 2656 insertions, 0 deletions
diff --git a/roms/u-boot/drivers/block/Kconfig b/roms/u-boot/drivers/block/Kconfig new file mode 100644 index 000000000..1e6dad869 --- /dev/null +++ b/roms/u-boot/drivers/block/Kconfig @@ -0,0 +1,73 @@ +config BLK + bool "Support block devices" + depends on DM + default y if DM_MMC + help + Enable support for block devices, such as SCSI, MMC and USB + flash sticks. These provide a block-level interface which permits + reading, writing and (in some cases) erasing blocks. Block + devices often have a partition table which allows the device to + be partitioned into several areas, called 'partitions' in U-Boot. + A filesystem can be placed in each partition. + +config HAVE_BLOCK_DEVICE + bool "Enable Legacy Block Device" + help + Some devices require block support whether or not DM is enabled + +config SPL_BLK + bool "Support block devices in SPL" + depends on SPL_DM && BLK + default y + help + Enable support for block devices, such as SCSI, MMC and USB + flash sticks. These provide a block-level interface which permits + reading, writing and (in some cases) erasing blocks. Block + devices often have a partition table which allows the device to + be partitioned into several areas, called 'partitions' in U-Boot. + A filesystem can be placed in each partition. + +config TPL_BLK + bool "Support block devices in TPL" + depends on TPL_DM && BLK + default y + help + Enable support for block devices, such as SCSI, MMC and USB + flash sticks. These provide a block-level interface which permits + reading, writing and (in some cases) erasing blocks. Block + devices often have a partition table which allows the device to + be partitioned into several areas, called 'partitions' in U-Boot. + A filesystem can be placed in each partition. + +config BLOCK_CACHE + bool "Use block device cache" + depends on BLK + default y + help + This option enables a disk-block cache for all block devices. + This is most useful when accessing filesystems under U-Boot since + it will prevent repeated reads from directory structures and other + filesystem data structures. + +config SPL_BLOCK_CACHE + bool "Use block device cache in SPL" + depends on SPL_BLK + default n + help + This option enables the disk-block cache in SPL + +config TPL_BLOCK_CACHE + bool "Use block device cache in TPL" + depends on TPL_BLK + default n + help + This option enables the disk-block cache in TPL + +config IDE + bool "Support IDE controllers" + select HAVE_BLOCK_DEVICE + help + Enables support for IDE (Integrated Drive Electronics) hard drives. + This allows access to raw blocks and filesystems on an IDE drive + from U-Boot. See also CMD_IDE which provides an 'ide' command for + performing various IDE operations. diff --git a/roms/u-boot/drivers/block/Makefile b/roms/u-boot/drivers/block/Makefile new file mode 100644 index 000000000..94ab5c6f9 --- /dev/null +++ b/roms/u-boot/drivers/block/Makefile @@ -0,0 +1,16 @@ +# SPDX-License-Identifier: GPL-2.0+ +# +# (C) Copyright 2000-2007 +# Wolfgang Denk, DENX Software Engineering, wd@denx.de. + +obj-$(CONFIG_$(SPL_)BLK) += blk-uclass.o + +ifndef CONFIG_$(SPL_)BLK +obj-$(CONFIG_HAVE_BLOCK_DEVICE) += blk_legacy.o +endif + +ifndef CONFIG_SPL_BUILD +obj-$(CONFIG_IDE) += ide.o +endif +obj-$(CONFIG_SANDBOX) += sandbox.o +obj-$(CONFIG_$(SPL_TPL_)BLOCK_CACHE) += blkcache.o diff --git a/roms/u-boot/drivers/block/blk-uclass.c b/roms/u-boot/drivers/block/blk-uclass.c new file mode 100644 index 000000000..d0d91db54 --- /dev/null +++ b/roms/u-boot/drivers/block/blk-uclass.c @@ -0,0 +1,664 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2016 Google, Inc + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <common.h> +#include <blk.h> +#include <dm.h> +#include <log.h> +#include <malloc.h> +#include <part.h> +#include <dm/device-internal.h> +#include <dm/lists.h> +#include <dm/uclass-internal.h> +#include <linux/err.h> + +static const char *if_typename_str[IF_TYPE_COUNT] = { + [IF_TYPE_IDE] = "ide", + [IF_TYPE_SCSI] = "scsi", + [IF_TYPE_ATAPI] = "atapi", + [IF_TYPE_USB] = "usb", + [IF_TYPE_DOC] = "doc", + [IF_TYPE_MMC] = "mmc", + [IF_TYPE_SD] = "sd", + [IF_TYPE_SATA] = "sata", + [IF_TYPE_HOST] = "host", + [IF_TYPE_NVME] = "nvme", + [IF_TYPE_EFI] = "efi", + [IF_TYPE_VIRTIO] = "virtio", + [IF_TYPE_PVBLOCK] = "pvblock", +}; + +static enum uclass_id if_type_uclass_id[IF_TYPE_COUNT] = { + [IF_TYPE_IDE] = UCLASS_IDE, + [IF_TYPE_SCSI] = UCLASS_SCSI, + [IF_TYPE_ATAPI] = UCLASS_INVALID, + [IF_TYPE_USB] = UCLASS_MASS_STORAGE, + [IF_TYPE_DOC] = UCLASS_INVALID, + [IF_TYPE_MMC] = UCLASS_MMC, + [IF_TYPE_SD] = UCLASS_INVALID, + [IF_TYPE_SATA] = UCLASS_AHCI, + [IF_TYPE_HOST] = UCLASS_ROOT, + [IF_TYPE_NVME] = UCLASS_NVME, + [IF_TYPE_EFI] = UCLASS_EFI, + [IF_TYPE_VIRTIO] = UCLASS_VIRTIO, + [IF_TYPE_PVBLOCK] = UCLASS_PVBLOCK, +}; + +static enum if_type if_typename_to_iftype(const char *if_typename) +{ + int i; + + for (i = 0; i < IF_TYPE_COUNT; i++) { + if (if_typename_str[i] && + !strcmp(if_typename, if_typename_str[i])) + return i; + } + + return IF_TYPE_UNKNOWN; +} + +static enum uclass_id if_type_to_uclass_id(enum if_type if_type) +{ + return if_type_uclass_id[if_type]; +} + +const char *blk_get_if_type_name(enum if_type if_type) +{ + return if_typename_str[if_type]; +} + +struct blk_desc *blk_get_devnum_by_type(enum if_type if_type, int devnum) +{ + struct blk_desc *desc; + struct udevice *dev; + int ret; + + ret = blk_get_device(if_type, devnum, &dev); + if (ret) + return NULL; + desc = dev_get_uclass_plat(dev); + + return desc; +} + +/* + * This function is complicated with driver model. We look up the interface + * name in a local table. This gives us an interface type which we can match + * against the uclass of the block device's parent. + */ +struct blk_desc *blk_get_devnum_by_typename(const char *if_typename, int devnum) +{ + enum uclass_id uclass_id; + enum if_type if_type; + struct udevice *dev; + struct uclass *uc; + int ret; + + if_type = if_typename_to_iftype(if_typename); + if (if_type == IF_TYPE_UNKNOWN) { + debug("%s: Unknown interface type '%s'\n", __func__, + if_typename); + return NULL; + } + uclass_id = if_type_to_uclass_id(if_type); + if (uclass_id == UCLASS_INVALID) { + debug("%s: Unknown uclass for interface type'\n", + if_typename_str[if_type]); + return NULL; + } + + ret = uclass_get(UCLASS_BLK, &uc); + if (ret) + return NULL; + uclass_foreach_dev(dev, uc) { + struct blk_desc *desc = dev_get_uclass_plat(dev); + + debug("%s: if_type=%d, devnum=%d: %s, %d, %d\n", __func__, + if_type, devnum, dev->name, desc->if_type, desc->devnum); + if (desc->devnum != devnum) + continue; + + /* Find out the parent device uclass */ + if (device_get_uclass_id(dev->parent) != uclass_id) { + debug("%s: parent uclass %d, this dev %d\n", __func__, + device_get_uclass_id(dev->parent), uclass_id); + continue; + } + + if (device_probe(dev)) + return NULL; + + debug("%s: Device desc %p\n", __func__, desc); + return desc; + } + debug("%s: No device found\n", __func__); + + return NULL; +} + +/** + * blk_get_by_device() - Get the block device descriptor for the given device + * @dev: Instance of a storage device + * + * Return: With block device descriptor on success , NULL if there is no such + * block device. + */ +struct blk_desc *blk_get_by_device(struct udevice *dev) +{ + struct udevice *child_dev; + + device_foreach_child(child_dev, dev) { + if (device_get_uclass_id(child_dev) != UCLASS_BLK) + continue; + + return dev_get_uclass_plat(child_dev); + } + + debug("%s: No block device found\n", __func__); + + return NULL; +} + +/** + * get_desc() - Get the block device descriptor for the given device number + * + * @if_type: Interface type + * @devnum: Device number (0 = first) + * @descp: Returns block device descriptor on success + * @return 0 on success, -ENODEV if there is no such device and no device + * with a higher device number, -ENOENT if there is no such device but there + * is one with a higher number, or other -ve on other error. + */ +static int get_desc(enum if_type if_type, int devnum, struct blk_desc **descp) +{ + bool found_more = false; + struct udevice *dev; + struct uclass *uc; + int ret; + + *descp = NULL; + ret = uclass_get(UCLASS_BLK, &uc); + if (ret) + return ret; + uclass_foreach_dev(dev, uc) { + struct blk_desc *desc = dev_get_uclass_plat(dev); + + debug("%s: if_type=%d, devnum=%d: %s, %d, %d\n", __func__, + if_type, devnum, dev->name, desc->if_type, desc->devnum); + if (desc->if_type == if_type) { + if (desc->devnum == devnum) { + ret = device_probe(dev); + if (ret) + return ret; + + *descp = desc; + return 0; + } else if (desc->devnum > devnum) { + found_more = true; + } + } + } + + return found_more ? -ENOENT : -ENODEV; +} + +int blk_select_hwpart_devnum(enum if_type if_type, int devnum, int hwpart) +{ + struct udevice *dev; + int ret; + + ret = blk_get_device(if_type, devnum, &dev); + if (ret) + return ret; + + return blk_select_hwpart(dev, hwpart); +} + +int blk_list_part(enum if_type if_type) +{ + struct blk_desc *desc; + int devnum, ok; + int ret; + + for (ok = 0, devnum = 0;; ++devnum) { + ret = get_desc(if_type, devnum, &desc); + if (ret == -ENODEV) + break; + else if (ret) + continue; + if (desc->part_type != PART_TYPE_UNKNOWN) { + ++ok; + if (devnum) + putc('\n'); + part_print(desc); + } + } + if (!ok) + return -ENODEV; + + return 0; +} + +int blk_print_part_devnum(enum if_type if_type, int devnum) +{ + struct blk_desc *desc; + int ret; + + ret = get_desc(if_type, devnum, &desc); + if (ret) + return ret; + if (desc->type == DEV_TYPE_UNKNOWN) + return -ENOENT; + part_print(desc); + + return 0; +} + +void blk_list_devices(enum if_type if_type) +{ + struct blk_desc *desc; + int ret; + int i; + + for (i = 0;; ++i) { + ret = get_desc(if_type, i, &desc); + if (ret == -ENODEV) + break; + else if (ret) + continue; + if (desc->type == DEV_TYPE_UNKNOWN) + continue; /* list only known devices */ + printf("Device %d: ", i); + dev_print(desc); + } +} + +int blk_print_device_num(enum if_type if_type, int devnum) +{ + struct blk_desc *desc; + int ret; + + ret = get_desc(if_type, devnum, &desc); + if (ret) + return ret; + printf("\nIDE device %d: ", devnum); + dev_print(desc); + + return 0; +} + +int blk_show_device(enum if_type if_type, int devnum) +{ + struct blk_desc *desc; + int ret; + + printf("\nDevice %d: ", devnum); + ret = get_desc(if_type, devnum, &desc); + if (ret == -ENODEV || ret == -ENOENT) { + printf("unknown device\n"); + return -ENODEV; + } + if (ret) + return ret; + dev_print(desc); + + if (desc->type == DEV_TYPE_UNKNOWN) + return -ENOENT; + + return 0; +} + +ulong blk_read_devnum(enum if_type if_type, int devnum, lbaint_t start, + lbaint_t blkcnt, void *buffer) +{ + struct blk_desc *desc; + ulong n; + int ret; + + ret = get_desc(if_type, devnum, &desc); + if (ret) + return ret; + n = blk_dread(desc, start, blkcnt, buffer); + if (IS_ERR_VALUE(n)) + return n; + + return n; +} + +ulong blk_write_devnum(enum if_type if_type, int devnum, lbaint_t start, + lbaint_t blkcnt, const void *buffer) +{ + struct blk_desc *desc; + int ret; + + ret = get_desc(if_type, devnum, &desc); + if (ret) + return ret; + return blk_dwrite(desc, start, blkcnt, buffer); +} + +int blk_select_hwpart(struct udevice *dev, int hwpart) +{ + const struct blk_ops *ops = blk_get_ops(dev); + + if (!ops) + return -ENOSYS; + if (!ops->select_hwpart) + return 0; + + return ops->select_hwpart(dev, hwpart); +} + +int blk_dselect_hwpart(struct blk_desc *desc, int hwpart) +{ + return blk_select_hwpart(desc->bdev, hwpart); +} + +int blk_first_device(int if_type, struct udevice **devp) +{ + struct blk_desc *desc; + int ret; + + ret = uclass_find_first_device(UCLASS_BLK, devp); + if (ret) + return ret; + if (!*devp) + return -ENODEV; + do { + desc = dev_get_uclass_plat(*devp); + if (desc->if_type == if_type) + return 0; + ret = uclass_find_next_device(devp); + if (ret) + return ret; + } while (*devp); + + return -ENODEV; +} + +int blk_next_device(struct udevice **devp) +{ + struct blk_desc *desc; + int ret, if_type; + + desc = dev_get_uclass_plat(*devp); + if_type = desc->if_type; + do { + ret = uclass_find_next_device(devp); + if (ret) + return ret; + if (!*devp) + return -ENODEV; + desc = dev_get_uclass_plat(*devp); + if (desc->if_type == if_type) + return 0; + } while (1); +} + +int blk_find_device(int if_type, int devnum, struct udevice **devp) +{ + struct uclass *uc; + struct udevice *dev; + int ret; + + ret = uclass_get(UCLASS_BLK, &uc); + if (ret) + return ret; + uclass_foreach_dev(dev, uc) { + struct blk_desc *desc = dev_get_uclass_plat(dev); + + debug("%s: if_type=%d, devnum=%d: %s, %d, %d\n", __func__, + if_type, devnum, dev->name, desc->if_type, desc->devnum); + if (desc->if_type == if_type && desc->devnum == devnum) { + *devp = dev; + return 0; + } + } + + return -ENODEV; +} + +int blk_get_device(int if_type, int devnum, struct udevice **devp) +{ + int ret; + + ret = blk_find_device(if_type, devnum, devp); + if (ret) + return ret; + + return device_probe(*devp); +} + +unsigned long blk_dread(struct blk_desc *block_dev, lbaint_t start, + lbaint_t blkcnt, void *buffer) +{ + struct udevice *dev = block_dev->bdev; + const struct blk_ops *ops = blk_get_ops(dev); + ulong blks_read; + + if (!ops->read) + return -ENOSYS; + + if (blkcache_read(block_dev->if_type, block_dev->devnum, + start, blkcnt, block_dev->blksz, buffer)) + return blkcnt; + blks_read = ops->read(dev, start, blkcnt, buffer); + if (blks_read == blkcnt) + blkcache_fill(block_dev->if_type, block_dev->devnum, + start, blkcnt, block_dev->blksz, buffer); + + return blks_read; +} + +unsigned long blk_dwrite(struct blk_desc *block_dev, lbaint_t start, + lbaint_t blkcnt, const void *buffer) +{ + struct udevice *dev = block_dev->bdev; + const struct blk_ops *ops = blk_get_ops(dev); + + if (!ops->write) + return -ENOSYS; + + blkcache_invalidate(block_dev->if_type, block_dev->devnum); + return ops->write(dev, start, blkcnt, buffer); +} + +unsigned long blk_derase(struct blk_desc *block_dev, lbaint_t start, + lbaint_t blkcnt) +{ + struct udevice *dev = block_dev->bdev; + const struct blk_ops *ops = blk_get_ops(dev); + + if (!ops->erase) + return -ENOSYS; + + blkcache_invalidate(block_dev->if_type, block_dev->devnum); + return ops->erase(dev, start, blkcnt); +} + +int blk_get_from_parent(struct udevice *parent, struct udevice **devp) +{ + struct udevice *dev; + enum uclass_id id; + int ret; + + device_find_first_child(parent, &dev); + if (!dev) { + debug("%s: No block device found for parent '%s'\n", __func__, + parent->name); + return -ENODEV; + } + id = device_get_uclass_id(dev); + if (id != UCLASS_BLK) { + debug("%s: Incorrect uclass %s for block device '%s'\n", + __func__, uclass_get_name(id), dev->name); + return -ENOTBLK; + } + ret = device_probe(dev); + if (ret) + return ret; + *devp = dev; + + return 0; +} + +int blk_find_max_devnum(enum if_type if_type) +{ + struct udevice *dev; + int max_devnum = -ENODEV; + struct uclass *uc; + int ret; + + ret = uclass_get(UCLASS_BLK, &uc); + if (ret) + return ret; + uclass_foreach_dev(dev, uc) { + struct blk_desc *desc = dev_get_uclass_plat(dev); + + if (desc->if_type == if_type && desc->devnum > max_devnum) + max_devnum = desc->devnum; + } + + return max_devnum; +} + +int blk_next_free_devnum(enum if_type if_type) +{ + int ret; + + ret = blk_find_max_devnum(if_type); + if (ret == -ENODEV) + return 0; + if (ret < 0) + return ret; + + return ret + 1; +} + +static int blk_claim_devnum(enum if_type if_type, int devnum) +{ + struct udevice *dev; + struct uclass *uc; + int ret; + + ret = uclass_get(UCLASS_BLK, &uc); + if (ret) + return ret; + uclass_foreach_dev(dev, uc) { + struct blk_desc *desc = dev_get_uclass_plat(dev); + + if (desc->if_type == if_type && desc->devnum == devnum) { + int next = blk_next_free_devnum(if_type); + + if (next < 0) + return next; + desc->devnum = next; + return 0; + } + } + + return -ENOENT; +} + +int blk_create_device(struct udevice *parent, const char *drv_name, + const char *name, int if_type, int devnum, int blksz, + lbaint_t lba, struct udevice **devp) +{ + struct blk_desc *desc; + struct udevice *dev; + int ret; + + if (devnum == -1) { + devnum = blk_next_free_devnum(if_type); + } else { + ret = blk_claim_devnum(if_type, devnum); + if (ret < 0 && ret != -ENOENT) + return ret; + } + if (devnum < 0) + return devnum; + ret = device_bind_driver(parent, drv_name, name, &dev); + if (ret) + return ret; + desc = dev_get_uclass_plat(dev); + desc->if_type = if_type; + desc->blksz = blksz; + desc->log2blksz = LOG2(desc->blksz); + desc->lba = lba; + desc->part_type = PART_TYPE_UNKNOWN; + desc->bdev = dev; + desc->devnum = devnum; + *devp = dev; + + return 0; +} + +int blk_create_devicef(struct udevice *parent, const char *drv_name, + const char *name, int if_type, int devnum, int blksz, + lbaint_t lba, struct udevice **devp) +{ + char dev_name[30], *str; + int ret; + + snprintf(dev_name, sizeof(dev_name), "%s.%s", parent->name, name); + str = strdup(dev_name); + if (!str) + return -ENOMEM; + + ret = blk_create_device(parent, drv_name, str, if_type, devnum, + blksz, lba, devp); + if (ret) { + free(str); + return ret; + } + device_set_name_alloced(*devp); + + return 0; +} + +int blk_unbind_all(int if_type) +{ + struct uclass *uc; + struct udevice *dev, *next; + int ret; + + ret = uclass_get(UCLASS_BLK, &uc); + if (ret) + return ret; + uclass_foreach_dev_safe(dev, next, uc) { + struct blk_desc *desc = dev_get_uclass_plat(dev); + + if (desc->if_type == if_type) { + ret = device_remove(dev, DM_REMOVE_NORMAL); + if (ret) + return ret; + ret = device_unbind(dev); + if (ret) + return ret; + } + } + + return 0; +} + +static int blk_post_probe(struct udevice *dev) +{ + if (IS_ENABLED(CONFIG_PARTITIONS) && + IS_ENABLED(CONFIG_HAVE_BLOCK_DEVICE)) { + struct blk_desc *desc = dev_get_uclass_plat(dev); + + part_init(desc); + } + + return 0; +} + +UCLASS_DRIVER(blk) = { + .id = UCLASS_BLK, + .name = "blk", + .post_probe = blk_post_probe, + .per_device_plat_auto = sizeof(struct blk_desc), +}; diff --git a/roms/u-boot/drivers/block/blk_legacy.c b/roms/u-boot/drivers/block/blk_legacy.c new file mode 100644 index 000000000..e3ee778c2 --- /dev/null +++ b/roms/u-boot/drivers/block/blk_legacy.c @@ -0,0 +1,266 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2016 Google, Inc + * Written by Simon Glass <sjg@chromium.org> + */ + +#include <common.h> +#include <blk.h> +#include <part.h> +#include <linux/err.h> + +struct blk_driver *blk_driver_lookup_type(int if_type) +{ + struct blk_driver *drv = ll_entry_start(struct blk_driver, blk_driver); + const int n_ents = ll_entry_count(struct blk_driver, blk_driver); + struct blk_driver *entry; + + for (entry = drv; entry != drv + n_ents; entry++) { + if (if_type == entry->if_type) + return entry; + } + + /* Not found */ + return NULL; +} + +static struct blk_driver *blk_driver_lookup_typename(const char *if_typename) +{ + struct blk_driver *drv = ll_entry_start(struct blk_driver, blk_driver); + const int n_ents = ll_entry_count(struct blk_driver, blk_driver); + struct blk_driver *entry; + + for (entry = drv; entry != drv + n_ents; entry++) { + if (!strcmp(if_typename, entry->if_typename)) + return entry; + } + + /* Not found */ + return NULL; +} + +const char *blk_get_if_type_name(enum if_type if_type) +{ + struct blk_driver *drv = blk_driver_lookup_type(if_type); + + return drv ? drv->if_typename : NULL; +} + +/** + * get_desc() - Get the block device descriptor for the given device number + * + * @drv: Legacy block driver + * @devnum: Device number (0 = first) + * @descp: Returns block device descriptor on success + * @return 0 on success, -ENODEV if there is no such device, -ENOSYS if the + * driver does not provide a way to find a device, or other -ve on other + * error. + */ +static int get_desc(struct blk_driver *drv, int devnum, struct blk_desc **descp) +{ + if (drv->desc) { + if (devnum < 0 || devnum >= drv->max_devs) + return -ENODEV; + *descp = &drv->desc[devnum]; + return 0; + } + if (!drv->get_dev) + return -ENOSYS; + + return drv->get_dev(devnum, descp); +} + +#ifdef CONFIG_HAVE_BLOCK_DEVICE +int blk_list_part(enum if_type if_type) +{ + struct blk_driver *drv; + struct blk_desc *desc; + int devnum, ok; + bool first = true; + + drv = blk_driver_lookup_type(if_type); + if (!drv) + return -ENOSYS; + for (ok = 0, devnum = 0; devnum < drv->max_devs; ++devnum) { + if (get_desc(drv, devnum, &desc)) + continue; + if (desc->part_type != PART_TYPE_UNKNOWN) { + ++ok; + if (!first) + putc('\n'); + part_print(desc); + first = false; + } + } + if (!ok) + return -ENODEV; + + return 0; +} + +int blk_print_part_devnum(enum if_type if_type, int devnum) +{ + struct blk_driver *drv = blk_driver_lookup_type(if_type); + struct blk_desc *desc; + int ret; + + if (!drv) + return -ENOSYS; + ret = get_desc(drv, devnum, &desc); + if (ret) + return ret; + if (desc->type == DEV_TYPE_UNKNOWN) + return -ENOENT; + part_print(desc); + + return 0; +} + +void blk_list_devices(enum if_type if_type) +{ + struct blk_driver *drv = blk_driver_lookup_type(if_type); + struct blk_desc *desc; + int i; + + if (!drv) + return; + for (i = 0; i < drv->max_devs; ++i) { + if (get_desc(drv, i, &desc)) + continue; + if (desc->type == DEV_TYPE_UNKNOWN) + continue; /* list only known devices */ + printf("Device %d: ", i); + dev_print(desc); + } +} + +int blk_print_device_num(enum if_type if_type, int devnum) +{ + struct blk_driver *drv = blk_driver_lookup_type(if_type); + struct blk_desc *desc; + int ret; + + if (!drv) + return -ENOSYS; + ret = get_desc(drv, devnum, &desc); + if (ret) + return ret; + printf("\n%s device %d: ", drv->if_typename, devnum); + dev_print(desc); + + return 0; +} + +int blk_show_device(enum if_type if_type, int devnum) +{ + struct blk_driver *drv = blk_driver_lookup_type(if_type); + struct blk_desc *desc; + int ret; + + if (!drv) + return -ENOSYS; + printf("\nDevice %d: ", devnum); + if (devnum >= drv->max_devs) { + puts("unknown device\n"); + return -ENODEV; + } + ret = get_desc(drv, devnum, &desc); + if (ret) + return ret; + dev_print(desc); + + if (desc->type == DEV_TYPE_UNKNOWN) + return -ENOENT; + + return 0; +} +#endif /* CONFIG_HAVE_BLOCK_DEVICE */ + +struct blk_desc *blk_get_devnum_by_type(enum if_type if_type, int devnum) +{ + struct blk_driver *drv = blk_driver_lookup_type(if_type); + struct blk_desc *desc; + + if (!drv) + return NULL; + + if (get_desc(drv, devnum, &desc)) + return NULL; + + return desc; +} + +int blk_dselect_hwpart(struct blk_desc *desc, int hwpart) +{ + struct blk_driver *drv = blk_driver_lookup_type(desc->if_type); + + if (!drv) + return -ENOSYS; + if (drv->select_hwpart) + return drv->select_hwpart(desc, hwpart); + + return 0; +} + +struct blk_desc *blk_get_devnum_by_typename(const char *if_typename, int devnum) +{ + struct blk_driver *drv = blk_driver_lookup_typename(if_typename); + struct blk_desc *desc; + + if (!drv) + return NULL; + + if (get_desc(drv, devnum, &desc)) + return NULL; + + return desc; +} + +ulong blk_read_devnum(enum if_type if_type, int devnum, lbaint_t start, + lbaint_t blkcnt, void *buffer) +{ + struct blk_driver *drv = blk_driver_lookup_type(if_type); + struct blk_desc *desc; + ulong n; + int ret; + + if (!drv) + return -ENOSYS; + ret = get_desc(drv, devnum, &desc); + if (ret) + return ret; + n = desc->block_read(desc, start, blkcnt, buffer); + if (IS_ERR_VALUE(n)) + return n; + + return n; +} + +ulong blk_write_devnum(enum if_type if_type, int devnum, lbaint_t start, + lbaint_t blkcnt, const void *buffer) +{ + struct blk_driver *drv = blk_driver_lookup_type(if_type); + struct blk_desc *desc; + int ret; + + if (!drv) + return -ENOSYS; + ret = get_desc(drv, devnum, &desc); + if (ret) + return ret; + return desc->block_write(desc, start, blkcnt, buffer); +} + +int blk_select_hwpart_devnum(enum if_type if_type, int devnum, int hwpart) +{ + struct blk_driver *drv = blk_driver_lookup_type(if_type); + struct blk_desc *desc; + int ret; + + if (!drv) + return -ENOSYS; + ret = get_desc(drv, devnum, &desc); + if (ret) + return ret; + return drv->select_hwpart(desc, hwpart); +} diff --git a/roms/u-boot/drivers/block/blkcache.c b/roms/u-boot/drivers/block/blkcache.c new file mode 100644 index 000000000..b53420a3a --- /dev/null +++ b/roms/u-boot/drivers/block/blkcache.c @@ -0,0 +1,190 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) Nelson Integration, LLC 2016 + * Author: Eric Nelson<eric@nelint.com> + * + */ +#include <common.h> +#include <blk.h> +#include <log.h> +#include <malloc.h> +#include <part.h> +#include <asm/global_data.h> +#include <linux/ctype.h> +#include <linux/list.h> + +#ifdef CONFIG_NEEDS_MANUAL_RELOC +DECLARE_GLOBAL_DATA_PTR; +#endif + +struct block_cache_node { + struct list_head lh; + int iftype; + int devnum; + lbaint_t start; + lbaint_t blkcnt; + unsigned long blksz; + char *cache; +}; + +static LIST_HEAD(block_cache); + +static struct block_cache_stats _stats = { + .max_blocks_per_entry = 8, + .max_entries = 32 +}; + +#ifdef CONFIG_NEEDS_MANUAL_RELOC +int blkcache_init(void) +{ + struct list_head *head = &block_cache; + + head->next = (uintptr_t)head->next + gd->reloc_off; + head->prev = (uintptr_t)head->prev + gd->reloc_off; + + return 0; +} +#endif + +static struct block_cache_node *cache_find(int iftype, int devnum, + lbaint_t start, lbaint_t blkcnt, + unsigned long blksz) +{ + struct block_cache_node *node; + + list_for_each_entry(node, &block_cache, lh) + if ((node->iftype == iftype) && + (node->devnum == devnum) && + (node->blksz == blksz) && + (node->start <= start) && + (node->start + node->blkcnt >= start + blkcnt)) { + if (block_cache.next != &node->lh) { + /* maintain MRU ordering */ + list_del(&node->lh); + list_add(&node->lh, &block_cache); + } + return node; + } + return 0; +} + +int blkcache_read(int iftype, int devnum, + lbaint_t start, lbaint_t blkcnt, + unsigned long blksz, void *buffer) +{ + struct block_cache_node *node = cache_find(iftype, devnum, start, + blkcnt, blksz); + if (node) { + const char *src = node->cache + (start - node->start) * blksz; + memcpy(buffer, src, blksz * blkcnt); + debug("hit: start " LBAF ", count " LBAFU "\n", + start, blkcnt); + ++_stats.hits; + return 1; + } + + debug("miss: start " LBAF ", count " LBAFU "\n", + start, blkcnt); + ++_stats.misses; + return 0; +} + +void blkcache_fill(int iftype, int devnum, + lbaint_t start, lbaint_t blkcnt, + unsigned long blksz, void const *buffer) +{ + lbaint_t bytes; + struct block_cache_node *node; + + /* don't cache big stuff */ + if (blkcnt > _stats.max_blocks_per_entry) + return; + + if (_stats.max_entries == 0) + return; + + bytes = blksz * blkcnt; + if (_stats.max_entries <= _stats.entries) { + /* pop LRU */ + node = (struct block_cache_node *)block_cache.prev; + list_del(&node->lh); + _stats.entries--; + debug("drop: start " LBAF ", count " LBAFU "\n", + node->start, node->blkcnt); + if (node->blkcnt * node->blksz < bytes) { + free(node->cache); + node->cache = 0; + } + } else { + node = malloc(sizeof(*node)); + if (!node) + return; + node->cache = 0; + } + + if (!node->cache) { + node->cache = malloc(bytes); + if (!node->cache) { + free(node); + return; + } + } + + debug("fill: start " LBAF ", count " LBAFU "\n", + start, blkcnt); + + node->iftype = iftype; + node->devnum = devnum; + node->start = start; + node->blkcnt = blkcnt; + node->blksz = blksz; + memcpy(node->cache, buffer, bytes); + list_add(&node->lh, &block_cache); + _stats.entries++; +} + +void blkcache_invalidate(int iftype, int devnum) +{ + struct list_head *entry, *n; + struct block_cache_node *node; + + list_for_each_safe(entry, n, &block_cache) { + node = (struct block_cache_node *)entry; + if ((node->iftype == iftype) && + (node->devnum == devnum)) { + list_del(entry); + free(node->cache); + free(node); + --_stats.entries; + } + } +} + +void blkcache_configure(unsigned blocks, unsigned entries) +{ + struct block_cache_node *node; + if ((blocks != _stats.max_blocks_per_entry) || + (entries != _stats.max_entries)) { + /* invalidate cache */ + while (!list_empty(&block_cache)) { + node = (struct block_cache_node *)block_cache.next; + list_del(&node->lh); + free(node->cache); + free(node); + } + _stats.entries = 0; + } + + _stats.max_blocks_per_entry = blocks; + _stats.max_entries = entries; + + _stats.hits = 0; + _stats.misses = 0; +} + +void blkcache_stats(struct block_cache_stats *stats) +{ + memcpy(stats, &_stats, sizeof(*stats)); + _stats.hits = 0; + _stats.misses = 0; +} diff --git a/roms/u-boot/drivers/block/ide.c b/roms/u-boot/drivers/block/ide.c new file mode 100644 index 000000000..862a85bc8 --- /dev/null +++ b/roms/u-boot/drivers/block/ide.c @@ -0,0 +1,1182 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2000-2011 + * Wolfgang Denk, DENX Software Engineering, wd@denx.de. + */ + +#include <common.h> +#include <ata.h> +#include <blk.h> +#include <dm.h> +#include <ide.h> +#include <log.h> +#include <part.h> +#include <watchdog.h> +#include <asm/io.h> +#include <linux/delay.h> + +#ifdef __PPC__ +# define EIEIO __asm__ volatile ("eieio") +# define SYNC __asm__ volatile ("sync") +#else +# define EIEIO /* nothing */ +# define SYNC /* nothing */ +#endif + +/* Current offset for IDE0 / IDE1 bus access */ +ulong ide_bus_offset[CONFIG_SYS_IDE_MAXBUS] = { +#if defined(CONFIG_SYS_ATA_IDE0_OFFSET) + CONFIG_SYS_ATA_IDE0_OFFSET, +#endif +#if defined(CONFIG_SYS_ATA_IDE1_OFFSET) && (CONFIG_SYS_IDE_MAXBUS > 1) + CONFIG_SYS_ATA_IDE1_OFFSET, +#endif +}; + +static int ide_bus_ok[CONFIG_SYS_IDE_MAXBUS]; + +struct blk_desc ide_dev_desc[CONFIG_SYS_IDE_MAXDEVICE]; + +#define IDE_TIME_OUT 2000 /* 2 sec timeout */ + +#define ATAPI_TIME_OUT 7000 /* 7 sec timeout (5 sec seems to work...) */ + +#define IDE_SPIN_UP_TIME_OUT 5000 /* 5 sec spin-up timeout */ + +#ifndef CONFIG_SYS_ATA_PORT_ADDR +#define CONFIG_SYS_ATA_PORT_ADDR(port) (port) +#endif + +#ifdef CONFIG_IDE_RESET +extern void ide_set_reset(int idereset); + +static void ide_reset(void) +{ + int i; + + for (i = 0; i < CONFIG_SYS_IDE_MAXBUS; ++i) + ide_bus_ok[i] = 0; + for (i = 0; i < CONFIG_SYS_IDE_MAXDEVICE; ++i) + ide_dev_desc[i].type = DEV_TYPE_UNKNOWN; + + ide_set_reset(1); /* assert reset */ + + /* the reset signal shall be asserted for et least 25 us */ + udelay(25); + + WATCHDOG_RESET(); + + /* de-assert RESET signal */ + ide_set_reset(0); + + /* wait 250 ms */ + for (i = 0; i < 250; ++i) + udelay(1000); +} +#else +#define ide_reset() /* dummy */ +#endif /* CONFIG_IDE_RESET */ + +/* + * Wait until Busy bit is off, or timeout (in ms) + * Return last status + */ +static uchar ide_wait(int dev, ulong t) +{ + ulong delay = 10 * t; /* poll every 100 us */ + uchar c; + + while ((c = ide_inb(dev, ATA_STATUS)) & ATA_STAT_BUSY) { + udelay(100); + if (delay-- == 0) + break; + } + return c; +} + +/* + * copy src to dest, skipping leading and trailing blanks and null + * terminate the string + * "len" is the size of available memory including the terminating '\0' + */ +static void ident_cpy(unsigned char *dst, unsigned char *src, + unsigned int len) +{ + unsigned char *end, *last; + + last = dst; + end = src + len - 1; + + /* reserve space for '\0' */ + if (len < 2) + goto OUT; + + /* skip leading white space */ + while ((*src) && (src < end) && (*src == ' ')) + ++src; + + /* copy string, omitting trailing white space */ + while ((*src) && (src < end)) { + *dst++ = *src; + if (*src++ != ' ') + last = dst; + } +OUT: + *last = '\0'; +} + +#ifdef CONFIG_ATAPI +/**************************************************************************** + * ATAPI Support + */ + +/* since ATAPI may use commands with not 4 bytes alligned length + * we have our own transfer functions, 2 bytes alligned */ +__weak void ide_output_data_shorts(int dev, ushort *sect_buf, int shorts) +{ + uintptr_t paddr = (ATA_CURR_BASE(dev) + ATA_DATA_REG); + ushort *dbuf; + + dbuf = (ushort *)sect_buf; + + debug("in output data shorts base for read is %p\n", (void *)paddr); + + while (shorts--) { + EIEIO; + outw(cpu_to_le16(*dbuf++), paddr); + } +} + +__weak void ide_input_data_shorts(int dev, ushort *sect_buf, int shorts) +{ + uintptr_t paddr = (ATA_CURR_BASE(dev) + ATA_DATA_REG); + ushort *dbuf; + + dbuf = (ushort *)sect_buf; + + debug("in input data shorts base for read is %p\n", (void *)paddr); + + while (shorts--) { + EIEIO; + *dbuf++ = le16_to_cpu(inw(paddr)); + } +} + +/* + * Wait until (Status & mask) == res, or timeout (in ms) + * Return last status + * This is used since some ATAPI CD ROMs clears their Busy Bit first + * and then they set their DRQ Bit + */ +static uchar atapi_wait_mask(int dev, ulong t, uchar mask, uchar res) +{ + ulong delay = 10 * t; /* poll every 100 us */ + uchar c; + + /* prevents to read the status before valid */ + c = ide_inb(dev, ATA_DEV_CTL); + + while (((c = ide_inb(dev, ATA_STATUS)) & mask) != res) { + /* break if error occurs (doesn't make sense to wait more) */ + if ((c & ATA_STAT_ERR) == ATA_STAT_ERR) + break; + udelay(100); + if (delay-- == 0) + break; + } + return c; +} + +/* + * issue an atapi command + */ +unsigned char atapi_issue(int device, unsigned char *ccb, int ccblen, + unsigned char *buffer, int buflen) +{ + unsigned char c, err, mask, res; + int n; + + /* Select device + */ + mask = ATA_STAT_BUSY | ATA_STAT_DRQ; + res = 0; + ide_outb(device, ATA_DEV_HD, ATA_LBA | ATA_DEVICE(device)); + c = atapi_wait_mask(device, ATAPI_TIME_OUT, mask, res); + if ((c & mask) != res) { + printf("ATAPI_ISSUE: device %d not ready status %X\n", device, + c); + err = 0xFF; + goto AI_OUT; + } + /* write taskfile */ + ide_outb(device, ATA_ERROR_REG, 0); /* no DMA, no overlaped */ + ide_outb(device, ATA_SECT_CNT, 0); + ide_outb(device, ATA_SECT_NUM, 0); + ide_outb(device, ATA_CYL_LOW, (unsigned char) (buflen & 0xFF)); + ide_outb(device, ATA_CYL_HIGH, + (unsigned char) ((buflen >> 8) & 0xFF)); + ide_outb(device, ATA_DEV_HD, ATA_LBA | ATA_DEVICE(device)); + + ide_outb(device, ATA_COMMAND, ATA_CMD_PACKET); + udelay(50); + + mask = ATA_STAT_DRQ | ATA_STAT_BUSY | ATA_STAT_ERR; + res = ATA_STAT_DRQ; + c = atapi_wait_mask(device, ATAPI_TIME_OUT, mask, res); + + if ((c & mask) != res) { /* DRQ must be 1, BSY 0 */ + printf("ATAPI_ISSUE: Error (no IRQ) before sending ccb dev %d status 0x%02x\n", + device, c); + err = 0xFF; + goto AI_OUT; + } + + /* write command block */ + ide_output_data_shorts(device, (unsigned short *)ccb, ccblen / 2); + + /* ATAPI Command written wait for completition */ + udelay(5000); /* device must set bsy */ + + mask = ATA_STAT_DRQ | ATA_STAT_BUSY | ATA_STAT_ERR; + /* + * if no data wait for DRQ = 0 BSY = 0 + * if data wait for DRQ = 1 BSY = 0 + */ + res = 0; + if (buflen) + res = ATA_STAT_DRQ; + c = atapi_wait_mask(device, ATAPI_TIME_OUT, mask, res); + if ((c & mask) != res) { + if (c & ATA_STAT_ERR) { + err = (ide_inb(device, ATA_ERROR_REG)) >> 4; + debug("atapi_issue 1 returned sense key %X status %02X\n", + err, c); + } else { + printf("ATAPI_ISSUE: (no DRQ) after sending ccb (%x) status 0x%02x\n", + ccb[0], c); + err = 0xFF; + } + goto AI_OUT; + } + n = ide_inb(device, ATA_CYL_HIGH); + n <<= 8; + n += ide_inb(device, ATA_CYL_LOW); + if (n > buflen) { + printf("ERROR, transfer bytes %d requested only %d\n", n, + buflen); + err = 0xff; + goto AI_OUT; + } + if ((n == 0) && (buflen < 0)) { + printf("ERROR, transfer bytes %d requested %d\n", n, buflen); + err = 0xff; + goto AI_OUT; + } + if (n != buflen) { + debug("WARNING, transfer bytes %d not equal with requested %d\n", + n, buflen); + } + if (n != 0) { /* data transfer */ + debug("ATAPI_ISSUE: %d Bytes to transfer\n", n); + /* we transfer shorts */ + n >>= 1; + /* ok now decide if it is an in or output */ + if ((ide_inb(device, ATA_SECT_CNT) & 0x02) == 0) { + debug("Write to device\n"); + ide_output_data_shorts(device, (unsigned short *)buffer, + n); + } else { + debug("Read from device @ %p shorts %d\n", buffer, n); + ide_input_data_shorts(device, (unsigned short *)buffer, + n); + } + } + udelay(5000); /* seems that some CD ROMs need this... */ + mask = ATA_STAT_BUSY | ATA_STAT_ERR; + res = 0; + c = atapi_wait_mask(device, ATAPI_TIME_OUT, mask, res); + if ((c & ATA_STAT_ERR) == ATA_STAT_ERR) { + err = (ide_inb(device, ATA_ERROR_REG) >> 4); + debug("atapi_issue 2 returned sense key %X status %X\n", err, + c); + } else { + err = 0; + } +AI_OUT: + return err; +} + +/* + * sending the command to atapi_issue. If an status other than good + * returns, an request_sense will be issued + */ + +#define ATAPI_DRIVE_NOT_READY 100 +#define ATAPI_UNIT_ATTN 10 + +unsigned char atapi_issue_autoreq(int device, + unsigned char *ccb, + int ccblen, + unsigned char *buffer, int buflen) +{ + unsigned char sense_data[18], sense_ccb[12]; + unsigned char res, key, asc, ascq; + int notready, unitattn; + + unitattn = ATAPI_UNIT_ATTN; + notready = ATAPI_DRIVE_NOT_READY; + +retry: + res = atapi_issue(device, ccb, ccblen, buffer, buflen); + if (res == 0) + return 0; /* Ok */ + + if (res == 0xFF) + return 0xFF; /* error */ + + debug("(auto_req)atapi_issue returned sense key %X\n", res); + + memset(sense_ccb, 0, sizeof(sense_ccb)); + memset(sense_data, 0, sizeof(sense_data)); + sense_ccb[0] = ATAPI_CMD_REQ_SENSE; + sense_ccb[4] = 18; /* allocation Length */ + + res = atapi_issue(device, sense_ccb, 12, sense_data, 18); + key = (sense_data[2] & 0xF); + asc = (sense_data[12]); + ascq = (sense_data[13]); + + debug("ATAPI_CMD_REQ_SENSE returned %x\n", res); + debug(" Sense page: %02X key %02X ASC %02X ASCQ %02X\n", + sense_data[0], key, asc, ascq); + + if ((key == 0)) + return 0; /* ok device ready */ + + if ((key == 6) || (asc == 0x29) || (asc == 0x28)) { /* Unit Attention */ + if (unitattn-- > 0) { + udelay(200 * 1000); + goto retry; + } + printf("Unit Attention, tried %d\n", ATAPI_UNIT_ATTN); + goto error; + } + if ((asc == 0x4) && (ascq == 0x1)) { + /* not ready, but will be ready soon */ + if (notready-- > 0) { + udelay(200 * 1000); + goto retry; + } + printf("Drive not ready, tried %d times\n", + ATAPI_DRIVE_NOT_READY); + goto error; + } + if (asc == 0x3a) { + debug("Media not present\n"); + goto error; + } + + printf("ERROR: Unknown Sense key %02X ASC %02X ASCQ %02X\n", key, asc, + ascq); +error: + debug("ERROR Sense key %02X ASC %02X ASCQ %02X\n", key, asc, ascq); + return 0xFF; +} + +/* + * atapi_read: + * we transfer only one block per command, since the multiple DRQ per + * command is not yet implemented + */ +#define ATAPI_READ_MAX_BYTES 2048 /* we read max 2kbytes */ +#define ATAPI_READ_BLOCK_SIZE 2048 /* assuming CD part */ +#define ATAPI_READ_MAX_BLOCK (ATAPI_READ_MAX_BYTES/ATAPI_READ_BLOCK_SIZE) + +ulong atapi_read(struct blk_desc *block_dev, lbaint_t blknr, lbaint_t blkcnt, + void *buffer) +{ + int device = block_dev->devnum; + ulong n = 0; + unsigned char ccb[12]; /* Command descriptor block */ + ulong cnt; + + debug("atapi_read dev %d start " LBAF " blocks " LBAF + " buffer at %lX\n", device, blknr, blkcnt, (ulong) buffer); + + do { + if (blkcnt > ATAPI_READ_MAX_BLOCK) + cnt = ATAPI_READ_MAX_BLOCK; + else + cnt = blkcnt; + + ccb[0] = ATAPI_CMD_READ_12; + ccb[1] = 0; /* reserved */ + ccb[2] = (unsigned char) (blknr >> 24) & 0xFF; /* MSB Block */ + ccb[3] = (unsigned char) (blknr >> 16) & 0xFF; /* */ + ccb[4] = (unsigned char) (blknr >> 8) & 0xFF; + ccb[5] = (unsigned char) blknr & 0xFF; /* LSB Block */ + ccb[6] = (unsigned char) (cnt >> 24) & 0xFF; /* MSB Block cnt */ + ccb[7] = (unsigned char) (cnt >> 16) & 0xFF; + ccb[8] = (unsigned char) (cnt >> 8) & 0xFF; + ccb[9] = (unsigned char) cnt & 0xFF; /* LSB Block */ + ccb[10] = 0; /* reserved */ + ccb[11] = 0; /* reserved */ + + if (atapi_issue_autoreq(device, ccb, 12, + (unsigned char *)buffer, + cnt * ATAPI_READ_BLOCK_SIZE) + == 0xFF) { + return n; + } + n += cnt; + blkcnt -= cnt; + blknr += cnt; + buffer += (cnt * ATAPI_READ_BLOCK_SIZE); + } while (blkcnt > 0); + return n; +} + +static void atapi_inquiry(struct blk_desc *dev_desc) +{ + unsigned char ccb[12]; /* Command descriptor block */ + unsigned char iobuf[64]; /* temp buf */ + unsigned char c; + int device; + + device = dev_desc->devnum; + dev_desc->type = DEV_TYPE_UNKNOWN; /* not yet valid */ +#ifndef CONFIG_BLK + dev_desc->block_read = atapi_read; +#endif + + memset(ccb, 0, sizeof(ccb)); + memset(iobuf, 0, sizeof(iobuf)); + + ccb[0] = ATAPI_CMD_INQUIRY; + ccb[4] = 40; /* allocation Legnth */ + c = atapi_issue_autoreq(device, ccb, 12, (unsigned char *)iobuf, 40); + + debug("ATAPI_CMD_INQUIRY returned %x\n", c); + if (c != 0) + return; + + /* copy device ident strings */ + ident_cpy((unsigned char *)dev_desc->vendor, &iobuf[8], 8); + ident_cpy((unsigned char *)dev_desc->product, &iobuf[16], 16); + ident_cpy((unsigned char *)dev_desc->revision, &iobuf[32], 5); + + dev_desc->lun = 0; + dev_desc->lba = 0; + dev_desc->blksz = 0; + dev_desc->log2blksz = LOG2_INVALID(typeof(dev_desc->log2blksz)); + dev_desc->type = iobuf[0] & 0x1f; + + if ((iobuf[1] & 0x80) == 0x80) + dev_desc->removable = 1; + else + dev_desc->removable = 0; + + memset(ccb, 0, sizeof(ccb)); + memset(iobuf, 0, sizeof(iobuf)); + ccb[0] = ATAPI_CMD_START_STOP; + ccb[4] = 0x03; /* start */ + + c = atapi_issue_autoreq(device, ccb, 12, (unsigned char *)iobuf, 0); + + debug("ATAPI_CMD_START_STOP returned %x\n", c); + if (c != 0) + return; + + memset(ccb, 0, sizeof(ccb)); + memset(iobuf, 0, sizeof(iobuf)); + c = atapi_issue_autoreq(device, ccb, 12, (unsigned char *)iobuf, 0); + + debug("ATAPI_CMD_UNIT_TEST_READY returned %x\n", c); + if (c != 0) + return; + + memset(ccb, 0, sizeof(ccb)); + memset(iobuf, 0, sizeof(iobuf)); + ccb[0] = ATAPI_CMD_READ_CAP; + c = atapi_issue_autoreq(device, ccb, 12, (unsigned char *)iobuf, 8); + debug("ATAPI_CMD_READ_CAP returned %x\n", c); + if (c != 0) + return; + + debug("Read Cap: LBA %02X%02X%02X%02X blksize %02X%02X%02X%02X\n", + iobuf[0], iobuf[1], iobuf[2], iobuf[3], + iobuf[4], iobuf[5], iobuf[6], iobuf[7]); + + dev_desc->lba = ((unsigned long) iobuf[0] << 24) + + ((unsigned long) iobuf[1] << 16) + + ((unsigned long) iobuf[2] << 8) + ((unsigned long) iobuf[3]); + dev_desc->blksz = ((unsigned long) iobuf[4] << 24) + + ((unsigned long) iobuf[5] << 16) + + ((unsigned long) iobuf[6] << 8) + ((unsigned long) iobuf[7]); + dev_desc->log2blksz = LOG2(dev_desc->blksz); +#ifdef CONFIG_LBA48 + /* ATAPI devices cannot use 48bit addressing (ATA/ATAPI v7) */ + dev_desc->lba48 = 0; +#endif + return; +} + +#endif /* CONFIG_ATAPI */ + +static void ide_ident(struct blk_desc *dev_desc) +{ + unsigned char c; + hd_driveid_t iop; + +#ifdef CONFIG_ATAPI + int retries = 0; +#endif + int device; + + device = dev_desc->devnum; + printf(" Device %d: ", device); + + /* Select device + */ + ide_outb(device, ATA_DEV_HD, ATA_LBA | ATA_DEVICE(device)); + dev_desc->if_type = IF_TYPE_IDE; +#ifdef CONFIG_ATAPI + + retries = 0; + + /* Warning: This will be tricky to read */ + while (retries <= 1) { + /* check signature */ + if ((ide_inb(device, ATA_SECT_CNT) == 0x01) && + (ide_inb(device, ATA_SECT_NUM) == 0x01) && + (ide_inb(device, ATA_CYL_LOW) == 0x14) && + (ide_inb(device, ATA_CYL_HIGH) == 0xEB)) { + /* ATAPI Signature found */ + dev_desc->if_type = IF_TYPE_ATAPI; + /* + * Start Ident Command + */ + ide_outb(device, ATA_COMMAND, ATA_CMD_ID_ATAPI); + /* + * Wait for completion - ATAPI devices need more time + * to become ready + */ + c = ide_wait(device, ATAPI_TIME_OUT); + } else +#endif + { + /* + * Start Ident Command + */ + ide_outb(device, ATA_COMMAND, ATA_CMD_ID_ATA); + + /* + * Wait for completion + */ + c = ide_wait(device, IDE_TIME_OUT); + } + + if (((c & ATA_STAT_DRQ) == 0) || + ((c & (ATA_STAT_FAULT | ATA_STAT_ERR)) != 0)) { +#ifdef CONFIG_ATAPI + { + /* + * Need to soft reset the device + * in case it's an ATAPI... + */ + debug("Retrying...\n"); + ide_outb(device, ATA_DEV_HD, + ATA_LBA | ATA_DEVICE(device)); + udelay(100000); + ide_outb(device, ATA_COMMAND, 0x08); + udelay(500000); /* 500 ms */ + } + /* + * Select device + */ + ide_outb(device, ATA_DEV_HD, + ATA_LBA | ATA_DEVICE(device)); + retries++; +#else + return; +#endif + } +#ifdef CONFIG_ATAPI + else + break; + } /* see above - ugly to read */ + + if (retries == 2) /* Not found */ + return; +#endif + + ide_input_swap_data(device, (ulong *)&iop, ATA_SECTORWORDS); + + ident_cpy((unsigned char *)dev_desc->revision, iop.fw_rev, + sizeof(dev_desc->revision)); + ident_cpy((unsigned char *)dev_desc->vendor, iop.model, + sizeof(dev_desc->vendor)); + ident_cpy((unsigned char *)dev_desc->product, iop.serial_no, + sizeof(dev_desc->product)); + + if ((iop.config & 0x0080) == 0x0080) + dev_desc->removable = 1; + else + dev_desc->removable = 0; + +#ifdef CONFIG_ATAPI + if (dev_desc->if_type == IF_TYPE_ATAPI) { + atapi_inquiry(dev_desc); + return; + } +#endif /* CONFIG_ATAPI */ + + iop.lba_capacity[0] = be16_to_cpu(iop.lba_capacity[0]); + iop.lba_capacity[1] = be16_to_cpu(iop.lba_capacity[1]); + dev_desc->lba = + ((unsigned long)iop.lba_capacity[0]) | + ((unsigned long)iop.lba_capacity[1] << 16); + +#ifdef CONFIG_LBA48 + if (iop.command_set_2 & 0x0400) { /* LBA 48 support */ + dev_desc->lba48 = 1; + for (int i = 0; i < 4; i++) + iop.lba48_capacity[i] = be16_to_cpu(iop.lba48_capacity[i]); + dev_desc->lba = + ((unsigned long long)iop.lba48_capacity[0] | + ((unsigned long long)iop.lba48_capacity[1] << 16) | + ((unsigned long long)iop.lba48_capacity[2] << 32) | + ((unsigned long long)iop.lba48_capacity[3] << 48)); + } else { + dev_desc->lba48 = 0; + } +#endif /* CONFIG_LBA48 */ + /* assuming HD */ + dev_desc->type = DEV_TYPE_HARDDISK; + dev_desc->blksz = ATA_BLOCKSIZE; + dev_desc->log2blksz = LOG2(dev_desc->blksz); + dev_desc->lun = 0; /* just to fill something in... */ + +#if 0 /* only used to test the powersaving mode, + * if enabled, the drive goes after 5 sec + * in standby mode */ + ide_outb(device, ATA_DEV_HD, ATA_LBA | ATA_DEVICE(device)); + c = ide_wait(device, IDE_TIME_OUT); + ide_outb(device, ATA_SECT_CNT, 1); + ide_outb(device, ATA_LBA_LOW, 0); + ide_outb(device, ATA_LBA_MID, 0); + ide_outb(device, ATA_LBA_HIGH, 0); + ide_outb(device, ATA_DEV_HD, ATA_LBA | ATA_DEVICE(device)); + ide_outb(device, ATA_COMMAND, 0xe3); + udelay(50); + c = ide_wait(device, IDE_TIME_OUT); /* can't take over 500 ms */ +#endif +} + +__weak void ide_outb(int dev, int port, unsigned char val) +{ + debug("ide_outb (dev= %d, port= 0x%x, val= 0x%02x) : @ 0x%08lx\n", + dev, port, val, + (ATA_CURR_BASE(dev) + CONFIG_SYS_ATA_PORT_ADDR(port))); + +#if defined(CONFIG_IDE_AHB) + if (port) { + /* write command */ + ide_write_register(dev, port, val); + } else { + /* write data */ + outb(val, (ATA_CURR_BASE(dev))); + } +#else + outb(val, (ATA_CURR_BASE(dev) + CONFIG_SYS_ATA_PORT_ADDR(port))); +#endif +} + +__weak unsigned char ide_inb(int dev, int port) +{ + uchar val; + +#if defined(CONFIG_IDE_AHB) + val = ide_read_register(dev, port); +#else + val = inb((ATA_CURR_BASE(dev) + CONFIG_SYS_ATA_PORT_ADDR(port))); +#endif + + debug("ide_inb (dev= %d, port= 0x%x) : @ 0x%08lx -> 0x%02x\n", + dev, port, + (ATA_CURR_BASE(dev) + CONFIG_SYS_ATA_PORT_ADDR(port)), val); + return val; +} + +void ide_init(void) +{ + unsigned char c; + int i, bus; + +#ifdef CONFIG_IDE_PREINIT + WATCHDOG_RESET(); + + if (ide_preinit()) { + puts("ide_preinit failed\n"); + return; + } +#endif /* CONFIG_IDE_PREINIT */ + + WATCHDOG_RESET(); + + /* ATAPI Drives seems to need a proper IDE Reset */ + ide_reset(); + + /* + * Wait for IDE to get ready. + * According to spec, this can take up to 31 seconds! + */ + for (bus = 0; bus < CONFIG_SYS_IDE_MAXBUS; ++bus) { + int dev = + bus * (CONFIG_SYS_IDE_MAXDEVICE / + CONFIG_SYS_IDE_MAXBUS); + + printf("Bus %d: ", bus); + + ide_bus_ok[bus] = 0; + + /* Select device + */ + udelay(100000); /* 100 ms */ + ide_outb(dev, ATA_DEV_HD, ATA_LBA | ATA_DEVICE(dev)); + udelay(100000); /* 100 ms */ + i = 0; + do { + udelay(10000); /* 10 ms */ + + c = ide_inb(dev, ATA_STATUS); + i++; + if (i > (ATA_RESET_TIME * 100)) { + puts("** Timeout **\n"); + return; + } + if ((i >= 100) && ((i % 100) == 0)) + putc('.'); + + } while (c & ATA_STAT_BUSY); + + if (c & (ATA_STAT_BUSY | ATA_STAT_FAULT)) { + puts("not available "); + debug("Status = 0x%02X ", c); +#ifndef CONFIG_ATAPI /* ATAPI Devices do not set DRDY */ + } else if ((c & ATA_STAT_READY) == 0) { + puts("not available "); + debug("Status = 0x%02X ", c); +#endif + } else { + puts("OK "); + ide_bus_ok[bus] = 1; + } + WATCHDOG_RESET(); + } + + putc('\n'); + + for (i = 0; i < CONFIG_SYS_IDE_MAXDEVICE; ++i) { + ide_dev_desc[i].type = DEV_TYPE_UNKNOWN; + ide_dev_desc[i].if_type = IF_TYPE_IDE; + ide_dev_desc[i].devnum = i; + ide_dev_desc[i].part_type = PART_TYPE_UNKNOWN; + ide_dev_desc[i].blksz = 0; + ide_dev_desc[i].log2blksz = + LOG2_INVALID(typeof(ide_dev_desc[i].log2blksz)); + ide_dev_desc[i].lba = 0; +#ifndef CONFIG_BLK + ide_dev_desc[i].block_read = ide_read; + ide_dev_desc[i].block_write = ide_write; +#endif + if (!ide_bus_ok[IDE_BUS(i)]) + continue; + ide_ident(&ide_dev_desc[i]); + dev_print(&ide_dev_desc[i]); + +#ifndef CONFIG_BLK + if ((ide_dev_desc[i].lba > 0) && (ide_dev_desc[i].blksz > 0)) { + /* initialize partition type */ + part_init(&ide_dev_desc[i]); + } +#endif + } + WATCHDOG_RESET(); + +#ifdef CONFIG_BLK + struct udevice *dev; + + uclass_first_device(UCLASS_IDE, &dev); +#endif +} + +__weak void ide_input_swap_data(int dev, ulong *sect_buf, int words) +{ + uintptr_t paddr = (ATA_CURR_BASE(dev) + ATA_DATA_REG); + ushort *dbuf = (ushort *)sect_buf; + + debug("in input swap data base for read is %p\n", (void *)paddr); + + while (words--) { + EIEIO; + *dbuf++ = be16_to_cpu(inw(paddr)); + EIEIO; + *dbuf++ = be16_to_cpu(inw(paddr)); + } +} + +__weak void ide_output_data(int dev, const ulong *sect_buf, int words) +{ +#if defined(CONFIG_IDE_AHB) + ide_write_data(dev, sect_buf, words); +#else + uintptr_t paddr = (ATA_CURR_BASE(dev) + ATA_DATA_REG); + ushort *dbuf; + + dbuf = (ushort *)sect_buf; + while (words--) { + EIEIO; + outw(cpu_to_le16(*dbuf++), paddr); + EIEIO; + outw(cpu_to_le16(*dbuf++), paddr); + } +#endif /* CONFIG_IDE_AHB */ +} + +__weak void ide_input_data(int dev, ulong *sect_buf, int words) +{ +#if defined(CONFIG_IDE_AHB) + ide_read_data(dev, sect_buf, words); +#else + uintptr_t paddr = (ATA_CURR_BASE(dev) + ATA_DATA_REG); + ushort *dbuf; + + dbuf = (ushort *)sect_buf; + + debug("in input data base for read is %p\n", (void *)paddr); + + while (words--) { + EIEIO; + *dbuf++ = le16_to_cpu(inw(paddr)); + EIEIO; + *dbuf++ = le16_to_cpu(inw(paddr)); + } +#endif /* CONFIG_IDE_AHB */ +} + +#ifdef CONFIG_BLK +ulong ide_read(struct udevice *dev, lbaint_t blknr, lbaint_t blkcnt, + void *buffer) +#else +ulong ide_read(struct blk_desc *block_dev, lbaint_t blknr, lbaint_t blkcnt, + void *buffer) +#endif +{ +#ifdef CONFIG_BLK + struct blk_desc *block_dev = dev_get_uclass_plat(dev); +#endif + int device = block_dev->devnum; + ulong n = 0; + unsigned char c; + unsigned char pwrsave = 0; /* power save */ + +#ifdef CONFIG_LBA48 + unsigned char lba48 = 0; + + if (blknr & 0x0000fffff0000000ULL) { + /* more than 28 bits used, use 48bit mode */ + lba48 = 1; + } +#endif + debug("ide_read dev %d start " LBAF ", blocks " LBAF " buffer at %lX\n", + device, blknr, blkcnt, (ulong) buffer); + + /* Select device + */ + ide_outb(device, ATA_DEV_HD, ATA_LBA | ATA_DEVICE(device)); + c = ide_wait(device, IDE_TIME_OUT); + + if (c & ATA_STAT_BUSY) { + printf("IDE read: device %d not ready\n", device); + goto IDE_READ_E; + } + + /* first check if the drive is in Powersaving mode, if yes, + * increase the timeout value */ + ide_outb(device, ATA_COMMAND, ATA_CMD_CHK_POWER); + udelay(50); + + c = ide_wait(device, IDE_TIME_OUT); /* can't take over 500 ms */ + + if (c & ATA_STAT_BUSY) { + printf("IDE read: device %d not ready\n", device); + goto IDE_READ_E; + } + if ((c & ATA_STAT_ERR) == ATA_STAT_ERR) { + printf("No Powersaving mode %X\n", c); + } else { + c = ide_inb(device, ATA_SECT_CNT); + debug("Powersaving %02X\n", c); + if (c == 0) + pwrsave = 1; + } + + + while (blkcnt-- > 0) { + c = ide_wait(device, IDE_TIME_OUT); + + if (c & ATA_STAT_BUSY) { + printf("IDE read: device %d not ready\n", device); + break; + } +#ifdef CONFIG_LBA48 + if (lba48) { + /* write high bits */ + ide_outb(device, ATA_SECT_CNT, 0); + ide_outb(device, ATA_LBA_LOW, (blknr >> 24) & 0xFF); +#ifdef CONFIG_SYS_64BIT_LBA + ide_outb(device, ATA_LBA_MID, (blknr >> 32) & 0xFF); + ide_outb(device, ATA_LBA_HIGH, (blknr >> 40) & 0xFF); +#else + ide_outb(device, ATA_LBA_MID, 0); + ide_outb(device, ATA_LBA_HIGH, 0); +#endif + } +#endif + ide_outb(device, ATA_SECT_CNT, 1); + ide_outb(device, ATA_LBA_LOW, (blknr >> 0) & 0xFF); + ide_outb(device, ATA_LBA_MID, (blknr >> 8) & 0xFF); + ide_outb(device, ATA_LBA_HIGH, (blknr >> 16) & 0xFF); + +#ifdef CONFIG_LBA48 + if (lba48) { + ide_outb(device, ATA_DEV_HD, + ATA_LBA | ATA_DEVICE(device)); + ide_outb(device, ATA_COMMAND, ATA_CMD_PIO_READ_EXT); + + } else +#endif + { + ide_outb(device, ATA_DEV_HD, ATA_LBA | + ATA_DEVICE(device) | ((blknr >> 24) & 0xF)); + ide_outb(device, ATA_COMMAND, ATA_CMD_PIO_READ); + } + + udelay(50); + + if (pwrsave) { + /* may take up to 4 sec */ + c = ide_wait(device, IDE_SPIN_UP_TIME_OUT); + pwrsave = 0; + } else { + /* can't take over 500 ms */ + c = ide_wait(device, IDE_TIME_OUT); + } + + if ((c & (ATA_STAT_DRQ | ATA_STAT_BUSY | ATA_STAT_ERR)) != + ATA_STAT_DRQ) { + printf("Error (no IRQ) dev %d blk " LBAF + ": status %#02x\n", device, blknr, c); + break; + } + + ide_input_data(device, buffer, ATA_SECTORWORDS); + (void) ide_inb(device, ATA_STATUS); /* clear IRQ */ + + ++n; + ++blknr; + buffer += ATA_BLOCKSIZE; + } +IDE_READ_E: + return n; +} + +#ifdef CONFIG_BLK +ulong ide_write(struct udevice *dev, lbaint_t blknr, lbaint_t blkcnt, + const void *buffer) +#else +ulong ide_write(struct blk_desc *block_dev, lbaint_t blknr, lbaint_t blkcnt, + const void *buffer) +#endif +{ +#ifdef CONFIG_BLK + struct blk_desc *block_dev = dev_get_uclass_plat(dev); +#endif + int device = block_dev->devnum; + ulong n = 0; + unsigned char c; + +#ifdef CONFIG_LBA48 + unsigned char lba48 = 0; + + if (blknr & 0x0000fffff0000000ULL) { + /* more than 28 bits used, use 48bit mode */ + lba48 = 1; + } +#endif + + /* Select device + */ + ide_outb(device, ATA_DEV_HD, ATA_LBA | ATA_DEVICE(device)); + + while (blkcnt-- > 0) { + c = ide_wait(device, IDE_TIME_OUT); + + if (c & ATA_STAT_BUSY) { + printf("IDE read: device %d not ready\n", device); + goto WR_OUT; + } +#ifdef CONFIG_LBA48 + if (lba48) { + /* write high bits */ + ide_outb(device, ATA_SECT_CNT, 0); + ide_outb(device, ATA_LBA_LOW, (blknr >> 24) & 0xFF); +#ifdef CONFIG_SYS_64BIT_LBA + ide_outb(device, ATA_LBA_MID, (blknr >> 32) & 0xFF); + ide_outb(device, ATA_LBA_HIGH, (blknr >> 40) & 0xFF); +#else + ide_outb(device, ATA_LBA_MID, 0); + ide_outb(device, ATA_LBA_HIGH, 0); +#endif + } +#endif + ide_outb(device, ATA_SECT_CNT, 1); + ide_outb(device, ATA_LBA_LOW, (blknr >> 0) & 0xFF); + ide_outb(device, ATA_LBA_MID, (blknr >> 8) & 0xFF); + ide_outb(device, ATA_LBA_HIGH, (blknr >> 16) & 0xFF); + +#ifdef CONFIG_LBA48 + if (lba48) { + ide_outb(device, ATA_DEV_HD, + ATA_LBA | ATA_DEVICE(device)); + ide_outb(device, ATA_COMMAND, ATA_CMD_PIO_WRITE_EXT); + + } else +#endif + { + ide_outb(device, ATA_DEV_HD, ATA_LBA | + ATA_DEVICE(device) | ((blknr >> 24) & 0xF)); + ide_outb(device, ATA_COMMAND, ATA_CMD_PIO_WRITE); + } + + udelay(50); + + /* can't take over 500 ms */ + c = ide_wait(device, IDE_TIME_OUT); + + if ((c & (ATA_STAT_DRQ | ATA_STAT_BUSY | ATA_STAT_ERR)) != + ATA_STAT_DRQ) { + printf("Error (no IRQ) dev %d blk " LBAF + ": status %#02x\n", device, blknr, c); + goto WR_OUT; + } + + ide_output_data(device, buffer, ATA_SECTORWORDS); + c = ide_inb(device, ATA_STATUS); /* clear IRQ */ + ++n; + ++blknr; + buffer += ATA_BLOCKSIZE; + } +WR_OUT: + return n; +} + +#if defined(CONFIG_OF_IDE_FIXUP) +int ide_device_present(int dev) +{ + if (dev >= CONFIG_SYS_IDE_MAXBUS) + return 0; + return ide_dev_desc[dev].type == DEV_TYPE_UNKNOWN ? 0 : 1; +} +#endif + +#ifdef CONFIG_BLK +static int ide_blk_probe(struct udevice *udev) +{ + struct blk_desc *desc = dev_get_uclass_plat(udev); + + /* fill in device vendor/product/rev strings */ + strncpy(desc->vendor, ide_dev_desc[desc->devnum].vendor, + BLK_VEN_SIZE); + desc->vendor[BLK_VEN_SIZE] = '\0'; + strncpy(desc->product, ide_dev_desc[desc->devnum].product, + BLK_PRD_SIZE); + desc->product[BLK_PRD_SIZE] = '\0'; + strncpy(desc->revision, ide_dev_desc[desc->devnum].revision, + BLK_REV_SIZE); + desc->revision[BLK_REV_SIZE] = '\0'; + + return 0; +} + +static const struct blk_ops ide_blk_ops = { + .read = ide_read, + .write = ide_write, +}; + +U_BOOT_DRIVER(ide_blk) = { + .name = "ide_blk", + .id = UCLASS_BLK, + .ops = &ide_blk_ops, + .probe = ide_blk_probe, +}; + +static int ide_probe(struct udevice *udev) +{ + struct udevice *blk_dev; + char name[20]; + int blksz; + lbaint_t size; + int i; + int ret; + + for (i = 0; i < CONFIG_SYS_IDE_MAXDEVICE; i++) { + if (ide_dev_desc[i].type != DEV_TYPE_UNKNOWN) { + sprintf(name, "blk#%d", i); + + blksz = ide_dev_desc[i].blksz; + size = blksz * ide_dev_desc[i].lba; + + /* + * With CDROM, if there is no CD inserted, blksz will + * be zero, don't bother to create IDE block device. + */ + if (!blksz) + continue; + ret = blk_create_devicef(udev, "ide_blk", name, + IF_TYPE_IDE, i, + blksz, size, &blk_dev); + if (ret) + return ret; + } + } + + return 0; +} + +U_BOOT_DRIVER(ide) = { + .name = "ide", + .id = UCLASS_IDE, + .probe = ide_probe, +}; + +struct pci_device_id ide_supported[] = { + { PCI_DEVICE_CLASS(PCI_CLASS_STORAGE_IDE << 8, 0xffff00) }, + { } +}; + +U_BOOT_PCI_DEVICE(ide, ide_supported); + +UCLASS_DRIVER(ide) = { + .name = "ide", + .id = UCLASS_IDE, +}; +#else +U_BOOT_LEGACY_BLK(ide) = { + .if_typename = "ide", + .if_type = IF_TYPE_IDE, + .max_devs = CONFIG_SYS_IDE_MAXDEVICE, + .desc = ide_dev_desc, +}; +#endif diff --git a/roms/u-boot/drivers/block/sandbox.c b/roms/u-boot/drivers/block/sandbox.c new file mode 100644 index 000000000..1c2c3b4f8 --- /dev/null +++ b/roms/u-boot/drivers/block/sandbox.c @@ -0,0 +1,265 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2013 Henrik Nordstrom <henrik@henriknordstrom.net> + */ + +#include <common.h> +#include <blk.h> +#include <dm.h> +#include <fdtdec.h> +#include <part.h> +#include <os.h> +#include <malloc.h> +#include <sandboxblockdev.h> +#include <asm/global_data.h> +#include <dm/device_compat.h> +#include <linux/errno.h> +#include <dm/device-internal.h> + +DECLARE_GLOBAL_DATA_PTR; + +#ifndef CONFIG_BLK +static struct host_block_dev host_devices[CONFIG_HOST_MAX_DEVICES]; + +static struct host_block_dev *find_host_device(int dev) +{ + if (dev >= 0 && dev < CONFIG_HOST_MAX_DEVICES) + return &host_devices[dev]; + + return NULL; +} +#endif + +#ifdef CONFIG_BLK +static unsigned long host_block_read(struct udevice *dev, + unsigned long start, lbaint_t blkcnt, + void *buffer) +{ + struct host_block_dev *host_dev = dev_get_plat(dev); + struct blk_desc *block_dev = dev_get_uclass_plat(dev); + +#else +static unsigned long host_block_read(struct blk_desc *block_dev, + unsigned long start, lbaint_t blkcnt, + void *buffer) +{ + int dev = block_dev->devnum; + struct host_block_dev *host_dev = find_host_device(dev); + + if (!host_dev) + return -1; +#endif + + if (os_lseek(host_dev->fd, start * block_dev->blksz, OS_SEEK_SET) == + -1) { + printf("ERROR: Invalid block %lx\n", start); + return -1; + } + ssize_t len = os_read(host_dev->fd, buffer, blkcnt * block_dev->blksz); + if (len >= 0) + return len / block_dev->blksz; + return -1; +} + +#ifdef CONFIG_BLK +static unsigned long host_block_write(struct udevice *dev, + unsigned long start, lbaint_t blkcnt, + const void *buffer) +{ + struct host_block_dev *host_dev = dev_get_plat(dev); + struct blk_desc *block_dev = dev_get_uclass_plat(dev); +#else +static unsigned long host_block_write(struct blk_desc *block_dev, + unsigned long start, lbaint_t blkcnt, + const void *buffer) +{ + int dev = block_dev->devnum; + struct host_block_dev *host_dev = find_host_device(dev); +#endif + + if (os_lseek(host_dev->fd, start * block_dev->blksz, OS_SEEK_SET) == + -1) { + printf("ERROR: Invalid block %lx\n", start); + return -1; + } + ssize_t len = os_write(host_dev->fd, buffer, blkcnt * block_dev->blksz); + if (len >= 0) + return len / block_dev->blksz; + return -1; +} + +#ifdef CONFIG_BLK +int host_dev_bind(int devnum, char *filename, bool removable) +{ + struct host_block_dev *host_dev; + struct udevice *dev; + struct blk_desc *desc; + char dev_name[20], *str, *fname; + int ret, fd; + + /* Remove and unbind the old device, if any */ + ret = blk_get_device(IF_TYPE_HOST, devnum, &dev); + if (ret == 0) { + ret = device_remove(dev, DM_REMOVE_NORMAL); + if (ret) + return ret; + ret = device_unbind(dev); + if (ret) + return ret; + } else if (ret != -ENODEV) { + return ret; + } + + if (!filename) + return 0; + + snprintf(dev_name, sizeof(dev_name), "host%d", devnum); + str = strdup(dev_name); + if (!str) + return -ENOMEM; + fname = strdup(filename); + if (!fname) { + free(str); + return -ENOMEM; + } + + fd = os_open(filename, OS_O_RDWR); + if (fd == -1) { + printf("Failed to access host backing file '%s'\n", filename); + ret = -ENOENT; + goto err; + } + ret = blk_create_device(gd->dm_root, "sandbox_host_blk", str, + IF_TYPE_HOST, devnum, 512, + os_lseek(fd, 0, OS_SEEK_END) / 512, &dev); + if (ret) + goto err_file; + + host_dev = dev_get_plat(dev); + host_dev->fd = fd; + host_dev->filename = fname; + + ret = device_probe(dev); + if (ret) { + device_unbind(dev); + goto err_file; + } + + desc = blk_get_devnum_by_type(IF_TYPE_HOST, devnum); + desc->removable = removable; + snprintf(desc->vendor, BLK_VEN_SIZE, "U-Boot"); + snprintf(desc->product, BLK_PRD_SIZE, "hostfile"); + snprintf(desc->revision, BLK_REV_SIZE, "1.0"); + + return 0; +err_file: + os_close(fd); +err: + free(fname); + free(str); + return ret; +} +#else +int host_dev_bind(int dev, char *filename, bool removable) +{ + struct host_block_dev *host_dev = find_host_device(dev); + + if (!host_dev) + return -1; + if (host_dev->blk_dev.priv) { + os_close(host_dev->fd); + host_dev->blk_dev.priv = NULL; + } + if (host_dev->filename) + free(host_dev->filename); + if (filename && *filename) { + host_dev->filename = strdup(filename); + } else { + host_dev->filename = NULL; + return 0; + } + + host_dev->fd = os_open(host_dev->filename, OS_O_RDWR); + if (host_dev->fd == -1) { + printf("Failed to access host backing file '%s'\n", + host_dev->filename); + return 1; + } + + struct blk_desc *blk_dev = &host_dev->blk_dev; + blk_dev->if_type = IF_TYPE_HOST; + blk_dev->priv = host_dev; + blk_dev->blksz = 512; + blk_dev->lba = os_lseek(host_dev->fd, 0, OS_SEEK_END) / blk_dev->blksz; + blk_dev->block_read = host_block_read; + blk_dev->block_write = host_block_write; + blk_dev->devnum = dev; + blk_dev->part_type = PART_TYPE_UNKNOWN; + blk_dev->removable = removable; + snprintf(blk_dev->vendor, BLK_VEN_SIZE, "U-Boot"); + snprintf(blk_dev->product, BLK_PRD_SIZE, "hostfile"); + snprintf(blk_dev->revision, BLK_REV_SIZE, "1.0"); + part_init(blk_dev); + + return 0; +} +#endif + +int host_get_dev_err(int devnum, struct blk_desc **blk_devp) +{ +#ifdef CONFIG_BLK + struct udevice *dev; + int ret; + + ret = blk_get_device(IF_TYPE_HOST, devnum, &dev); + if (ret) + return ret; + *blk_devp = dev_get_uclass_plat(dev); +#else + struct host_block_dev *host_dev = find_host_device(devnum); + + if (!host_dev) + return -ENODEV; + + if (!host_dev->blk_dev.priv) + return -ENOENT; + + *blk_devp = &host_dev->blk_dev; +#endif + + return 0; +} + +#ifdef CONFIG_BLK + +int sandbox_host_unbind(struct udevice *dev) +{ + struct host_block_dev *host_dev; + + /* Data validity is checked in host_dev_bind() */ + host_dev = dev_get_plat(dev); + os_close(host_dev->fd); + + return 0; +} + +static const struct blk_ops sandbox_host_blk_ops = { + .read = host_block_read, + .write = host_block_write, +}; + +U_BOOT_DRIVER(sandbox_host_blk) = { + .name = "sandbox_host_blk", + .id = UCLASS_BLK, + .ops = &sandbox_host_blk_ops, + .unbind = sandbox_host_unbind, + .plat_auto = sizeof(struct host_block_dev), +}; +#else +U_BOOT_LEGACY_BLK(sandbox_host) = { + .if_typename = "host", + .if_type = IF_TYPE_HOST, + .max_devs = CONFIG_HOST_MAX_DEVICES, + .get_dev = host_get_dev_err, +}; +#endif |