242 lines
5.3 KiB
C
242 lines
5.3 KiB
C
// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
|
|
/* Copyright 2013-2017 IBM Corp.
|
|
*/
|
|
|
|
#define _GNU_SOURCE
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <limits.h>
|
|
#include <unistd.h>
|
|
#include <dirent.h>
|
|
#include <stdbool.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <string.h>
|
|
|
|
#include <libflash/file.h>
|
|
|
|
#include "arch_flash.h"
|
|
|
|
#define FDT_FLASH_PATH "/proc/device-tree/chosen/ibm,system-flash"
|
|
#define SYSFS_MTD_PATH "/sys/class/mtd"
|
|
|
|
static inline void hint_root(void)
|
|
{
|
|
fprintf(stderr, "Do you have permission? Are you root?\n");
|
|
}
|
|
|
|
static int get_dev_attr(const char *dev, const char *attr_file, uint32_t *attr)
|
|
{
|
|
char *dev_path = NULL;
|
|
int fd, rc;
|
|
|
|
/*
|
|
* Needs to be large enough to hold at most uint32_t represented as a
|
|
* string in hex with leading 0x
|
|
*/
|
|
char attr_buf[10];
|
|
|
|
rc = asprintf(&dev_path, "%s/%s/%s", SYSFS_MTD_PATH, dev, attr_file);
|
|
if (rc < 0) {
|
|
dev_path = NULL;
|
|
goto out;
|
|
}
|
|
|
|
fd = open(dev_path, O_RDONLY);
|
|
if (fd == -1)
|
|
goto out;
|
|
|
|
rc = read(fd, attr_buf, sizeof(attr_buf));
|
|
close(fd);
|
|
if (rc == -1)
|
|
goto out;
|
|
|
|
if (attr)
|
|
*attr = strtol(attr_buf, NULL, 0);
|
|
|
|
free(dev_path);
|
|
return 0;
|
|
|
|
out:
|
|
free(dev_path);
|
|
fprintf(stderr, "Couldn't get MTD attribute '%s' from '%s'\n", dev, attr_file);
|
|
return -1;
|
|
}
|
|
|
|
static int get_dev_mtd(const char *fdt_flash_path, char **mtd_path)
|
|
{
|
|
struct dirent **namelist;
|
|
char fdt_node_path[PATH_MAX];
|
|
int count, i, rc, fd;
|
|
bool done;
|
|
|
|
if (!fdt_flash_path)
|
|
return -1;
|
|
|
|
fd = open(fdt_flash_path, O_RDONLY);
|
|
if (fd == -1) {
|
|
fprintf(stderr, "Couldn't open '%s' FDT attribute to determine which flash device to use\n",
|
|
fdt_flash_path);
|
|
fprintf(stderr, "Is your skiboot new enough to expose the flash through the device tree?\n");
|
|
hint_root();
|
|
return -1;
|
|
}
|
|
|
|
rc = read(fd, fdt_node_path, sizeof(fdt_node_path));
|
|
close(fd);
|
|
if (rc == -1) {
|
|
fprintf(stderr, "Couldn't read flash FDT node from '%s'\n", fdt_flash_path);
|
|
hint_root();
|
|
return -1;
|
|
}
|
|
|
|
count = scandir(SYSFS_MTD_PATH, &namelist, NULL, alphasort);
|
|
if (count == -1) {
|
|
fprintf(stderr, "Couldn't scan '%s' for MTD\n", SYSFS_MTD_PATH);
|
|
hint_root();
|
|
return -1;
|
|
}
|
|
|
|
rc = 0;
|
|
done = false;
|
|
for (i = 0; i < count; i++) {
|
|
struct dirent *dirent;
|
|
char *dev_path;
|
|
char fdt_node_path_tmp[PATH_MAX];
|
|
|
|
dirent = namelist[i];
|
|
|
|
/*
|
|
* The call to asprintf must happen last as when it succeeds it
|
|
* will allocate dev_path
|
|
*/
|
|
if (dirent->d_name[0] == '.' || rc || done ||
|
|
asprintf(&dev_path, "%s/%s/device/of_node", SYSFS_MTD_PATH, dirent->d_name) < 0) {
|
|
free(namelist[i]);
|
|
continue;
|
|
}
|
|
|
|
rc = readlink(dev_path, fdt_node_path_tmp, sizeof(fdt_node_path_tmp) - 1);
|
|
free(dev_path);
|
|
if (rc == -1) {
|
|
/*
|
|
* This might fail because it could not exist if the system has flash
|
|
* devices that present as mtd but don't have corresponding FDT
|
|
* nodes, just continue silently.
|
|
*/
|
|
free(namelist[i]);
|
|
/* Should still try the next dir so reset rc */
|
|
rc = 0;
|
|
continue;
|
|
}
|
|
fdt_node_path_tmp[rc] = '\0';
|
|
|
|
if (strstr(fdt_node_path_tmp, fdt_node_path)) {
|
|
uint32_t flags, size;
|
|
|
|
/*
|
|
* size and flags could perhaps have be gotten another way but this
|
|
* method is super unlikely to fail so it will do.
|
|
*/
|
|
|
|
/* Check to see if device is writeable */
|
|
rc = get_dev_attr(dirent->d_name, "flags", &flags);
|
|
if (rc) {
|
|
free(namelist[i]);
|
|
continue;
|
|
}
|
|
|
|
/* Get the size of the mtd device while we're at it */
|
|
rc = get_dev_attr(dirent->d_name, "size", &size);
|
|
if (rc) {
|
|
free(namelist[i]);
|
|
continue;
|
|
}
|
|
|
|
rc = asprintf(&dev_path, "/dev/%s", dirent->d_name);
|
|
if (rc < 0) {
|
|
free(namelist[i]);
|
|
continue;
|
|
}
|
|
rc = 0;
|
|
*mtd_path = dev_path;
|
|
done = true;
|
|
}
|
|
free(namelist[i]);
|
|
}
|
|
free(namelist);
|
|
|
|
if (!done) {
|
|
fprintf(stderr, "Couldn't find '%s' corresponding MTD\n", fdt_flash_path);
|
|
fprintf(stderr, "Is your kernel new enough to expose MTD?\n");
|
|
}
|
|
|
|
/* explicit negative value so as to not return a libflash code */
|
|
return done ? rc : -1;
|
|
}
|
|
|
|
static struct blocklevel_device *arch_init_blocklevel(const char *file, bool keep_alive)
|
|
{
|
|
int rc;
|
|
struct blocklevel_device *new_bl = NULL;
|
|
char *real_file = NULL;
|
|
|
|
if (!file) {
|
|
rc = get_dev_mtd(FDT_FLASH_PATH, &real_file);
|
|
if (rc)
|
|
return NULL;
|
|
}
|
|
|
|
rc = file_init_path(file ? file : real_file, NULL, keep_alive, &new_bl);
|
|
if (rc)
|
|
new_bl = NULL;
|
|
free(real_file);
|
|
return new_bl;
|
|
}
|
|
|
|
/* Skiboot will worry about this for us */
|
|
int __attribute__((const)) arch_flash_set_wrprotect(struct blocklevel_device *bl, int set)
|
|
{
|
|
(void)bl;
|
|
(void)set;
|
|
|
|
return 0;
|
|
}
|
|
|
|
enum flash_access __attribute__((const)) arch_flash_access(struct blocklevel_device *bl,
|
|
enum flash_access access)
|
|
{
|
|
(void)bl;
|
|
|
|
if (access != PNOR_MTD)
|
|
return ACCESS_INVAL;
|
|
|
|
return PNOR_MTD;
|
|
}
|
|
|
|
int arch_flash_init(struct blocklevel_device **r_bl, const char *file, bool keep_alive)
|
|
{
|
|
struct blocklevel_device *new_bl;
|
|
|
|
/*
|
|
* In theory here we should check that something crazy wasn't
|
|
* passed to arch_flash_access() and refuse to init.
|
|
* However, arch_flash_access won't accept anything except
|
|
* PNOR_MTD, if they want something different then they should
|
|
* have checked with arch_flash_access()
|
|
*/
|
|
new_bl = arch_init_blocklevel(file, keep_alive);
|
|
if (!new_bl)
|
|
return -1;
|
|
|
|
*r_bl = new_bl;
|
|
return 0;
|
|
}
|
|
|
|
void arch_flash_close(struct blocklevel_device *bl, const char *file)
|
|
{
|
|
(void)file;
|
|
|
|
file_exit_close(bl);
|
|
}
|