diff --git a/Documentation/user/usb.rst b/Documentation/user/usb.rst index ec219cf..8396f38 100644 --- a/Documentation/user/usb.rst +++ b/Documentation/user/usb.rst @@ -35,6 +35,43 @@ USB device support ------------------ +barebox supports several different USB gadget drivers: + +- Device Firmware Upgrade (DFU) +- Android Fastboot +- serial gadget + +The recommended way to use USB gadget is with the :ref:`command_usbgadget` command. +While there are individual commands for :ref:`command_dfu` and :ref:`command_usbserial`, +the :ref:`command_usbgadget` commands supports registering composite gadgets. + +Partition description +^^^^^^^^^^^^^^^^^^^^^ + +The USB gadget commands for Android Fastboot and DFU take a partition description +which describes which barebox partitions are exported via USB. The partition +description is referred to as ```` in the command help texts. It has +the general form ``partition(name)flags,partition(name)flags,...``. + +The **partition** field is the partition as accessible in barebox. This can be a +path in ``/dev/``, but could also be a regular file. + +The **name** field is the name under which the partition shall be exported. This +is the name under which the partition can be found with the host tool. + +Several **flags** are supported, each denoted by a single character: + +* ``s`` Safe mode. The file is downloaded completely before it is written (DFU specific) +* ``r`` Readback. The partition is allowed to be read back (DFU specific) +* ``c`` The file shall be created if it doesn't exist. Needed when a regular file is exported. +* ``u`` The partition is a MTD device and shall be flashed with a UBI image. + +Example: + +.. code-block:: sh + + /dev/nand0.barebox.bb(barebox)sr,/kernel(kernel)rc + DFU ^^^ @@ -43,16 +80,8 @@ devices. The current specification is version 1.1 and can be downloaded here: http://www.usb.org/developers/devclass_docs/DFU_1.1.pdf -On the barebox side, the update is handled with the :ref:`command_dfu` command. -It is passed a list of partitions to provide to the host. The partition list -has the form ``()``. ``file`` is the path to the device or -regular file which shall be updated. ``name`` is the name under which the partition -shall be provided to the host. For the possible ``flags`` see -:ref:`command_dfu`. A typical ``dfu`` command could look like this: - -.. code-block:: sh - - dfu "/dev/nand0.barebox.bb(barebox)sr,/dev/nand0.kernel.bb(kernel)r,/dev/nand0.root.bb(root)r" +On the barebox side, the update is handled with the :ref:`command_usbgadget` or the +:ref:`command_dfu` command. On the host side, the tool `dfu-util `_ can be used to update the partitions. It is available for most distributions and typically diff --git a/common/file-list.c b/common/file-list.c index 8d61b76..eb469cf 100644 --- a/common/file-list.c +++ b/common/file-list.c @@ -92,6 +92,9 @@ case 'c': flags |= FILE_LIST_FLAG_CREATE; break; + case 'u': + flags |= FILE_LIST_FLAG_UBI; + break; default: pr_err("Unknown flag '%c'\n", *partstr); return -EINVAL; diff --git a/common/filetype.c b/common/filetype.c index 323da02..f9c034f 100644 --- a/common/filetype.c +++ b/common/filetype.c @@ -25,6 +25,7 @@ #include #include #include +#include struct filetype_str { const char *name; /* human readable filetype */ @@ -64,6 +65,7 @@ [filetype_mxs_bootstream] = { "Freescale MXS bootstream", "mxsbs" }, [filetype_socfpga_xload] = { "SoCFPGA prebootloader image", "socfpga-xload" }, [filetype_kwbimage_v1] = { "MVEBU kwbimage (v1)", "kwb" }, + [filetype_android_sparse] = { "Android sparse image", "sparse" }, }; const char *file_type_to_string(enum filetype f) @@ -301,6 +303,9 @@ (buf8[0x1e] == 0 || buf8[0x1e] == 1)) return filetype_kwbimage_v1; + if (is_sparse_image(_buf)) + return filetype_android_sparse; + if (bufsize < 64) return filetype_unknown; diff --git a/common/ubiformat.c b/common/ubiformat.c index aaa1f5d..4c5f1f5 100644 --- a/common/ubiformat.c +++ b/common/ubiformat.c @@ -679,3 +679,64 @@ return err; } +int ubiformat_write(struct mtd_info *mtd, const void *buf, size_t count, + loff_t offset) +{ + int writesize = mtd->writesize >> mtd->subpage_sft; + size_t retlen; + int ret; + + if (offset & (mtd->writesize - 1)) + return -EINVAL; + + if (count & (mtd->writesize - 1)) + return -EINVAL; + + while (count) { + size_t now; + + now = ALIGN(offset, mtd->erasesize) - offset; + if (now > count) + now = count; + + if (!now) { + const struct ubi_ec_hdr *ec = buf; + const struct ubi_vid_hdr *vid; + + if (be32_to_cpu(ec->magic) != UBI_EC_HDR_MAGIC) { + pr_err("bad UBI magic %#08x, should be %#08x", + be32_to_cpu(ec->magic), UBI_EC_HDR_MAGIC); + return -EINVAL; + } + + /* skip ec header */ + offset += writesize; + buf += writesize; + count -= writesize; + + if (!count) + break; + + vid = buf; + if (be32_to_cpu(vid->magic) != UBI_VID_HDR_MAGIC) { + pr_err("bad UBI magic %#08x, should be %#08x", + be32_to_cpu(vid->magic), UBI_VID_HDR_MAGIC); + return -EINVAL; + } + + continue; + } + + ret = mtd_write(mtd, offset, now, &retlen, buf); + if (ret < 0) + return ret; + if (retlen != now) + return -EIO; + + buf += now; + count -= now; + offset += now; + } + + return 0; +} diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index 64347f0..b612d39 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -57,6 +57,7 @@ bool select BANNER select FILE_LIST + select IMAGE_SPARSE prompt "Android Fastboot support" endif diff --git a/drivers/usb/gadget/f_fastboot.c b/drivers/usb/gadget/f_fastboot.c index 85c64c0..2ba5977 100644 --- a/drivers/usb/gadget/f_fastboot.c +++ b/drivers/usb/gadget/f_fastboot.c @@ -32,11 +32,14 @@ #include #include #include +#include +#include #include #include #include #include #include +#include #include #include #include @@ -45,6 +48,7 @@ #include #include #include +#include #define FASTBOOT_VERSION "0.4" @@ -56,6 +60,8 @@ #define EP_BUFFER_SIZE 4096 +static unsigned int fastboot_max_download_size = SZ_8M; + struct fb_variable { char *name; char *value; @@ -316,6 +322,8 @@ fb_setvar(var, "0.4"); var = fb_addvar(f_fb, "bootloader-version"); fb_setvar(var, release_string); + var = fb_addvar(f_fb, "max-download-size"); + fb_setvar(var, "%u", fastboot_max_download_size); if (IS_ENABLED(CONFIG_BAREBOX_UPDATE) && opts->export_bbu) bbu_handlers_iterate(fastboot_add_bbu_variables, f_fb); @@ -526,7 +534,7 @@ return 0; } -static int fastboot_tx_print(struct f_fastboot *f_fb, const char *fmt, ...) +int fastboot_tx_print(struct f_fastboot *f_fb, const char *fmt, ...) { char buf[64]; va_list ap; @@ -687,6 +695,197 @@ fastboot_tx_print(f_fb, "OKAY"); } +static struct mtd_info *get_mtd(struct f_fastboot *f_fb, const char *filename) +{ + int fd, ret; + struct mtd_info_user meminfo; + + fd = open(filename, O_RDONLY); + if (fd < 0) + return ERR_PTR(-errno); + + ret = ioctl(fd, MEMGETINFO, &meminfo); + + close(fd); + + if (ret) + return ERR_PTR(ret); + + return meminfo.mtd; +} + +static int do_ubiformat(struct f_fastboot *f_fb, struct mtd_info *mtd, + const char *file) +{ + struct ubiformat_args args = { + .yes = 1, + .image = file, + }; + + if (!file) + args.novtbl = 1; + + if (!IS_ENABLED(CONFIG_UBIFORMAT)) { + fastboot_tx_print(f_fb, "FAILubiformat is not available"); + return -ENODEV; + } + + return ubiformat(mtd, &args); +} + + +static int check_ubi(struct f_fastboot *f_fb, struct file_list_entry *fentry, + enum filetype filetype) +{ + struct mtd_info *mtd; + + mtd = get_mtd(f_fb, fentry->filename); + + /* + * Issue a warning when we are about to write a UBI image to a MTD device + * and the FILE_LIST_FLAG_UBI is not given as this means we loose all + * erase counters. + */ + if (!IS_ERR(mtd) && filetype == filetype_ubi && + !(fentry->flags & FILE_LIST_FLAG_UBI)) { + fastboot_tx_print(f_fb, "INFOwriting UBI image to MTD device, " + "add the 'u' "); + fastboot_tx_print(f_fb, "INFOflag to the partition description"); + return 0; + } + + if (!(fentry->flags & FILE_LIST_FLAG_UBI)) + return 0; + + if (!IS_ENABLED(CONFIG_UBIFORMAT)) { + fastboot_tx_print(f_fb, "FAILformat not available"); + return -ENOSYS; + } + + if (IS_ERR(mtd)) { + fastboot_tx_print(f_fb, "FAILUBI flag given on non-MTD device"); + return -EINVAL; + } + + if (filetype == filetype_ubi) { + fastboot_tx_print(f_fb, "INFOThis is an UBI image..."); + return 1; + } else { + fastboot_tx_print(f_fb, "FAILThis is no UBI image but %s", + file_type_to_string(filetype)); + return -EINVAL; + } +} + +static int fastboot_handle_sparse(struct f_fastboot *f_fb, + struct file_list_entry *fentry) +{ + struct sparse_image_ctx *sparse; + void *buf = NULL; + int ret, fd; + unsigned int flags = O_RDWR; + int bufsiz = SZ_128K; + struct stat s; + struct mtd_info *mtd = NULL; + + ret = stat(fentry->filename, &s); + if (ret) { + if (fentry->flags & FILE_LIST_FLAG_CREATE) + flags |= O_CREAT; + else + return ret; + } + + fd = open(fentry->filename, flags); + if (fd < 0) + return -errno; + + ret = fstat(fd, &s); + if (ret) + goto out_close_fd; + + sparse = sparse_image_open(FASTBOOT_TMPFILE); + if (IS_ERR(sparse)) { + pr_err("Cannot open sparse image\n"); + ret = PTR_ERR(sparse); + goto out_close_fd; + } + + if (S_ISREG(s.st_mode)) { + ret = ftruncate(fd, sparse_image_size(sparse)); + if (ret) + goto out; + } + + buf = malloc(bufsiz); + if (!buf) { + ret = -ENOMEM; + goto out; + } + + if (fentry->flags & FILE_LIST_FLAG_UBI) { + mtd = get_mtd(f_fb, fentry->filename); + if (IS_ERR(mtd)) { + ret = PTR_ERR(mtd); + goto out; + } + } + + while (1) { + int retlen; + loff_t pos; + + ret = sparse_image_read(sparse, buf, &pos, bufsiz, &retlen); + if (ret) + goto out; + if (!retlen) + break; + + if (pos == 0) { + ret = check_ubi(f_fb, fentry, file_detect_type(buf, retlen)); + if (ret < 0) + goto out; + } + + if (fentry->flags & FILE_LIST_FLAG_UBI) { + if (!IS_ENABLED(CONFIG_UBIFORMAT)) { + ret = -ENOSYS; + goto out; + } + + if (pos == 0) { + ret = do_ubiformat(f_fb, mtd, NULL); + if (ret) + goto out; + } + + ret = ubiformat_write(mtd, buf, retlen, pos); + if (ret) + goto out; + } else { + pos = lseek(fd, pos, SEEK_SET); + if (pos == -1) { + ret = -errno; + goto out; + } + + ret = write_full(fd, buf, retlen); + if (ret < 0) + goto out; + } + } + + ret = 0; + +out: + free(buf); + sparse_image_close(sparse); +out_close_fd: + close(fd); + + return ret; +} + static void cb_flash(struct usb_ep *ep, struct usb_request *req, const char *cmd) { struct f_fastboot *f_fb = req->context; @@ -706,33 +905,27 @@ filename = fentry->filename; - if (filetype == filetype_ubi) { - int fd; - struct mtd_info_user meminfo; - struct ubiformat_args args = { - .yes = 1, - .image = FASTBOOT_TMPFILE, - }; - - fd = open(filename, O_RDONLY); - if (fd < 0) - goto copy; - - ret = ioctl(fd, MEMGETINFO, &meminfo); - close(fd); - /* Not a MTD device, ubiformat is not a valid operation */ - if (ret) - goto copy; - - fastboot_tx_print(f_fb, "INFOThis is an UBI image..."); - - if (!IS_ENABLED(CONFIG_UBIFORMAT)) { - fastboot_tx_print(f_fb, "FAILubiformat is not available"); + if (filetype == filetype_android_sparse) { + ret = fastboot_handle_sparse(f_fb, fentry); + if (ret) { + fastboot_tx_print(f_fb, "FAILwriting sparse image: %s", + strerror(-ret)); return; } - ret = ubiformat(meminfo.mtd, &args); + goto out; + } + ret = check_ubi(f_fb, fentry, filetype); + if (ret < 0) + return; + + if (ret > 0) { + struct mtd_info *mtd; + + mtd = get_mtd(f_fb, fentry->filename); + + ret = do_ubiformat(f_fb, mtd, FASTBOOT_TMPFILE); if (ret) { fastboot_tx_print(f_fb, "FAILwrite partition: %s", strerror(-ret)); return; @@ -973,3 +1166,17 @@ memset(req->buf, 0, EP_BUFFER_SIZE); usb_ep_queue(ep, req); } + +static int fastboot_globalvars_init(void) +{ + globalvar_add_simple_int("usbgadget.fastboot_max_download_size", + &fastboot_max_download_size, "%u"); + + return 0; +} + +device_initcall(fastboot_globalvars_init); + +BAREBOX_MAGICVAR_NAMED(global_usbgadget_fastboot_max_download_size, + global.usbgadget.fastboot_max_download_size, + "Fastboot maximum download size"); diff --git a/fs/fs.c b/fs/fs.c index f61ee09..051af8d 100644 --- a/fs/fs.c +++ b/fs/fs.c @@ -742,6 +742,28 @@ } EXPORT_SYMBOL(creat); +int ftruncate(int fd, loff_t length) +{ + struct fs_driver_d *fsdrv; + FILE *f; + int ret; + + if (check_fd(fd)) + return -errno; + + f = &files[fd]; + + fsdrv = f->fsdev->driver; + + ret = fsdrv->truncate(&f->fsdev->dev, f, length); + if (ret) + return ret; + + f->size = length; + + return 0; +} + int ioctl(int fd, int request, void *buf) { struct fs_driver_d *fsdrv; diff --git a/include/file-list.h b/include/file-list.h index 1e02539..404d8d6 100644 --- a/include/file-list.h +++ b/include/file-list.h @@ -4,6 +4,7 @@ #define FILE_LIST_FLAG_SAFE (1 << 0) #define FILE_LIST_FLAG_READBACK (1 << 1) #define FILE_LIST_FLAG_CREATE (1 << 2) +#define FILE_LIST_FLAG_UBI (1 << 3) struct file_list_entry { char *name; diff --git a/include/filetype.h b/include/filetype.h index c84905d..b98dcb5 100644 --- a/include/filetype.h +++ b/include/filetype.h @@ -39,6 +39,7 @@ filetype_mxs_bootstream, filetype_socfpga_xload, filetype_kwbimage_v1, + filetype_android_sparse, filetype_max, }; diff --git a/include/image-sparse.h b/include/image-sparse.h new file mode 100644 index 0000000..29242f4 --- /dev/null +++ b/include/image-sparse.h @@ -0,0 +1,67 @@ +/* + * This is from the Android Project, + * Repository: https://android.googlesource.com/platform/system/core + * File: libsparse/sparse_format.h + * Commit: 28fa5bc347390480fe190294c6c385b6a9f0d68b + * Copyright (C) 2010 The Android Open Source Project + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef _IMAGE_SPARSE_H +#define _IMAGE_SPARSE_H + +struct sparse_header { + __le32 magic; /* 0xed26ff3a */ + __le16 major_version; /* (0x1) - reject images with higher major versions */ + __le16 minor_version; /* (0x0) - allow images with higer minor versions */ + __le16 file_hdr_sz; /* 28 bytes for first revision of the file format */ + __le16 chunk_hdr_sz; /* 12 bytes for first revision of the file format */ + __le32 blk_sz; /* block size in bytes, must be a multiple of 4 (4096) */ + __le32 total_blks; /* total blocks in the non-sparse output image */ + __le32 total_chunks; /* total chunks in the sparse input image */ + __le32 image_checksum; /* CRC32 checksum of the original data, counting "don't care" */ + /* as 0. Standard 802.3 polynomial, use a Public Domain */ + /* table implementation */ +}; + +#define SPARSE_HEADER_MAGIC 0xed26ff3a + +#define CHUNK_TYPE_RAW 0xCAC1 +#define CHUNK_TYPE_FILL 0xCAC2 +#define CHUNK_TYPE_DONT_CARE 0xCAC3 +#define CHUNK_TYPE_CRC32 0xCAC4 + +struct chunk_header { + __le16 chunk_type; /* 0xCAC1 -> raw; 0xCAC2 -> fill; 0xCAC3 -> don't care */ + __le16 reserved1; + __le32 chunk_sz; /* in blocks in output image */ + __le32 total_sz; /* in bytes of chunk input file including chunk header and data */ +}; + +/* Following a Raw or Fill or CRC32 chunk is data. + * For a Raw chunk, it's the data in chunk_sz * blk_sz. + * For a Fill chunk, it's 4 bytes of the fill data. + * For a CRC32 chunk, it's 4 bytes of CRC32 + */ + +static inline int is_sparse_image(const void *buf) +{ + const struct sparse_header *s = buf; + + if ((le32_to_cpu(s->magic) == SPARSE_HEADER_MAGIC) && + (le16_to_cpu(s->major_version) == 1)) + return 1; + + return 0; +} + +struct sparse_image_ctx; + +struct sparse_image_ctx *sparse_image_open(const char *path); +int sparse_image_read(struct sparse_image_ctx *si, void *buf, + loff_t *pos, size_t len, int *retlen); +void sparse_image_close(struct sparse_image_ctx *si); +loff_t sparse_image_size(struct sparse_image_ctx *si); + +#endif /* _IMAGE_SPARSE_H */ diff --git a/include/ubiformat.h b/include/ubiformat.h index b195fd8..8305a85 100644 --- a/include/ubiformat.h +++ b/include/ubiformat.h @@ -20,4 +20,7 @@ int ubiformat(struct mtd_info *mtd, struct ubiformat_args *args); +int ubiformat_write(struct mtd_info *mtd, const void *buf, size_t count, + loff_t offset); + #endif /* __UBIFORMAT_H */ diff --git a/include/unistd.h b/include/unistd.h index 31f430a..f392e6d 100644 --- a/include/unistd.h +++ b/include/unistd.h @@ -20,5 +20,6 @@ int readlink(const char *path, char *buf, size_t bufsiz); int chdir(const char *pathname); const char *getcwd(void); +int ftruncate(int fd, loff_t length); #endif /* __UNISTD_H */ diff --git a/lib/Kconfig b/lib/Kconfig index 9562b1b..637b3f1 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -79,6 +79,9 @@ config LIBUBIGEN bool +config IMAGE_SPARSE + bool + config STMP_DEVICE bool diff --git a/lib/Makefile b/lib/Makefile index 1be1742..0d5ac65 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -32,6 +32,7 @@ obj-y += glob.o obj-y += notifier.o obj-y += random.o +obj-$(CONFIG_IMAGE_SPARSE) += image-sparse.o obj-y += lzo/ obj-$(CONFIG_LZ4_DECOMPRESS) += lz4/ obj-y += show_progress.o diff --git a/lib/image-sparse.c b/lib/image-sparse.c new file mode 100644 index 0000000..7137d15 --- /dev/null +++ b/lib/image-sparse.c @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2009, Google Inc. + * All rights reserved. + * + * Copyright (c) 2009-2014, The Linux Foundation. All rights reserved. + * Portions Copyright 2014 Broadcom Corporation. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of The Linux Foundation nor + * the names of its contributors may be used to endorse or promote + * products derived from this software without specific prior written + * permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * NOTE: + * Although it is very similar, this license text is not identical + * to the "BSD-3-Clause", therefore, DO NOT MODIFY THIS LICENSE TEXT! + */ +#define pr_fmt(fmt) "image-sparse: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#ifndef CONFIG_FASTBOOT_FLASH_FILLBUF_SIZE +#define CONFIG_FASTBOOT_FLASH_FILLBUF_SIZE (1024 * 512) +#endif + +struct sparse_image_ctx { + int fd; + struct sparse_header sparse; + int processed_chunks; + struct chunk_header chunk; + loff_t pos; + size_t remaining; + uint32_t fill_val; +}; + +int sparse_seek(struct sparse_image_ctx *si) +{ + unsigned int chunk_data_sz, payload; + loff_t offs; + int ret; + +again: + if (si->processed_chunks == si->sparse.total_chunks) + return 0; + + /* Read and skip over chunk header */ + ret = read_full(si->fd, &si->chunk, + sizeof(struct chunk_header)); + if (ret < 0) + return ret; + if (ret < sizeof(struct chunk_header)) + return -EINVAL; + + pr_debug("=== Chunk Header ===\n"); + pr_debug("chunk_type: 0x%x\n", si->chunk.chunk_type); + pr_debug("chunk_data_sz: 0x%x\n", si->chunk.chunk_sz); + pr_debug("total_size: 0x%x\n", si->chunk.total_sz); + + if (si->sparse.chunk_hdr_sz > sizeof(struct chunk_header)) { + /* + * Skip the remaining bytes in a header that is longer + * than we expected. + */ + offs = lseek(si->fd, si->sparse.chunk_hdr_sz - + sizeof(struct chunk_header), SEEK_CUR); + if (offs == -1) + return -errno; + } + + chunk_data_sz = si->sparse.blk_sz * si->chunk.chunk_sz; + payload = si->chunk.total_sz - si->sparse.chunk_hdr_sz; + + si->processed_chunks++; + + switch (si->chunk.chunk_type) { + case CHUNK_TYPE_RAW: + if (payload != chunk_data_sz) + return -EINVAL; + + si->remaining = payload; + + break; + + case CHUNK_TYPE_FILL: + if (payload != sizeof(uint32_t)) + return -EINVAL; + + ret = read_full(si->fd, &si->fill_val, sizeof(uint32_t)); + if (ret < 0) + return ret; + if (ret < sizeof(uint32_t)) + return -EINVAL; + + si->remaining = chunk_data_sz; + + break; + + case CHUNK_TYPE_DONT_CARE: + si->pos += chunk_data_sz; + goto again; + + case CHUNK_TYPE_CRC32: + if (payload != sizeof(uint32_t)) + return -EINVAL; + + offs = lseek(si->fd, chunk_data_sz, SEEK_CUR); + if (offs == -1) + return -EINVAL; + goto again; + + default: + pr_err("Unknown chunk type 0x%04x", + si->chunk.chunk_type); + return -EINVAL; + } + + return 1; +} + +loff_t sparse_image_size(struct sparse_image_ctx *si) +{ + return (loff_t)si->sparse.blk_sz * si->sparse.total_blks; +} + +struct sparse_image_ctx *sparse_image_open(const char *path) +{ + struct sparse_image_ctx *si; + loff_t offs; + int ret; + + si = xzalloc(sizeof(*si)); + + si->fd = open(path, O_RDONLY); + if (si->fd < 0) { + ret = -errno; + goto out; + } + + /* Read and skip over sparse image header */ + read(si->fd, &si->sparse, sizeof(struct sparse_header)); + + if (si->sparse.file_hdr_sz > sizeof(struct sparse_header)) { + /* + * Skip the remaining bytes in a header that is longer than + * we expected. + */ + offs = lseek(si->fd, si->sparse.file_hdr_sz, SEEK_SET); + if (offs == -1) { + ret = -errno; + goto out; + } + } + + ret = sparse_seek(si); + if (ret < 0) + goto out; + + return si; +out: + free(si); + + return ERR_PTR(ret); +} + +int sparse_image_read(struct sparse_image_ctx *si, void *buf, loff_t *pos, + size_t len, int *retlen) +{ + size_t now; + int ret, i; + + if (si->remaining == 0) { + ret = sparse_seek(si); + if (ret < 0) + return ret; + if (ret == 0) { + *retlen = 0; + return 0; + } + } + + *pos = si->pos; + + now = min(si->remaining, len); + + switch (si->chunk.chunk_type) { + case CHUNK_TYPE_RAW: + ret = read_full(si->fd, buf, now); + if (ret < 0) + return ret; + if (ret < now) + return -EINVAL; + + break; + + case CHUNK_TYPE_FILL: + if (now & 3) + return -EINVAL; + + for (i = 0; i < now / sizeof(uint32_t); i++) { + uint32_t *buf32 = buf; + + buf32[i] = si->fill_val; + } + + break; + default: + return -EINVAL; + } + + si->pos += now; + si->remaining -= now; + + *retlen = now; + + return 0; +} + +void sparse_image_close(struct sparse_image_ctx *si) +{ + close(si->fd); + free(si); +}