diff options
Diffstat (limited to 'common/block.c')
-rw-r--r-- | common/block.c | 135 |
1 files changed, 101 insertions, 34 deletions
diff --git a/common/block.c b/common/block.c index 02be80d7cc..c7ca4fb403 100644 --- a/common/block.c +++ b/common/block.c @@ -1,36 +1,24 @@ +// SPDX-License-Identifier: GPL-2.0-only /* * block.c - simple block layer * * Copyright (c) 2011 Sascha Hauer <s.hauer@pengutronix.de>, Pengutronix - * - * 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 version 2 - * as published by the Free Software Foundation. - * - * 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. - * */ #include <common.h> #include <block.h> +#include <disks.h> #include <malloc.h> #include <linux/err.h> #include <linux/list.h> #include <dma.h> - -#define BLOCKSIZE(blk) (1 << blk->blockbits) +#include <file-list.h> LIST_HEAD(block_device_list); -/* a chunk of contigous data */ +/* a chunk of contiguous data */ struct chunk { void *data; /* data buffer */ - int block_start; /* first block in this chunk */ + sector_t block_start; /* first block in this chunk */ int dirty; /* need to write back to device */ int num; /* number of chunk, debugging only */ struct list_head list; @@ -40,7 +28,7 @@ struct chunk { static int writebuffer_io_len(struct block_device *blk, struct chunk *chunk) { - return min(blk->rdbufsize, blk->num_blocks - chunk->block_start); + return min_t(blkcnt_t, blk->rdbufsize, blk->num_blocks - chunk->block_start); } /* @@ -76,14 +64,14 @@ static int writebuffer_flush(struct block_device *blk) * get the chunk containing a given block. Will return NULL if the * block is not cached, the chunk otherwise. */ -static struct chunk *chunk_get_cached(struct block_device *blk, int block) +static struct chunk *chunk_get_cached(struct block_device *blk, sector_t block) { struct chunk *chunk; list_for_each_entry(chunk, &blk->buffered_blocks, list) { if (block >= chunk->block_start && block < chunk->block_start + blk->rdbufsize) { - dev_dbg(blk->dev, "%s: found %d in %d\n", __func__, + dev_dbg(blk->dev, "%s: found %llu in %d\n", __func__, block, chunk->num); /* * move most recently used entry to the head of the list @@ -100,7 +88,7 @@ static struct chunk *chunk_get_cached(struct block_device *blk, int block) * Get the data pointer for a given block. Will return NULL if * the block is not cached, the data pointer otherwise. */ -static void *block_get_cached(struct block_device *blk, int block) +static void *block_get_cached(struct block_device *blk, sector_t block) { struct chunk *chunk; @@ -147,7 +135,7 @@ static struct chunk *get_chunk(struct block_device *blk) * not cached already. By definition block_get_cached() for * the same block will succeed after this call. */ -static int block_cache(struct block_device *blk, int block) +static int block_cache(struct block_device *blk, sector_t block) { struct chunk *chunk; int ret; @@ -158,7 +146,7 @@ static int block_cache(struct block_device *blk, int block) chunk->block_start = block & ~blk->blkmask; - dev_dbg(blk->dev, "%s: %d to %d\n", __func__, chunk->block_start, + dev_dbg(blk->dev, "%s: %llu to %d\n", __func__, chunk->block_start, chunk->num); if (chunk->block_start * BLOCKSIZE(blk) >= blk->discard_start && @@ -184,7 +172,7 @@ static int block_cache(struct block_device *blk, int block) * Get the data for a block, either from the cache or from * the device. */ -static void *block_get(struct block_device *blk, int block) +static void *block_get(struct block_device *blk, sector_t block) { void *outdata; int ret; @@ -212,9 +200,9 @@ static ssize_t block_op_read(struct cdev *cdev, void *buf, size_t count, { struct block_device *blk = cdev->priv; unsigned long mask = BLOCKSIZE(blk) - 1; - unsigned long block = offset >> blk->blockbits; + sector_t block = offset >> blk->blockbits; size_t icount = count; - int blocks; + blkcnt_t blocks; if (offset & mask) { size_t now = BLOCKSIZE(blk) - (offset & mask); @@ -264,7 +252,7 @@ static ssize_t block_op_read(struct cdev *cdev, void *buf, size_t count, * Put data into a block. This only overwrites the data in the * cache and marks the corresponding chunk as dirty. */ -static int block_put(struct block_device *blk, const void *buf, int block) +static int block_put(struct block_device *blk, const void *buf, sector_t block) { struct chunk *chunk; void *data; @@ -289,9 +277,21 @@ static ssize_t block_op_write(struct cdev *cdev, const void *buf, size_t count, { struct block_device *blk = cdev->priv; unsigned long mask = BLOCKSIZE(blk) - 1; - unsigned long block = offset >> blk->blockbits; + sector_t block = offset >> blk->blockbits; size_t icount = count; - int blocks, ret; + blkcnt_t blocks; + int ret; + + /* + * When the offset that is written to is within the first two + * LBAs then the partition table has changed, reparse the partition + * table at close time in this case. A GPT covers more space than + * only the first two LBAs, but a CRC of the remaining pieces is + * written to LBA1, so LBA1 must change as well when the partioning + * is changed. + */ + if (offset < 2 * SECTOR_SIZE) + blk->need_reparse = true; if (offset & mask) { size_t now = BLOCKSIZE(blk) - (offset & mask); @@ -350,7 +350,19 @@ static int block_op_flush(struct cdev *cdev) return writebuffer_flush(blk); } -static int block_op_close(struct cdev *cdev) __alias(block_op_flush); +static int block_op_close(struct cdev *cdev) +{ + struct block_device *blk = cdev->priv; + + block_op_flush(cdev); + + if (blk->need_reparse) { + reparse_partition_table(blk); + blk->need_reparse = false; + } + + return 0; +} static int block_op_discard_range(struct cdev *cdev, loff_t count, loff_t offset) { @@ -372,6 +384,14 @@ static struct cdev_operations block_ops = { .discard_range = block_op_discard_range, }; +struct block_device *cdev_get_block_device(const struct cdev *cdev) +{ + if (!cdev || cdev->ops != &block_ops) + return NULL; + + return cdev->priv; +} + int blockdevice_register(struct block_device *blk) { loff_t size = (loff_t)blk->num_blocks * BLOCKSIZE(blk); @@ -391,6 +411,11 @@ int blockdevice_register(struct block_device *blk) dev_dbg(blk->dev, "rdbufsize: %d blockbits: %d blkmask: 0x%08x\n", blk->rdbufsize, blk->blockbits, blk->blkmask); + if (!blk->rdbufsize) { + pr_warn("block size of %u not supported\n", BLOCKSIZE(blk)); + return -ENOSYS; + } + for (i = 0; i < 8; i++) { struct chunk *chunk = xzalloc(sizeof(*chunk)); chunk->data = dma_alloc(BUFSIZE); @@ -406,6 +431,9 @@ int blockdevice_register(struct block_device *blk) cdev_create_default_automount(&blk->cdev); + /* Lack of partition table is unusual, but not a failure */ + (void)parse_partition_table(blk); + return 0; } @@ -431,24 +459,63 @@ int blockdevice_unregister(struct block_device *blk) return 0; } -int block_read(struct block_device *blk, void *buf, int block, int num_blocks) +int block_read(struct block_device *blk, void *buf, sector_t block, blkcnt_t num_blocks) { int ret; ret = cdev_read(&blk->cdev, buf, num_blocks << blk->blockbits, - (loff_t)block << blk->blockbits, 0); + block << blk->blockbits, 0); return ret < 0 ? ret : 0; } -int block_write(struct block_device *blk, void *buf, int block, int num_blocks) +int block_write(struct block_device *blk, void *buf, sector_t block, blkcnt_t num_blocks) { int ret; ret = cdev_write(&blk->cdev, buf, num_blocks << blk->blockbits, - (loff_t)block << blk->blockbits, 0); + block << blk->blockbits, 0); return ret < 0 ? ret : 0; } + +unsigned file_list_add_blockdevs(struct file_list *files) +{ + struct block_device *blk; + unsigned count = 0; + int err; + + list_for_each_entry(blk, &block_device_list, list) { + err = file_list_add_cdev_entry(files, &blk->cdev, 0); + if (!err) + count++; + } + + return count; +} + +const char *blk_type_str(enum blk_type type) +{ + switch (type) { + case BLK_TYPE_UNSPEC: + return "unspecified"; + case BLK_TYPE_SD: + return "SD"; + case BLK_TYPE_MMC: + return "MMC"; + case BLK_TYPE_VIRTUAL: + return "virtual"; + case BLK_TYPE_IDE: + return "IDE"; + case BLK_TYPE_AHCI: + return "AHCI"; + case BLK_TYPE_USB: + return "USB"; + case BLK_TYPE_NVME: + return "NVMe"; + default: + return "unknown"; + } +} |