/* Copyright 2013-2015 IBM Corp. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. * See the License for the specific language governing permissions and * limitations under the License. */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #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); }