aboutsummaryrefslogtreecommitdiffstats
path: root/roms/skiboot/external/common/arch_flash_arm.c
diff options
context:
space:
mode:
Diffstat (limited to 'roms/skiboot/external/common/arch_flash_arm.c')
-rw-r--r--roms/skiboot/external/common/arch_flash_arm.c374
1 files changed, 374 insertions, 0 deletions
diff --git a/roms/skiboot/external/common/arch_flash_arm.c b/roms/skiboot/external/common/arch_flash_arm.c
new file mode 100644
index 000000000..cfe7b96aa
--- /dev/null
+++ b/roms/skiboot/external/common/arch_flash_arm.c
@@ -0,0 +1,374 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/* Copyright 2015-2016 IBM Corp.
+ */
+#define _GNU_SOURCE
+#include <stdlib.h>
+#include <stdio.h>
+#include <limits.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <sys/mman.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+#include <assert.h>
+
+#include <ccan/container_of/container_of.h>
+
+#include <libflash/libflash.h>
+#include <libflash/file.h>
+#include "ast.h"
+#include "arch_flash.h"
+#include "arch_flash_arm_io.h"
+
+struct flash_chip;
+
+static struct arch_arm_data {
+ int fd;
+ void *ahb_reg_map;
+ void *gpio_ctrl;
+ size_t ahb_flash_base;
+ size_t ahb_flash_size;
+ void *ahb_flash_map;
+ enum flash_access access;
+ struct flash_chip *flash_chip;
+ struct blocklevel_device *init_bl;
+} arch_data;
+
+uint32_t ast_ahb_readl(uint32_t offset)
+{
+ assert(((offset ^ AHB_REGS_BASE) & ~(AHB_REGS_SIZE - 1)) == 0);
+
+ return readl(arch_data.ahb_reg_map + (offset - AHB_REGS_BASE));
+}
+
+void ast_ahb_writel(uint32_t val, uint32_t offset)
+{
+ assert(((offset ^ AHB_REGS_BASE) & ~(AHB_REGS_SIZE - 1)) == 0);
+
+ writel(val, arch_data.ahb_reg_map + (offset - AHB_REGS_BASE));
+}
+
+int ast_copy_to_ahb(uint32_t reg, const void *src, uint32_t len)
+{
+ if (reg < arch_data.ahb_flash_base ||
+ (reg + len) > (arch_data.ahb_flash_base + arch_data.ahb_flash_size))
+ return -1;
+ reg -= arch_data.ahb_flash_base;
+
+ if (((reg | (unsigned long)src | len) & 3) == 0) {
+ while(len > 3) {
+ uint32_t val = *(uint32_t *)src;
+ writel(val, arch_data.ahb_flash_map + reg);
+ src += 4;
+ reg += 4;
+ len -= 4;
+ }
+ }
+
+ while(len--) {
+ uint8_t val = *(uint8_t *)src;
+ writeb(val, arch_data.ahb_flash_map + reg++);
+ src += 1;
+ }
+ return 0;
+}
+
+/*
+ * GPIO stuff to be replaced by higher level accessors for
+ * controlling the flash write lock via sysfs
+ */
+
+static inline uint32_t gpio_ctl_readl(uint32_t offset)
+{
+ return readl(arch_data.gpio_ctrl + offset);
+}
+
+static inline void gpio_ctl_writel(uint32_t val, uint32_t offset)
+{
+ writel(val, arch_data.gpio_ctrl + offset);
+}
+
+static bool set_wrprotect(bool protect)
+{
+ uint32_t reg;
+ bool was_protected;
+
+ reg = gpio_ctl_readl(0x20);
+ was_protected = !!(reg & 0x00004000);
+ if (protect)
+ reg |= 0x00004000; /* GPIOF[6] value */
+ else
+ reg &= ~0x00004000; /* GPIOF[6] value */
+ gpio_ctl_writel(reg, 0x20);
+ reg = gpio_ctl_readl(0x24);
+ reg |= 0x00004000; /* GPIOF[6] direction */
+ gpio_ctl_writel(reg, 0x24);
+
+ return was_protected;
+}
+
+int ast_copy_from_ahb(void *dst, uint32_t reg, uint32_t len)
+{
+ if (reg < arch_data.ahb_flash_base ||
+ (reg + len) > (arch_data.ahb_flash_base + arch_data.ahb_flash_size))
+ return -1;
+ reg -= arch_data.ahb_flash_base;
+
+ if (((reg | (unsigned long)dst | len) & 3) == 0) {
+ while(len > 3) {
+ *(uint32_t *)dst = readl(arch_data.ahb_flash_map + reg);
+ dst += 4;
+ reg += 4;
+ len -= 4;
+ }
+ }
+
+ while(len--) {
+ *(uint8_t *)dst = readb(arch_data.ahb_flash_map + reg++);
+ dst += 1;
+ }
+ return 0;
+}
+
+static void close_devs(void)
+{
+ /*
+ * Old code doesn't do this, not sure why not
+ *
+ * munmap(arch_data.ahb_flash_map, arch_data.ahb_flash_size);
+ * munmap(arch_data.gpio_ctrl, GPIO_CTRL_SIZE);
+ * munmap(arch_data.ahb_reg_map, AHB_REGS_SIZE);
+ * close(arch_data.fd);
+ */
+}
+
+static int open_devs(enum flash_access access)
+{
+ if (access != BMC_DIRECT && access != PNOR_DIRECT)
+ return -1;
+
+ arch_data.fd = open("/dev/mem", O_RDWR | O_SYNC);
+ if (arch_data.fd < 0) {
+ perror("can't open /dev/mem");
+ return -1;
+ }
+
+ arch_data.ahb_reg_map = mmap(0, AHB_REGS_SIZE, PROT_READ | PROT_WRITE,
+ MAP_SHARED, arch_data.fd, AHB_REGS_BASE);
+ if (arch_data.ahb_reg_map == MAP_FAILED) {
+ perror("can't map AHB registers /dev/mem");
+ return -1;
+ }
+ arch_data.gpio_ctrl = mmap(0, GPIO_CTRL_SIZE, PROT_READ | PROT_WRITE,
+ MAP_SHARED, arch_data.fd, GPIO_CTRL_BASE);
+ if (arch_data.gpio_ctrl == MAP_FAILED) {
+ perror("can't map GPIO control via /dev/mem");
+ return -1;
+ }
+ arch_data.ahb_flash_base = access == BMC_DIRECT ? BMC_FLASH_BASE : PNOR_FLASH_BASE;
+ arch_data.ahb_flash_size = access == BMC_DIRECT ? BMC_FLASH_SIZE : PNOR_FLASH_SIZE;
+ arch_data.ahb_flash_map = mmap(0, arch_data.ahb_flash_size, PROT_READ |
+ PROT_WRITE, MAP_SHARED, arch_data.fd, arch_data.ahb_flash_base);
+ if (arch_data.ahb_flash_map == MAP_FAILED) {
+ perror("can't map flash via /dev/mem");
+ return -1;
+ }
+ return 0;
+}
+
+static struct blocklevel_device *flash_setup(enum flash_access access)
+{
+ int rc;
+ struct blocklevel_device *bl;
+ struct spi_flash_ctrl *fl;
+
+ if (access != BMC_DIRECT && access != PNOR_DIRECT)
+ return NULL;
+
+ /* Open and map devices */
+ rc = open_devs(access);
+ if (rc)
+ return NULL;
+
+ /* Create the AST flash controller */
+ rc = ast_sf_open(access == BMC_DIRECT ? AST_SF_TYPE_BMC : AST_SF_TYPE_PNOR, &fl);
+ if (rc) {
+ fprintf(stderr, "Failed to open controller\n");
+ return NULL;
+ }
+
+ /* Open flash chip */
+ rc = flash_init(fl, &bl, &arch_data.flash_chip);
+ if (rc) {
+ fprintf(stderr, "Failed to open flash chip\n");
+ return NULL;
+ }
+
+ return bl;
+}
+
+static bool is_bmc_part(const char *str) {
+ /*
+ * On AMI firmmware "fullpart" is what they called the BMC partition
+ * On OpenBMC "bmc" is what they called the BMC partition
+ */
+ return strstr(str, "fullpart") || strstr(str, "bmc");
+}
+
+static bool is_pnor_part(const char *str) {
+ /*
+ * On AMI firmware "PNOR" is what they called the full PNOR
+ * On OpenBMC "pnor" is what they called the full PNOR
+ */
+ return strcasestr(str, "pnor");
+}
+
+static char *get_dev_mtd(enum flash_access access)
+{
+ FILE *f;
+ char *ret = NULL, *pos = NULL;
+ char line[255];
+
+ if (access != BMC_MTD && access != PNOR_MTD)
+ return NULL;
+
+ f = fopen("/proc/mtd", "r");
+ if (!f)
+ return NULL;
+
+ while (!pos && fgets(line, sizeof(line), f) != NULL) {
+ /* Going to have issues if we didn't get the full line */
+ if (line[strlen(line) - 1] != '\n')
+ break;
+
+ if (access == BMC_MTD && is_bmc_part(line)) {
+ pos = strchr(line, ':');
+ if (!pos)
+ break;
+
+ } else if (access == PNOR_MTD && is_pnor_part(line)) {
+ pos = strchr(line, ':');
+ if (!pos)
+ break;
+ }
+ }
+ if (pos) {
+ *pos = '\0';
+ if (asprintf(&ret, "/dev/%s", line) == -1)
+ ret = NULL;
+ }
+
+ fclose(f);
+ return ret;
+}
+
+enum flash_access arch_flash_access(struct blocklevel_device *bl,
+ enum flash_access access)
+{
+ if (access == ACCESS_INVAL)
+ return ACCESS_INVAL;
+
+ if (!arch_data.init_bl) {
+ arch_data.access = access;
+ return access;
+ }
+
+ /* Called with a BL not inited here, bail */
+ if (arch_data.init_bl != bl)
+ return ACCESS_INVAL;
+
+ return arch_data.flash_chip ? arch_data.access : ACCESS_INVAL;
+}
+
+int arch_flash_erase_chip(struct blocklevel_device *bl)
+{
+ /* Called with a BL not inited here, bail */
+ if (!arch_data.init_bl || arch_data.init_bl != bl)
+ return -1;
+
+ if (!arch_data.flash_chip) {
+ /* Just assume its a regular erase */
+ int rc;
+ uint64_t total_size;
+
+ rc = blocklevel_get_info(bl, NULL, &total_size, NULL);
+ if (rc)
+ return rc;
+
+ return blocklevel_erase(bl, 0, total_size);
+ }
+
+ return flash_erase_chip(arch_data.flash_chip);
+}
+
+int arch_flash_4b_mode(struct blocklevel_device *bl, int set_4b)
+{
+ /* Called with a BL not inited here, bail */
+ if (!arch_data.init_bl || arch_data.init_bl != bl)
+ return -1;
+
+ if (!arch_data.flash_chip)
+ return -1;
+
+ return flash_force_4b_mode(arch_data.flash_chip, set_4b);
+}
+
+int arch_flash_set_wrprotect(struct blocklevel_device *bl, int set)
+{
+ /* Called with a BL not inited here, bail */
+ if (!arch_data.init_bl || arch_data.init_bl != bl)
+ return -1;
+
+ if (arch_data.access == PNOR_MTD || arch_data.access == BMC_MTD)
+ return 0; /* Kernel looks after this for us */
+
+ if (!arch_data.flash_chip)
+ return -1;
+
+ return set_wrprotect(set);
+}
+
+int arch_flash_init(struct blocklevel_device **r_bl, const char *file, bool keep_alive)
+{
+ struct blocklevel_device *new_bl;
+ int rc = 0;
+
+ /* Check we haven't already inited */
+ if (arch_data.init_bl)
+ return -1;
+
+ if (file) {
+ rc = file_init_path(file, NULL, keep_alive, &new_bl);
+ } else if (arch_data.access == BMC_MTD || arch_data.access == PNOR_MTD) {
+ char *mtd_dev;
+
+ mtd_dev = get_dev_mtd(arch_data.access);
+ if (!mtd_dev) {
+ return -1;
+ }
+ rc = file_init_path(mtd_dev, NULL, keep_alive, &new_bl);
+ free(mtd_dev);
+ } else {
+ new_bl = flash_setup(arch_data.access);
+ if (!new_bl)
+ rc = -1;
+ }
+ if (rc)
+ return rc;
+
+ arch_data.init_bl = new_bl;
+ *r_bl = new_bl;
+ return 0;
+}
+
+void arch_flash_close(struct blocklevel_device *bl, const char *file)
+{
+ if (file || arch_data.access == BMC_MTD || arch_data.access == PNOR_MTD) {
+ file_exit_close(bl);
+ } else {
+ flash_exit_close(bl, &ast_sf_close);
+ close_devs();
+ }
+}