blob: cf1f6421aacc283724124f933ce2679258427c66 [file] [log] [blame]
/*
* Copyright(c) 2014 Intel Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of version 2 of the GNU General Public License 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 <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <fcntl.h>
#include <limits.h>
#include <unistd.h>
#include <endian.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <util/list.h>
#include <subcmd/parse-options.h>
#include <util/size.h>
#include <util/util.h>
#include <nfit.h>
#define DEFAULT_NFIT "local_nfit.bin"
static const char *nfit_file = DEFAULT_NFIT;
static LIST_HEAD(spas);
struct spa {
struct list_head list;
unsigned long long size, offset;
};
static int parse_add_spa(const struct option *option, const char *__arg, int unset)
{
struct spa *s = calloc(1, sizeof(struct spa));
char *arg = strdup(__arg);
char *size, *offset;
int rc = -ENOMEM;
if (!s || !arg)
goto err;
rc = -EINVAL;
size = arg;
offset = strchr(arg, ',');
if (!offset)
goto err;
*offset++ = '\0';
s->size = parse_size64(size);
if (s->size == ULLONG_MAX)
goto err;
s->offset = parse_size64(offset);
if (s->offset == ULLONG_MAX)
goto err;
list_add_tail(&s->list, &spas);
free(arg);
return 0;
err:
error("failed to parse --add-spa=%s\n", __arg);
free(arg);
free(s);
return rc;
}
static unsigned char nfit_checksum(void *buf, size_t size)
{
unsigned char sum, *data = buf;
size_t i;
for (sum = 0, i = 0; i < size; i++)
sum += data[i];
return 0 - sum;
}
static void writeq(unsigned long long v, void *a)
{
unsigned long long *p = a;
*p = htole64(v);
}
static void writel(unsigned long v, void *a)
{
unsigned long *p = a;
*p = htole32(v);
}
static void writew(unsigned short v, void *a)
{
unsigned short *p = a;
*p = htole16(v);
}
static void writeb(unsigned char v, void *a)
{
unsigned char *p = a;
*p = v;
}
static struct nfit *create_nfit(struct list_head *spa_list)
{
struct nfit_spa *nfit_spa;
struct nfit *nfit;
struct spa *s;
size_t size;
char *buf;
int i;
size = sizeof(struct nfit);
list_for_each_entry(s, spa_list, list)
size += sizeof(struct nfit_spa);
buf = calloc(1, size);
if (!buf)
return NULL;
/* nfit header */
nfit = (struct nfit *) buf;
memcpy(nfit->signature, "NFIT", 4);
writel(size, &nfit->length);
writeb(1, &nfit->revision);
memcpy(nfit->oemid, "LOCAL", 6);
writew(1, &nfit->oem_tbl_id);
writel(1, &nfit->oem_revision);
writel(0x80860000, &nfit->creator_id);
writel(1, &nfit->creator_revision);
nfit_spa = (struct nfit_spa *) (buf + sizeof(*nfit));
i = 1;
list_for_each_entry(s, spa_list, list) {
writew(NFIT_TABLE_SPA, &nfit_spa->type);
writew(sizeof(*nfit_spa), &nfit_spa->length);
nfit_spa_uuid_pm(&nfit_spa->type_uuid);
writew(i++, &nfit_spa->range_index);
writeq(s->offset, &nfit_spa->spa_base);
writeq(s->size, &nfit_spa->spa_length);
nfit_spa++;
}
writeb(nfit_checksum(buf, size), &nfit->checksum);
return nfit;
}
static int write_nfit(struct nfit *nfit, const char *file, int force)
{
int fd;
ssize_t rc;
mode_t mode = S_IRUSR|S_IRGRP|S_IWUSR|S_IWGRP;
fd = open(file, O_RDWR|O_CREAT|O_EXCL, mode);
if (fd < 0 && !force && errno == EEXIST) {
error("\"%s\" exists, overwrite with --force\n", file);
return -EEXIST;
} else if (fd < 0 && force && errno == EEXIST) {
fd = open(file, O_RDWR|O_CREAT|O_TRUNC, mode);
}
if (fd < 0) {
error("Failed to open \"%s\": %s\n", file, strerror(errno));
return -errno;
}
rc = write(fd, nfit, le32toh(nfit->length));
close(fd);
return rc;
}
struct ndctl_ctx;
int cmd_create_nfit(int argc, const char **argv, void *ctx)
{
int i, rc = -ENXIO, force = 0;
const char * const u[] = {
"ndctl create-nfit [<options>]",
NULL
};
const struct option options[] = {
OPT_CALLBACK('a', "add-spa", NULL, "size,offset",
"add a system-physical-address range table entry",
parse_add_spa),
OPT_STRING('o', NULL, &nfit_file, "file",
"output to <file> (default: " DEFAULT_NFIT ")"),
OPT_INCR('f', "force", &force, "overwrite <file> if it already exists"),
OPT_END(),
};
struct spa *s, *_s;
struct nfit *nfit = NULL;
argc = parse_options(argc, argv, options, u, 0);
for (i = 0; i < argc; i++)
error("unknown parameter \"%s\"\n", argv[i]);
if (list_empty(&spas))
error("specify at least one --add-spa= option\n");
if (argc || list_empty(&spas))
usage_with_options(u, options);
nfit = create_nfit(&spas);
if (!nfit)
goto out;
rc = write_nfit(nfit, nfit_file, force);
if ((unsigned int) rc == le32toh(nfit->length)) {
fprintf(stderr, "wrote %d bytes to %s\n",
le32toh(nfit->length), nfit_file);
rc = 0;
}
out:
free(nfit);
list_for_each_entry_safe(s, _s, &spas, list) {
list_del(&s->list);
free(s);
}
return rc;
}