
ata blockdev is an universal child of a blockctrl device, be it (P/S)ATA or SCSI partition blockdev is a child of other blockdev drivers
Signed-off-by: Pavel Herrmann morpheus.ibis@gmail.com --- drivers/blockdev/Makefile | 2 +- drivers/blockdev/ata.c | 234 +++++++++++++++++++++++++++++++++++++++++++ drivers/blockdev/ata.h | 37 +++++++ drivers/blockdev/partition.c | 179 +++++++++++++++++++++++++++++++++ 4 files changed, 451 insertions(+), 1 deletion(-) create mode 100644 drivers/blockdev/ata.c create mode 100644 drivers/blockdev/ata.h create mode 100644 drivers/blockdev/partition.c
diff --git a/drivers/blockdev/Makefile b/drivers/blockdev/Makefile index a988924..668ffbd 100644 --- a/drivers/blockdev/Makefile +++ b/drivers/blockdev/Makefile @@ -21,7 +21,7 @@ include $(TOPDIR)/config.mk
LIB := $(obj)libblockdev.o
-COBJS-${CONFIG_DM_BLOCK} := core.o +COBJS-${CONFIG_DM_BLOCK} := core.o ata.o partition.o COBJS-${CONFIG_DOS_PARTITION} += part_types/part_dos.o
COBJS := $(COBJS-y) diff --git a/drivers/blockdev/ata.c b/drivers/blockdev/ata.c new file mode 100644 index 0000000..698dc18 --- /dev/null +++ b/drivers/blockdev/ata.c @@ -0,0 +1,234 @@ +/* + * (C) Copyright 2012 + * Pavel Herrmann morpheus.ibis@gmail.com + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include "ata.h" +#include <dm/blockctrl.h> +#include <dm/manager.h> +#include <common.h> +#include <malloc.h> + +static lbaint_t read(struct instance *dev, lbaint_t start, lbaint_t blkcnt, + void *buffer) +{ + if (!buffer) + return 0; + + struct blockdev_ata_platform_data *platform = dev->info->platform_data; + struct blockdev_ata_private_data *priv = dev->private_data; + /* check for access beyond disk boundary */ + if ((start + blkcnt) > priv->block_count) + blkcnt = (priv->block_count - start); + + return blockctrl_read(dev->bus, platform->port_number, + start, blkcnt, buffer); +} + +static lbaint_t write(struct instance *dev, lbaint_t start, lbaint_t blkcnt, + void *buffer) +{ + if (!buffer) + return 0; + + struct blockdev_ata_platform_data *platform = dev->info->platform_data; + struct blockdev_ata_private_data *priv = dev->private_data; + /* check for access beyond disk boundary */ + if ((start + blkcnt) > priv->block_count) + blkcnt = (priv->block_count - start); + + return blockctrl_write(dev->bus, platform->port_number, + start, blkcnt, buffer); +} + +static lbaint_t erase(struct instance *dev, lbaint_t start, lbaint_t blkcnt) +{ + /* TRIM is not supported on ATA, so we just pretend to do something */ + return blkcnt; +} + +static int get_option(struct instance *dev, enum blockdev_option_code op, + struct option *result) +{ + struct blockdev_ata_private_data *priv = dev->private_data; + struct blockdev_ata_platform_data *plat = dev->info->platform_data; + int port = plat->port_number; + + if (!result || !priv) + return -EINVAL; + + switch (op) { + case BLKD_OPT_IFTYPE: + result->flags = OPTION_TYPE_U; + result->data.data_u = BLOCKDEV_IFTYPE_ATA; + return 0; + case BLKD_OPT_TYPE: + result->flags = OPTION_TYPE_U; + result->data.data_u = BLOCKDEV_TYPE_HARDDISK; + return 0; + case BLKD_OPT_OFFSET: + result->flags = OPTION_TYPE_U; + result->data.data_u = 0; + return 0; + case BLKD_OPT_BLOCKSIZE: + result->flags = OPTION_TYPE_U; + result->data.data_u = priv->block_size; + return 0; + case BLKD_OPT_BLOCKCOUNT: + result->flags = OPTION_TYPE_U; + result->data.data_u = priv->block_count; + return 0; + case BLKD_OPT_VENDOR: + result->flags = OPTION_TYPE_S; + result->data.data_s = priv->vendor; + return 0; + case BLKD_OPT_PRODUCT: + result->flags = OPTION_TYPE_S; + result->data.data_s = priv->product; + return 0; + case BLKD_OPT_REVISION: + result->flags = OPTION_TYPE_S; + result->data.data_s = priv->revision; + return 0; + case BLKD_OPT_LBA48: + return blockctrl_get_port_option(dev->bus, port, + BLKP_OPT_LBA48, result); + default: + break; + /* TODO: more options */ + + } + return -EINVAL; +} + +static int set_option(struct instance *dev, enum blockdev_option_code op, + struct option *value) +{ + /* there are currently no meaningful options to set */ + return -EINVAL; +} + +static struct blockdev_ops ops = { + .read = read, + .write = write, + .erase = erase, + .get_option = get_option, + .set_option = set_option, +}; + + +static int bind(struct instance *dev) +{ + /* prepare hint for the core to properly identify this as a ATA disk */ + struct blockdev_core_hint hint = { + .iftype = BLOCKDEV_IFTYPE_ATA + }; + core_bind(CORE_BLOCKDEV, dev, &ops, &hint); + return 0; +} + +static int probe(struct instance *dev) +{ + struct option op; + struct blockdev_ata_private_data *priv; + int error; + struct blockdev_ata_platform_data *plat = dev->info->platform_data; + int port = plat->port_number; + + priv = calloc(sizeof(*priv), 1); + if (!priv) + return -ENOMEM; + + error = blockctrl_get_port_option(dev->bus, port, BLKP_OPT_BLOCKSIZE, + &op); + if (error || (OPTION_TYPE(op) != OPTION_TYPE_U)) + return -EINVAL; + priv->block_size = op.data.data_u; + + error = blockctrl_get_port_option(dev->bus, port, BLKP_OPT_BLOCKCOUNT, + &op); + if (error || (OPTION_TYPE(op) != OPTION_TYPE_U)) + return -EINVAL; + priv->block_count = op.data.data_u; + + error = blockctrl_get_port_option(dev->bus, port, BLKP_OPT_VENDOR, &op); + if (error || (OPTION_TYPE(op) != OPTION_TYPE_S)) + return -EINVAL; + strncpy(priv->vendor, op.data.data_s, 40); + priv->vendor[40] = 0; + if (op.flags & OPTION_PTR_MALLOCED) + free(op.data.data_s); + + error = blockctrl_get_port_option(dev->bus, port, BLKP_OPT_PRODUCT, + &op); + if (error || (OPTION_TYPE(op) != OPTION_TYPE_S)) + return -EINVAL; + strncpy(priv->product, op.data.data_s, 20); + priv->vendor[20] = 0; + if (op.flags & OPTION_PTR_MALLOCED) + free(op.data.data_s); + + error = blockctrl_get_port_option(dev->bus, port, BLKP_OPT_REVISION, + &op); + if (error || (OPTION_TYPE(op) != OPTION_TYPE_S)) + return -EINVAL; + strncpy(priv->revision, op.data.data_s, 8); + priv->revision[8] = 0; + if (op.flags & OPTION_PTR_MALLOCED) + free(op.data.data_s); + + dev->private_data = priv; + + return 0; +} + +static int reloc(struct instance *dev, struct instance *old) +{ + core_replace(CORE_BLOCKDEV, dev, old); + dev->private_data = malloc(sizeof(struct blockdev_ata_private_data)); + if (!dev->private_data) + return -ENOMEM; + + memcpy(dev->private_data, old->private_data, + sizeof(struct blockdev_ata_private_data)); + + return 0; +} + +static int remove(struct instance *dev) +{ + /* remove our metadata cache */ + free(dev->private_data); + return 0; +} + +static int unbind(struct instance *dev) +{ + core_unbind(CORE_BLOCKDEV, dev); + return 0; +} + +U_BOOT_DRIVER(blockdev_ata, + bind, + probe, + reloc, + remove, + unbind); diff --git a/drivers/blockdev/ata.h b/drivers/blockdev/ata.h new file mode 100644 index 0000000..da63b5d --- /dev/null +++ b/drivers/blockdev/ata.h @@ -0,0 +1,37 @@ +/* + * (C) Copyright 2012 + * Pavel Herrmann morpheus.ibis@gmail.com + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#ifndef _BLOCKDEV_ATA_H_ +#define _BLOCKDEV_ATA_H_ +#include <dm/blockdev.h> + +struct blockdev_ata_private_data { + /* cache for get_option() */ + lbaint_t block_size; + lbaint_t block_count; + char vendor[40+1]; + char product[20+1]; + char revision[8+1]; +}; + +#endif diff --git a/drivers/blockdev/partition.c b/drivers/blockdev/partition.c new file mode 100644 index 0000000..c5c0f48 --- /dev/null +++ b/drivers/blockdev/partition.c @@ -0,0 +1,179 @@ +/* + * (C) Copyright 2012 + * Pavel Herrmann morpheus.ibis@gmail.com + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + */ + +#include <dm/blockdev.h> +#include <dm/manager.h> + +static lbaint_t read(struct instance *dev, lbaint_t start, lbaint_t blkcnt, + void *buffer) +{ + if (!buffer) + return 0; + + struct blockdev_partition_platform_data *platform = + dev->info->platform_data; + /* check for access beyond partition boundary */ + if ((start + blkcnt) > platform->block_count) + blkcnt = (platform->block_count - start); + + start += platform->offset; + + return blockdev_read(dev->bus, start, blkcnt, buffer); +} + +static lbaint_t write(struct instance *dev, lbaint_t start, lbaint_t blkcnt, + void *buffer) +{ + if (!buffer) + return 0; + + struct blockdev_partition_platform_data *platform = + dev->info->platform_data; + /* check for access beyond partition boundary */ + if ((start + blkcnt) > platform->block_count) + blkcnt = (platform->block_count - start); + + start += platform->offset; + + return blockdev_write(dev->bus, start, blkcnt, buffer); +} + +static lbaint_t erase(struct instance *dev, lbaint_t start, lbaint_t blkcnt) +{ + struct blockdev_partition_platform_data *platform = + dev->info->platform_data; + /* check for access beyond partition boundary */ + if ((start + blkcnt) > platform->block_count) + blkcnt = (platform->block_count - start); + + start += platform->offset; + + return blockdev_erase(dev->bus, start, blkcnt); +} + +static int get_option(struct instance *dev, enum blockdev_option_code op, + struct option *result) +{ + if (!result) + return -EINVAL; + + struct blockdev_partition_platform_data *platform = + dev->info->platform_data; + + switch (op) { + case BLKD_OPT_TYPE: + result->flags = OPTION_TYPE_U; + result->data.data_u = BLOCKDEV_TYPE_PARTITION; + return 0; + case BLKD_OPT_BLOCKCOUNT: + result->flags = OPTION_TYPE_U; + result->data.data_u = platform->block_count; + return 0; + case BLKD_OPT_OFFSET: + result->flags = OPTION_TYPE_U; + result->data.data_u = platform->offset; + return 0; + case BLKD_OPT_IFTYPE: + case BLKD_OPT_BLOCKSIZE: + case BLKD_OPT_REMOVABLE: + case BLKD_OPT_LBA48: + case BLKD_OPT_VENDOR: + case BLKD_OPT_PRODUCT: + case BLKD_OPT_REVISION: + case BLKD_OPT_SCSILUN: + case BLKD_OPT_SCSITARGET: + return blockdev_get_option(dev->bus, op, result); + + default: + break; + } + + return -EINVAL; +} + + +static int set_option(struct instance *dev, enum blockdev_option_code op, + struct option *value) +{ + /* there are currently no meaningful options to set */ + return -EINVAL; +} + +static struct blockdev_ops ops = { + .read = read, + .write = write, + .erase = erase, + .get_option = get_option, + .set_option = set_option +}; + + +static int bind(struct instance *dev) +{ + /* prepare hint for the core to properly identify this as a partition */ + struct blockdev_partition_platform_data *platform = + dev->info->platform_data; + + struct blockdev_core_hint hint = { + .iftype = BLOCKDEV_IFTYPE_PARTITION, + .part_number = platform->part_number + }; + core_bind(CORE_BLOCKDEV, dev, &ops, &hint); + + return 0; +} + +static int probe(struct instance *dev) +{ + /* there is nothing to activate */ + return 0; +} + +static int reloc(struct instance *dev, struct instance *old) +{ + core_replace(CORE_BLOCKDEV, dev, old); + /* there are no private data to relocate */ + + return 0; +} + +static int remove(struct instance *dev) +{ + /* nothing to do here */ + return 0; +} + +static int unbind(struct instance *dev) +{ + core_unbind(CORE_BLOCKDEV, dev); + + return 0; +} + + +U_BOOT_DRIVER(blockdev_partition, + bind, + probe, + reloc, + remove, + unbind);