From 09d71aac7b530f03ec29cc9d5787047460a629ca Mon Sep 17 00:00:00 2001 From: Simon Glass Date: Mon, 29 Feb 2016 15:25:55 -0700 Subject: [PATCH] dm: blk: Add a block-device uclass Add a uclass for block devices. These provide block-oriented data access, supporting reading, writing and erasing of whole blocks. Signed-off-by: Simon Glass Reviewed-by: Bin Meng Tested-by: Stephen Warren --- drivers/block/Kconfig | 11 +++ drivers/block/Makefile | 2 + drivers/block/blk-uclass.c | 175 +++++++++++++++++++++++++++++++++++++++++++++ include/blk.h | 145 +++++++++++++++++++++++++++++++++++++ include/dm/uclass-id.h | 1 + 5 files changed, 334 insertions(+) create mode 100644 drivers/block/blk-uclass.c diff --git a/drivers/block/Kconfig b/drivers/block/Kconfig index 990f768..f35c4d4 100644 --- a/drivers/block/Kconfig +++ b/drivers/block/Kconfig @@ -1,3 +1,14 @@ +config BLK + bool "Support block devices" + depends on DM + 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 DISK bool "Support disk controllers with driver model" depends on DM diff --git a/drivers/block/Makefile b/drivers/block/Makefile index 5eb87e0..b5c7ae1 100644 --- a/drivers/block/Makefile +++ b/drivers/block/Makefile @@ -5,6 +5,8 @@ # SPDX-License-Identifier: GPL-2.0+ # +obj-$(CONFIG_BLK) += blk-uclass.o + obj-$(CONFIG_DISK) += disk-uclass.o obj-$(CONFIG_SCSI_AHCI) += ahci.o obj-$(CONFIG_DWC_AHSATA) += dwc_ahsata.o diff --git a/drivers/block/blk-uclass.c b/drivers/block/blk-uclass.c new file mode 100644 index 0000000..49df2a6 --- /dev/null +++ b/drivers/block/blk-uclass.c @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2016 Google, Inc + * Written by Simon Glass + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include +#include +#include +#include +#include + +int blk_first_device(int if_type, struct udevice **devp) +{ + struct blk_desc *desc; + int ret; + + ret = uclass_first_device(UCLASS_BLK, devp); + if (ret) + return ret; + if (!*devp) + return -ENODEV; + do { + desc = dev_get_uclass_platdata(*devp); + if (desc->if_type == if_type) + return 0; + ret = uclass_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_platdata(*devp); + if_type = desc->if_type; + do { + ret = uclass_next_device(devp); + if (ret) + return ret; + if (!*devp) + return -ENODEV; + desc = dev_get_uclass_platdata(*devp); + if (desc->if_type == if_type) + return 0; + } while (1); +} + +int blk_get_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_platdata(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 device_probe(dev); + } + } + + return -ENODEV; +} + +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); + + if (!ops->read) + return -ENOSYS; + + return ops->read(dev, start, blkcnt, buffer); +} + +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; + + 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; + + return ops->erase(dev, start, blkcnt); +} + +int blk_prepare_device(struct udevice *dev) +{ + struct blk_desc *desc = dev_get_uclass_platdata(dev); + + part_init(desc); + + return 0; +} + +int blk_create_device(struct udevice *parent, const char *drv_name, + const char *name, int if_type, int devnum, int blksz, + lbaint_t size, struct udevice **devp) +{ + struct blk_desc *desc; + struct udevice *dev; + int ret; + + ret = device_bind_driver(parent, drv_name, name, &dev); + if (ret) + return ret; + desc = dev_get_uclass_platdata(dev); + desc->if_type = if_type; + desc->blksz = blksz; + desc->lba = size / blksz; + desc->part_type = PART_TYPE_UNKNOWN; + desc->bdev = dev; + desc->devnum = devnum; + *devp = dev; + + 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_platdata(dev); + + if (desc->if_type == if_type) { + ret = device_remove(dev); + if (ret) + return ret; + ret = device_unbind(dev); + if (ret) + return ret; + } + } + + return 0; +} + +UCLASS_DRIVER(blk) = { + .id = UCLASS_BLK, + .name = "blk", + .per_device_platdata_auto_alloc_size = sizeof(struct blk_desc), +}; diff --git a/include/blk.h b/include/blk.h index 7b2e5e2..e83c144 100644 --- a/include/blk.h +++ b/include/blk.h @@ -34,7 +34,15 @@ enum if_type { IF_TYPE_COUNT, /* Number of interface types */ }; +/* + * With driver model (CONFIG_BLK) this is uclass platform data, accessible + * with dev_get_uclass_platdata(dev) + */ struct blk_desc { + /* + * TODO: With driver model we should be able to use the parent + * device's uclass instead. + */ enum if_type if_type; /* type of the interface */ int devnum; /* device number */ unsigned char part_type; /* partition type */ @@ -53,6 +61,9 @@ struct blk_desc { char vendor[40+1]; /* IDE model, SCSI Vendor */ char product[20+1]; /* IDE Serial no, SCSI product */ char revision[8+1]; /* firmware revision */ +#ifdef CONFIG_BLK + struct udevice *bdev; +#else unsigned long (*block_read)(struct blk_desc *block_dev, lbaint_t start, lbaint_t blkcnt, @@ -65,12 +76,145 @@ struct blk_desc { lbaint_t start, lbaint_t blkcnt); void *priv; /* driver private struct pointer */ +#endif }; #define BLOCK_CNT(size, blk_desc) (PAD_COUNT(size, blk_desc->blksz)) #define PAD_TO_BLOCKSIZE(size, blk_desc) \ (PAD_SIZE(size, blk_desc->blksz)) +#ifdef CONFIG_BLK +struct udevice; + +/* Operations on block devices */ +struct blk_ops { + /** + * read() - read from a block device + * + * @dev: Device to read from + * @start: Start block number to read (0=first) + * @blkcnt: Number of blocks to read + * @buffer: Destination buffer for data read + * @return number of blocks read, or -ve error number (see the + * IS_ERR_VALUE() macro + */ + unsigned long (*read)(struct udevice *dev, lbaint_t start, + lbaint_t blkcnt, void *buffer); + + /** + * write() - write to a block device + * + * @dev: Device to write to + * @start: Start block number to write (0=first) + * @blkcnt: Number of blocks to write + * @buffer: Source buffer for data to write + * @return number of blocks written, or -ve error number (see the + * IS_ERR_VALUE() macro + */ + unsigned long (*write)(struct udevice *dev, lbaint_t start, + lbaint_t blkcnt, const void *buffer); + + /** + * erase() - erase a section of a block device + * + * @dev: Device to (partially) erase + * @start: Start block number to erase (0=first) + * @blkcnt: Number of blocks to erase + * @return number of blocks erased, or -ve error number (see the + * IS_ERR_VALUE() macro + */ + unsigned long (*erase)(struct udevice *dev, lbaint_t start, + lbaint_t blkcnt); +}; + +#define blk_get_ops(dev) ((struct blk_ops *)(dev)->driver->ops) + +/* + * These functions should take struct udevice instead of struct blk_desc, + * but this is convenient for migration to driver model. Add a 'd' prefix + * to the function operations, so that blk_read(), etc. can be reserved for + * functions with the correct arguments. + */ +unsigned long blk_dread(struct blk_desc *block_dev, lbaint_t start, + lbaint_t blkcnt, void *buffer); +unsigned long blk_dwrite(struct blk_desc *block_dev, lbaint_t start, + lbaint_t blkcnt, const void *buffer); +unsigned long blk_derase(struct blk_desc *block_dev, lbaint_t start, + lbaint_t blkcnt); + +/** + * blk_get_device() - Find and probe a block device ready for use + * + * @if_type: Interface type (enum if_type_t) + * @devnum: Device number (specific to each interface type) + * @devp: the device, if found + * @return - if found, -ENODEV if no device found, or other -ve error value + */ +int blk_get_device(int if_type, int devnum, struct udevice **devp); + +/** + * blk_first_device() - Find the first device for a given interface + * + * The device is probed ready for use + * + * @devnum: Device number (specific to each interface type) + * @devp: the device, if found + * @return 0 if found, -ENODEV if no device, or other -ve error value + */ +int blk_first_device(int if_type, struct udevice **devp); + +/** + * blk_next_device() - Find the next device for a given interface + * + * This can be called repeatedly after blk_first_device() to iterate through + * all devices of the given interface type. + * + * The device is probed ready for use + * + * @devp: On entry, the previous device returned. On exit, the next + * device, if found + * @return 0 if found, -ENODEV if no device, or other -ve error value + */ +int blk_next_device(struct udevice **devp); + +/** + * blk_create_device() - Create a new block device + * + * @parent: Parent of the new device + * @drv_name: Driver name to use for the block device + * @name: Name for the device + * @if_type: Interface type (enum if_type_t) + * @devnum: Device number, specific to the interface type + * @blksz: Block size of the device in bytes (typically 512) + * @size: Total size of the device in bytes + * @devp: the new device (which has not been probed) + */ +int blk_create_device(struct udevice *parent, const char *drv_name, + const char *name, int if_type, int devnum, int blksz, + lbaint_t size, struct udevice **devp); + +/** + * blk_prepare_device() - Prepare a block device for use + * + * This reads partition information from the device if supported. + * + * @dev: Device to prepare + * @return 0 if ok, -ve on error + */ +int blk_prepare_device(struct udevice *dev); + +/** + * blk_unbind_all() - Unbind all device of the given interface type + * + * The devices are removed and then unbound. + * + * @if_type: Interface type to unbind + * @return 0 if OK, -ve on error + */ +int blk_unbind_all(int if_type); + +#else +#include /* * These functions should take struct udevice instead of struct blk_desc, * but this is convenient for migration to driver model. Add a 'd' prefix @@ -99,5 +243,6 @@ static inline ulong blk_derase(struct blk_desc *block_dev, lbaint_t start, { return block_dev->block_erase(block_dev, start, blkcnt); } +#endif /* !CONFIG_BLK */ #endif diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h index 3bea308..37c4176 100644 --- a/include/dm/uclass-id.h +++ b/include/dm/uclass-id.h @@ -26,6 +26,7 @@ enum uclass_id { /* U-Boot uclasses start here - in alphabetical order */ UCLASS_ADC, /* Analog-to-digital converter */ + UCLASS_BLK, /* Block device */ UCLASS_CLK, /* Clock source, e.g. used by peripherals */ UCLASS_CPU, /* CPU, typically part of an SoC */ UCLASS_CROS_EC, /* Chrome OS EC */