| // 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(); |
| } |
| } |