blob: 3cdd41ded0a930f5264835901fa8c596150f4bbc [file] [log] [blame]
/* Copyright 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 <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();
}
}