9
0
Fork 0

Merge branch 'for-next/mtd-partitions'

This commit is contained in:
Sascha Hauer 2015-03-06 08:33:43 +01:00
commit 0e055c254c
21 changed files with 418 additions and 171 deletions

View File

@ -0,0 +1,6 @@
#!/bin/sh
global.bootm.image="/dev/nand0.kernel.bb"
global.bootm.oftree="/dev/nand0.oftree.bb"
global.linux.bootargs.dyn.root="root=ubi0:rootfs ubi.mtd=rootfs rootfstype=ubifs noinitrd"

View File

@ -1,42 +0,0 @@
#!/bin/sh
# use 'dhcp' to do dhcp in barebox and in kernel
# use 'none' if you want to skip kernel ip autoconfiguration
ip=dhcp
# or set your networking parameters here
#eth0.ipaddr=a.b.c.d
#eth0.netmask=a.b.c.d
#eth0.gateway=a.b.c.d
#eth0.serverip=a.b.c.d
# can be either 'nfs', 'tftp', 'nor' or 'nand'
kernel_loc=nfs
# can be either 'net', 'nor', 'nand' or 'initrd'
rootfs_loc=net
# can be either 'nfs', 'tftp', 'nand' or empty
oftree_loc=nfs
# can be either 'jffs2' or 'ubifs'
rootfs_type=ubifs
rootfsimage=root.$rootfs_type
ubiroot=rootfs
# The image type of the kernel. Can be uimage, zimage, raw, or raw_lzo
kernelimage=zImage
#kernelimage=uImage
#kernelimage=Image
#kernelimage=Image.lzo
nand_device=atmel_nand
nand_parts="256k(at91bootstrap),512k(barebox)ro,256k(bareboxenv),256k(bareboxenv2),256k(spare),512k(oftree),6M(kernel),-(rootfs)"
rootfs_mtdblock_nand=7
m25p80_parts="64k(bootstrap),384k(barebox),256k(bareboxenv),256k(bareboxenv2),128k(oftree),-(updater)"
autoboot_timeout=3
bootargs="console=ttyS0,115200"
# set a fancy prompt (if support is compiled in)
PS1="\e[1;32mbarebox@\e[1;31m\h:\w\e[0m\n# "

View File

@ -0,0 +1,6 @@
#!/bin/sh
mtdparts="256k(at91bootstrap),512k(barebox)ro,256k(bareboxenv),256k(bareboxenv2),256k(spare),512k(oftree),6M(kernel),-(rootfs)"
kernelname="atmel_nand"
mtdparts-add -b -d nand0 -k ${kernelname} -p ${mtdparts}

View File

@ -0,0 +1,6 @@
#!/bin/sh
mtdparts="64k(bootstrap),64k(bareboxenv),512k(barebox),384k(oftree),-(kernel)"
kernelname="m25p800"
mtdparts-add -d m25p0 -k ${kernelname} -p ${mtdparts}

View File

@ -1,10 +1,5 @@
#!/bin/sh
PATH=/env/bin
export PATH
. /env/config
splash=/env/splash.png
if [ -f ${splash} -a -e /dev/fb0 ]; then

View File

@ -0,0 +1 @@
nand net

View File

@ -0,0 +1 @@
sama5d4ek

View File

@ -0,0 +1 @@
console=ttyS0,115200

View File

@ -9,13 +9,12 @@ CONFIG_MALLOC_SIZE=0xA00000
CONFIG_EXPERIMENTAL=y
CONFIG_MALLOC_TLSF=y
CONFIG_PROMPT="A5D4EK:"
CONFIG_GLOB=y
CONFIG_PROMPT_HUSH_PS2="y"
CONFIG_HUSH_FANCY_PROMPT=y
CONFIG_CMDLINE_EDITING=y
CONFIG_AUTO_COMPLETE=y
CONFIG_CONSOLE_ACTIVATE_ALL=y
CONFIG_DEFAULT_ENVIRONMENT_GENERIC=y
CONFIG_DEFAULT_ENVIRONMENT_GENERIC_NEW=y
CONFIG_DEFAULT_ENVIRONMENT_PATH="arch/arm/boards/sama5d4ek/env"
CONFIG_DEBUG_INFO=y
# CONFIG_CMD_ARM_CPUINFO is not set
@ -35,6 +34,8 @@ CONFIG_CMD_PARTITION=y
CONFIG_CMD_EXPORT=y
CONFIG_CMD_LOADENV=y
CONFIG_CMD_PRINTENV=y
CONFIG_CMD_MAGICVAR=y
CONFIG_CMD_MAGICVAR_HELP=y
CONFIG_CMD_SAVEENV=y
CONFIG_CMD_FILETYPE=y
CONFIG_CMD_SLEEP=y

View File

@ -494,6 +494,7 @@ choice
config MACH_SAMA5D4EK
bool "Atmel SAMA5D4 Evaluation Kit"
select HAVE_DEFAULT_ENVIRONMENT_NEW
help
Select this if you are using Atmel's SAMA5D4-EK Evaluation Kit.

View File

@ -35,96 +35,21 @@
#include <linux/stat.h>
#include <libgen.h>
#include <getopt.h>
#include <cmdlinepart.h>
#include <linux/err.h>
#define SIZE_REMAINING ((ulong)-1)
#define PART_ADD_DEVNAME (1 << 0)
static int mtd_part_do_parse_one(char *devname, const char *partstr,
char **endp, loff_t *offset,
loff_t devsize, size_t *retsize,
unsigned int pflags)
{
loff_t size;
char *end;
char buf[PATH_MAX] = {};
unsigned long flags = 0;
int ret = 0;
struct cdev *cdev;
memset(buf, 0, PATH_MAX);
if (*partstr == '-') {
size = SIZE_REMAINING;
end = (char *)partstr + 1;
} else {
size = strtoull_suffix(partstr, &end, 0);
}
if (*end == '@')
*offset = strtoull_suffix(end+1, &end, 0);
if (size == SIZE_REMAINING)
size = devsize - *offset;
partstr = end;
if (*partstr == '(') {
partstr++;
end = strchr((char *) partstr, ')');
if (!end) {
printf("could not find matching ')'\n");
return -EINVAL;
}
if (pflags & PART_ADD_DEVNAME)
sprintf(buf, "%s.", devname);
memcpy(buf + strlen(buf), partstr, end - partstr);
end++;
}
if (size + *offset > devsize) {
printf("%s: partition end is beyond device\n", buf);
return -EINVAL;
}
partstr = end;
if (*partstr == 'r' && *(partstr + 1) == 'o') {
flags |= DEVFS_PARTITION_READONLY;
end = (char *)(partstr + 2);
}
if (endp)
*endp = end;
*retsize = size;
cdev = devfs_add_partition(devname, *offset, size, flags, buf);
if (IS_ERR(cdev)) {
ret = PTR_ERR(cdev);
printf("cannot create %s: %s\n", buf, strerror(-ret));
}
return ret;
}
static int do_addpart(int argc, char *argv[])
{
char *devname;
char *endp;
loff_t offset = 0;
loff_t devsize;
struct stat s;
int opt;
unsigned int flags = PART_ADD_DEVNAME;
unsigned int flags = CMDLINEPART_ADD_DEVNAME;
while ((opt = getopt(argc, argv, "n")) > 0) {
switch (opt) {
case 'n':
flags &= ~PART_ADD_DEVNAME;
flags &= ~CMDLINEPART_ADD_DEVNAME;
break;
}
}
@ -140,28 +65,7 @@ static int do_addpart(int argc, char *argv[])
devname = basename(argv[optind]);
endp = argv[optind + 1];
while (1) {
size_t size = 0;
if (mtd_part_do_parse_one(devname, endp, &endp, &offset,
devsize, &size, flags))
return 1;
offset += size;
if (!*endp)
break;
if (*endp != ',') {
printf("parse error\n");
return 1;
}
endp++;
}
return 0;
return cmdlinepart_do_parse(devname, argv[optind + 1], devsize, flags);
}
BAREBOX_CMD_HELP_START(addpart)

View File

@ -5,7 +5,6 @@ mkdir -p /tmp/mtdparts
parts=
device=
kernelname=
bbdev=
while getopt "p:d:k:b" opt; do
if [ ${opt} = p ]; then
@ -14,8 +13,6 @@ while getopt "p:d:k:b" opt; do
device=${OPTARG}
elif [ ${opt} = k ]; then
kernelname=${OPTARG}
elif [ ${opt} = b ]; then
bbdev=true
fi
done
@ -29,16 +26,12 @@ if [ -z "${parts}" ]; then
exit
fi
if [ -e /tmp/mtdparts/${device} ]; then
if [ -n "/dev/${device}.*.bb" ]; then
nand -d /dev/${device}.*.bb
fi
delpart /dev/${device}.*
${device}.partitions="$parts"
if [ $? != 0 ]; then
echo "Failed to add partitions $parts to $device"
exit 1
fi
addpart -n /dev/${device} "$parts" || exit
mkdir -p /tmp/mtdparts/${device}
if [ -n ${kernelname} ]; then
global linux.mtdparts.${device}
global.linux.mtdparts.${device}="${kernelname}:${parts}"

View File

@ -18,6 +18,7 @@
#include <common.h>
#include <linux/mtd/nand.h>
#include <linux/mtd/mtd.h>
#include <cmdlinepart.h>
#include <init.h>
#include <xfuncs.h>
#include <driver.h>
@ -377,9 +378,174 @@ static struct file_operations mtd_ops = {
.lseek = dev_lseek_default,
};
static int mtd_partition_set(struct device_d *dev, struct param_d *p, const char *val)
{
struct mtd_info *mtd = container_of(dev, struct mtd_info, class_dev);
struct mtd_info *mtdpart, *tmp;
int ret;
list_for_each_entry_safe(mtdpart, tmp, &mtd->partitions, partitions_entry) {
ret = mtd_del_partition(mtdpart);
if (ret)
return ret;
}
return cmdlinepart_do_parse(mtd->cdev.name, val, mtd->size, CMDLINEPART_ADD_DEVNAME);
}
static char *print_size(uint64_t s)
{
if (!(s & ((1 << 20) - 1)))
return asprintf("%lldM", s >> 20);
if (!(s & ((1 << 10) - 1)))
return asprintf("%lldk", s >> 10);
return asprintf("0x%lld", s);
}
static int print_part(char *buf, int bufsize, struct mtd_info *mtd, uint64_t last_ofs,
int is_last)
{
char *size = print_size(mtd->size);
char *ofs = print_size(mtd->master_offset);
int ret;
if (!size || !ofs) {
ret = -ENOMEM;
goto out;
}
if (mtd->master_offset == last_ofs)
ret = snprintf(buf, bufsize, "%s(%s)%s", size,
mtd->cdev.partname,
is_last ? "" : ",");
else
ret = snprintf(buf, bufsize, "%s@%s(%s)%s", size,
ofs,
mtd->cdev.partname,
is_last ? "" : ",");
out:
free(size);
free(ofs);
return ret;
}
static int print_parts(char *buf, int bufsize, struct mtd_info *mtd)
{
struct mtd_info *mtdpart;
uint64_t last_ofs = 0;
int ret = 0;
list_for_each_entry(mtdpart, &mtd->partitions, partitions_entry) {
int now;
int is_last = list_is_last(&mtdpart->partitions_entry,
&mtd->partitions);
now = print_part(buf, bufsize, mtdpart, last_ofs, is_last);
if (now < 0)
return now;
if (buf && bufsize) {
buf += now;
bufsize -= now;
}
ret += now;
last_ofs = mtdpart->master_offset + mtdpart->size;
}
return ret;
}
static const char *mtd_partition_get(struct device_d *dev, struct param_d *p)
{
struct mtd_info *mtd = container_of(dev, struct mtd_info, class_dev);
int len = 0;
free(p->value);
len = print_parts(NULL, 0, mtd);
p->value = xzalloc(len + 1);
print_parts(p->value, len + 1, mtd);
return p->value;
}
static int mtd_part_compare(struct list_head *a, struct list_head *b)
{
struct mtd_info *mtda = container_of(a, struct mtd_info, partitions_entry);
struct mtd_info *mtdb = container_of(b, struct mtd_info, partitions_entry);
if (mtda->master_offset > mtdb->master_offset)
return 1;
if (mtda->master_offset < mtdb->master_offset)
return -1;
return 0;
}
static int of_mtd_fixup(struct device_node *root, void *ctx)
{
struct mtd_info *mtd = ctx, *partmtd;
struct device_node *np, *part, *tmp;
int ret, i = 0;
np = of_find_node_by_path(mtd->of_path);
if (!np) {
dev_err(&mtd->class_dev, "Cannot find nodepath %s, cannot fixup\n",
mtd->of_path);
return -EINVAL;
}
for_each_child_of_node_safe(np, tmp, part) {
if (of_get_property(part, "compatible", NULL))
continue;
of_delete_node(part);
}
list_for_each_entry(partmtd, &mtd->partitions, partitions_entry) {
int na, ns, len = 0;
char *name = asprintf("partition@%d", i++);
void *p;
u8 tmp[16 * 16]; /* Up to 64-bit address + 64-bit size */
if (!name)
return -ENOMEM;
part = of_new_node(np, name);
free(name);
if (!part)
return -ENOMEM;
p = of_new_property(part, "label", partmtd->cdev.partname,
strlen(partmtd->cdev.partname) + 1);
if (!p)
return -ENOMEM;
na = of_n_addr_cells(np);
ns = of_n_size_cells(np);
of_write_number(tmp + len, partmtd->master_offset, na);
len += na * 4;
of_write_number(tmp + len, partmtd->size, ns);
len += ns * 4;
ret = of_set_property(part, "reg", tmp, len, 1);
if (ret)
return ret;
if (partmtd->cdev.flags & DEVFS_PARTITION_READONLY) {
ret = of_set_property(part, "read-only", NULL, 0, 1);
if (ret)
return ret;
}
}
return 0;
}
int add_mtd_device(struct mtd_info *mtd, char *devname, int device_id)
{
struct mtddev_hook *hook;
int ret;
if (!devname)
devname = "mtd";
@ -387,7 +553,10 @@ int add_mtd_device(struct mtd_info *mtd, char *devname, int device_id)
mtd->class_dev.id = device_id;
if (mtd->parent)
mtd->class_dev.parent = mtd->parent;
register_device(&mtd->class_dev);
ret = register_device(&mtd->class_dev);
if (ret)
return ret;
mtd->cdev.ops = &mtd_ops;
mtd->cdev.size = mtd->size;
@ -396,6 +565,8 @@ int add_mtd_device(struct mtd_info *mtd, char *devname, int device_id)
else
mtd->cdev.name = asprintf("%s%d", devname, mtd->class_dev.id);
INIT_LIST_HEAD(&mtd->partitions);
mtd->cdev.priv = mtd;
mtd->cdev.dev = &mtd->class_dev;
mtd->cdev.mtd = mtd;
@ -407,19 +578,50 @@ int add_mtd_device(struct mtd_info *mtd, char *devname, int device_id)
dev_add_param_int_ro(&mtd->class_dev, "oobsize", mtd->oobsize, "%u");
}
devfs_create(&mtd->cdev);
ret = devfs_create(&mtd->cdev);
if (ret)
goto err;
if (mtd->master && !(mtd->cdev.flags & DEVFS_PARTITION_FIXED)) {
struct mtd_info *mtdpart;
list_for_each_entry(mtdpart, &mtd->master->partitions, partitions_entry) {
if (mtdpart->master_offset + mtdpart->size <= mtd->master_offset)
continue;
if (mtd->master_offset + mtd->size <= mtdpart->master_offset)
continue;
dev_err(&mtd->class_dev, "New partition %s conflicts with %s\n",
mtd->name, mtdpart->name);
goto err1;
}
list_add_sort(&mtd->partitions_entry, &mtd->master->partitions, mtd_part_compare);
}
if (mtd_can_have_bb(mtd))
mtd->cdev_bb = mtd_add_bb(mtd, NULL);
if (mtd->parent && !mtd->master)
if (mtd->parent && !mtd->master) {
dev_add_param(&mtd->class_dev, "partitions", mtd_partition_set, mtd_partition_get, 0);
of_parse_partitions(&mtd->cdev, mtd->parent->device_node);
if (IS_ENABLED(CONFIG_OFDEVICE) && mtd->parent->device_node) {
mtd->of_path = xstrdup(mtd->parent->device_node->full_name);
of_register_fixup(of_mtd_fixup, mtd);
}
}
list_for_each_entry(hook, &mtd_register_hooks, hook)
if (hook->add_mtd_device)
hook->add_mtd_device(mtd, devname, &hook->priv);
return 0;
err1:
devfs_remove(&mtd->cdev);
err:
free(mtd->cdev.name);
unregister_device(&mtd->class_dev);
return ret;
}
int del_mtd_device (struct mtd_info *mtd)
@ -431,9 +633,14 @@ int del_mtd_device (struct mtd_info *mtd)
hook->del_mtd_device(mtd, &hook->priv);
devfs_remove(&mtd->cdev);
if (mtd->cdev_bb)
mtd_del_bb(mtd);
unregister_device(&mtd->class_dev);
free(mtd->param_size.value);
free(mtd->cdev.name);
if (mtd->master)
list_del(&mtd->partitions_entry);
return 0;
}

View File

@ -307,6 +307,17 @@ err:
return ERR_PTR(ret);
}
void mtd_del_bb(struct mtd_info *mtd)
{
struct cdev *cdev = mtd->cdev_bb;
struct nand_bb *bb = container_of(cdev, struct nand_bb, cdev);
devfs_remove(&bb->cdev);
list_del_init(&bb->list);
free(bb->name);
free(bb);
}
/**
* Add a bad block aware device ontop of another (NAND) device
* @param[in] dev The device to add a partition on
@ -335,10 +346,7 @@ int dev_remove_bb_dev(const char *name)
list_for_each_entry_safe(bb, tmp, &bb_list, list) {
if (!strcmp(bb->cdev.name, name)) {
devfs_remove(&bb->cdev);
list_del_init(&bb->list);
free(bb->name);
free(bb);
mtd_del_bb(bb->mtd);
return 0;
}
}

View File

@ -108,6 +108,7 @@ struct mtd_info *mtd_add_partition(struct mtd_info *mtd, off_t offset,
uint64_t size, unsigned long flags, const char *name)
{
struct mtd_info *part;
int ret;
part = xzalloc(sizeof(*part));
@ -122,6 +123,7 @@ struct mtd_info *mtd_add_partition(struct mtd_info *mtd, off_t offset,
part->ecclayout = mtd->ecclayout;
part->ecc_strength = mtd->ecc_strength;
part->subpage_sft = mtd->subpage_sft;
part->cdev.flags = flags;
if (mtd->numeraseregions > 1) {
/* Deal with variable erase size stuff */
@ -160,7 +162,7 @@ struct mtd_info *mtd_add_partition(struct mtd_info *mtd, off_t offset,
part->block_isbad = mtd->block_isbad ? mtd_part_block_isbad : NULL;
part->size = size;
part->name = strdup(name);
part->name = xstrdup(name);
part->master_offset = offset;
part->master = mtd;
@ -168,9 +170,17 @@ struct mtd_info *mtd_add_partition(struct mtd_info *mtd, off_t offset,
if (!strncmp(mtd->cdev.name, name, strlen(mtd->cdev.name)))
part->cdev.partname = xstrdup(name + strlen(mtd->cdev.name) + 1);
add_mtd_device(part, part->name, DEVICE_ID_SINGLE);
ret = add_mtd_device(part, part->name, DEVICE_ID_SINGLE);
if (ret)
goto err;
return part;
err:
free(part->cdev.partname);
free(part->name);
free(part);
return ERR_PTR(ret);
}
int mtd_del_partition(struct mtd_info *part)

14
include/cmdlinepart.h Normal file
View File

@ -0,0 +1,14 @@
#ifndef __CMD_LINE_PART_H
#define __CMD_LINE_PART_H
#define CMDLINEPART_ADD_DEVNAME (1 << 0)
int cmdlinepart_do_parse_one(const char *devname, const char *partstr,
const char **endp, loff_t *offset,
loff_t devsize, loff_t *retsize,
unsigned int partition_flags);
int cmdlinepart_do_parse(const char *devname, const char *parts, loff_t devsize,
unsigned partition_flags);
#endif /* __CMD_LINE_PART_H */

View File

@ -220,6 +220,11 @@ struct mtd_info {
struct mtd_info *master;
loff_t master_offset;
struct list_head partitions;
struct list_head partitions_entry;
char *of_path;
};
int mtd_erase(struct mtd_info *mtd, struct erase_info *instr);

View File

@ -1,4 +1,3 @@
#ifndef __NAND_H__
#define __NAND_H__
@ -8,6 +7,7 @@ struct nand_bb;
int dev_add_bb_dev(const char *filename, const char *name);
int dev_remove_bb_dev(const char *name);
struct cdev *mtd_add_bb(struct mtd_info *mtd, const char *name);
void mtd_del_bb(struct mtd_info *mtd);
#else
static inline int dev_add_bb_dev(const char *filename, const char *name) {
return 0;
@ -21,7 +21,10 @@ static inline struct cdev *mtd_add_bb(struct mtd_info *mtd, const char *name)
{
return NULL;
}
static inline void mtd_del_bb(struct mtd_info *mtd)
{
}
#endif
#endif /* __NAND_H__ */

View File

@ -623,6 +623,8 @@ static inline struct device_node *of_find_matching_node(
for (dn = of_find_node_with_property(NULL, prop_name); dn; \
dn = of_find_node_with_property(dn, prop_name))
#define for_each_child_of_node_safe(parent, tmp, child) \
list_for_each_entry_safe(child, tmp, &parent->children, parent_list)
#define for_each_child_of_node(parent, child) \
list_for_each_entry(child, &parent->children, parent_list)
#define for_each_available_child_of_node(parent, child) \

View File

@ -18,6 +18,7 @@ obj-y += kfifo.o
obj-y += libbb.o
obj-y += libgen.o
obj-y += stringlist.o
obj-y += cmdlinepart.o
obj-y += recursive_action.o
obj-y += make_directory.o
obj-y += math.o

124
lib/cmdlinepart.c Normal file
View File

@ -0,0 +1,124 @@
/*
* command line partition parsing code
*
* Copyright (c) 2015 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 <driver.h>
#include <fs.h>
#include <linux/err.h>
#include <cmdlinepart.h>
#define SIZE_REMAINING ((loff_t)-1)
int cmdlinepart_do_parse_one(const char *devname, const char *partstr,
const char **endp, loff_t *offset,
loff_t devsize, loff_t *retsize,
unsigned int partition_flags)
{
loff_t size;
char *end;
char buf[PATH_MAX] = {};
unsigned long flags = 0;
int ret = 0;
struct cdev *cdev;
memset(buf, 0, PATH_MAX);
if (*partstr == '-') {
size = SIZE_REMAINING;
end = (char *)partstr + 1;
} else {
size = strtoull_suffix(partstr, &end, 0);
}
if (*end == '@')
*offset = strtoull_suffix(end+1, &end, 0);
if (size == SIZE_REMAINING)
size = devsize - *offset;
partstr = end;
if (*partstr == '(') {
partstr++;
end = strchr((char *) partstr, ')');
if (!end) {
printf("could not find matching ')'\n");
return -EINVAL;
}
if ((partition_flags & CMDLINEPART_ADD_DEVNAME) &&
strncmp(devname, partstr, strlen(devname)))
sprintf(buf, "%s.", devname);
memcpy(buf + strlen(buf), partstr, end - partstr);
end++;
}
if (size + *offset > devsize) {
printf("%s: partition end is beyond device\n", buf);
return -EINVAL;
}
partstr = end;
if (*partstr == 'r' && *(partstr + 1) == 'o') {
flags |= DEVFS_PARTITION_READONLY;
end = (char *)(partstr + 2);
}
if (endp)
*endp = end;
*retsize = size;
cdev = devfs_add_partition(devname, *offset, size, flags, buf);
if (IS_ERR(cdev)) {
ret = PTR_ERR(cdev);
printf("cannot create %s: %s\n", buf, strerror(-ret));
}
return ret;
}
int cmdlinepart_do_parse(const char *devname, const char *parts, loff_t devsize,
unsigned partition_flags)
{
loff_t offset = 0;
int ret;
while (1) {
loff_t size = 0;
ret = cmdlinepart_do_parse_one(devname, parts, &parts, &offset,
devsize, &size, partition_flags);
if (ret)
return ret;
offset += size;
if (!*parts)
break;
if (*parts != ',') {
printf("parse error\n");
return -EINVAL;
}
parts++;
}
return 0;
}