x49gp/flash.c
claudiol 0df95a3dfd [3298 - patch 34] When loading a firmware during flash initialization, check for the KINPOUPDATEIMAGE signature, and fail if it's missing
[3298 - patch 35] When selecting a firmware interactively, show errors in a message window and retry
The user may not even have a terminal open to catch the error message, so the previous behavior was basically a silent failure to them.
[3298 - patch 36] Add newRPL keyboard layouts via new calculator types "hp49gp/newrpl" and "hp50g/newrpl"
Also fixes an old bug causing only a single keyboard layout to be used, regardless of selected calculator type.
It appears nobody noticed this bug because the only two layouts present before this commit were almost identical.
2018-10-10 09:11:30 -04:00

837 lines
19 KiB
C

/* $Id: flash.c,v 1.18 2008/12/11 12:18:17 ecd Exp $
*/
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <errno.h>
#include <gtk/gtk.h>
#include <glib.h>
#include <cairo.h>
#include <x49gp.h>
#include <x49gp_ui.h>
#include <memory.h>
#include <byteorder.h>
#define FLASH_STATE_NORMAL 0
#define FLASH_STATE_UNLOCK1 1
#define FLASH_STATE_UNLOCK2 2
#define FLASH_STATE_ERASE1 3
#define FLASH_STATE_ERASE2 4
#define FLASH_STATE_ERASE3 5
#define FLASH_STATE_SOFTWARE_EXIT1 6
#define FLASH_STATE_SOFTWARE_EXIT2 7
#define FLASH_STATE_CFI_QUERY_EXIT1 8
#define FLASH_STATE_CFI_QUERY_EXIT2 9
#define FLASH_STATE_SOFTWARE_ID 10
#define FLASH_STATE_CFI_QUERY 11
#define FLASH_STATE_WORD_PROG 12
typedef struct {
void *data;
int state;
unsigned short vendor_ID;
unsigned short device_ID;
const unsigned short *cfi_data;
uint32_t cfi_size;
uint32_t sector_size;
uint32_t block_size;
char *filename;
int fd;
size_t size;
uint32_t iotype;
uint32_t offset;
} x49gp_flash_t;
#define SST29VF160_VENDOR_ID 0x00bf
#define SST29VF160_DEVICE_ID 0x2782
#define SST29VF160_SECTOR_SIZE 0x00001000
#define SST29VF160_BLOCK_SIZE 0x00010000
#define SST29VF160_SIZE 0x00200000
#define BOOT_SIZE 0x00004000
static const unsigned short sst29vf160_cfi_data[] =
{
[0x10] = 0x0051,
[0x11] = 0x0052,
[0x12] = 0x0059,
[0x13] = 0x0001,
[0x14] = 0x0007,
[0x15] = 0x0000,
[0x16] = 0x0000,
[0x17] = 0x0000,
[0x18] = 0x0000,
[0x19] = 0x0000,
[0x1a] = 0x0000,
[0x1b] = 0x0027,
[0x1c] = 0x0036,
[0x1d] = 0x0000,
[0x1e] = 0x0000,
[0x1f] = 0x0004,
[0x20] = 0x0000,
[0x21] = 0x0004,
[0x22] = 0x0006,
[0x23] = 0x0001,
[0x24] = 0x0000,
[0x25] = 0x0001,
[0x26] = 0x0001,
[0x27] = 0x0015,
[0x28] = 0x0001,
[0x29] = 0x0000,
[0x2a] = 0x0000,
[0x2b] = 0x0000,
[0x2c] = 0x0002,
[0x2d] = 0x00ff,
[0x2e] = 0x0001,
[0x2f] = 0x0010,
[0x30] = 0x0000,
[0x31] = 0x003f,
[0x32] = 0x0000,
[0x33] = 0x0000,
[0x34] = 0x0001
};
#define SST29VF160_CFI_SIZE (sizeof(sst29vf160_cfi_data) / sizeof(sst29vf160_cfi_data[0]))
static void
flash_state_reset(x49gp_flash_t *flash)
{
if (flash->state != FLASH_STATE_NORMAL) {
cpu_register_physical_memory(0x00000000, SST29VF160_SIZE,
flash->offset | flash->iotype | IO_MEM_ROMD);
flash->state = FLASH_STATE_NORMAL;
}
}
static uint32_t
flash_get_halfword(x49gp_flash_t *flash, uint32_t offset)
{
uint8_t *datap = flash->data;
uint16_t data;
switch (flash->state) {
default:
flash_state_reset(flash);
/* fall through */
case FLASH_STATE_NORMAL:
data = lduw_p(datap + offset);
break;
case FLASH_STATE_SOFTWARE_ID:
if (offset & 2) {
data = flash->device_ID;
} else {
data = flash->vendor_ID;
}
break;
case FLASH_STATE_CFI_QUERY:
if (offset < flash->cfi_size) {
data = flash->cfi_data[offset >> 1];
} else {
data = 0x0000;
}
break;
}
return data;
}
static void
flash_put_halfword(x49gp_flash_t *flash, uint32_t offset, uint32_t data)
{
uint8_t *datap = flash->data;
uint16_t temp;
data &= 0xffff;
switch (flash->state) {
default:
flash_state_reset(flash);
/* fall through */
case FLASH_STATE_NORMAL:
if (((offset >> 1) == 0x5555) && ((data & 0xff) == 0xaa)) {
flash->state = FLASH_STATE_UNLOCK1;
cpu_register_physical_memory(0x00000000, SST29VF160_SIZE, flash->iotype);
}
break;
case FLASH_STATE_UNLOCK1:
if (((offset >> 1) == 0x2aaa) && ((data & 0xff) == 0x55)) {
flash->state = FLASH_STATE_UNLOCK2;
} else {
flash_state_reset(flash);
}
break;
case FLASH_STATE_UNLOCK2:
if ((offset >> 1) == 0x5555) {
switch (data & 0xff) {
case 0xa0:
flash->state = FLASH_STATE_WORD_PROG;
break;
case 0x80:
flash->state = FLASH_STATE_ERASE1;
break;
case 0x90:
flash->state = FLASH_STATE_SOFTWARE_ID;
break;
case 0x98:
flash->state = FLASH_STATE_CFI_QUERY;
break;
default:
flash_state_reset(flash);
break;
}
} else {
flash_state_reset(flash);
}
break;
case FLASH_STATE_ERASE1:
if (((offset >> 1) == 0x5555) && ((data & 0xff) == 0xaa)) {
flash->state = FLASH_STATE_ERASE2;
} else {
flash_state_reset(flash);
}
break;
case FLASH_STATE_ERASE2:
if (((offset >> 1) == 0x2aaa) && ((data & 0xff) == 0x55)) {
flash->state = FLASH_STATE_ERASE3;
} else {
flash_state_reset(flash);
}
break;
case FLASH_STATE_SOFTWARE_EXIT1:
if (((offset >> 1) == 0x2aaa) && ((data & 0xff) == 0x55)) {
flash->state = FLASH_STATE_SOFTWARE_EXIT2;
} else {
flash->state = FLASH_STATE_SOFTWARE_ID;
}
break;
case FLASH_STATE_SOFTWARE_EXIT2:
if (((offset >> 1) == 0x5555) && ((data & 0xff) == 0xf0)) {
flash_state_reset(flash);
} else {
flash->state = FLASH_STATE_SOFTWARE_ID;
}
break;
case FLASH_STATE_CFI_QUERY_EXIT1:
if (((offset >> 1) == 0x2aaa) && ((data & 0xff) == 0x55)) {
flash->state = FLASH_STATE_CFI_QUERY_EXIT2;
} else {
flash->state = FLASH_STATE_CFI_QUERY;
}
break;
case FLASH_STATE_CFI_QUERY_EXIT2:
if (((offset >> 1) == 0x5555) && ((data & 0xff) == 0xf0)) {
flash_state_reset(flash);
} else {
flash->state = FLASH_STATE_CFI_QUERY;
}
break;
case FLASH_STATE_SOFTWARE_ID:
if (((offset >> 1) == 0x5555) && ((data & 0xff) == 0xaa)) {
flash->state = FLASH_STATE_SOFTWARE_EXIT1;
} else if ((data & 0xff) == 0xf0) {
flash_state_reset(flash);
}
break;
case FLASH_STATE_CFI_QUERY:
if (((offset >> 1) == 0x5555) && ((data & 0xff) == 0xaa)) {
flash->state = FLASH_STATE_CFI_QUERY_EXIT1;
} else if ((data & 0xff) == 0xf0) {
flash_state_reset(flash);
}
break;
case FLASH_STATE_WORD_PROG:
temp = lduw_p(datap + offset);
stw_p(datap + offset, data & temp);
#ifdef DEBUG_X49GP_FLASH_WRITE
printf("write FLASH 2 (state %u) at offset %08x: %04x, result: %04x\n",
flash->state, offset, data, lduw_p(datap + offset));
#endif
flash_state_reset(flash);
break;
case FLASH_STATE_ERASE3:
switch (data & 0xff) {
case 0x10: /* Chip Erase */
#ifdef DEBUG_X49GP_FLASH_WRITE
printf("erase FLASH %08x %08x\n", 0, SST29VF160_SIZE);
#endif
memset(datap, 0xff, SST29VF160_SIZE);
break;
case 0x30: /* Sector Erase */
#ifdef DEBUG_X49GP_FLASH_WRITE
printf("erase FLASH %08x %08x\n",
(offset & ~(flash->sector_size - 1)),
flash->sector_size);
#endif
memset(datap + (offset & ~(flash->sector_size - 1)), 0xff, flash->sector_size);
#ifdef DEBUG_X49GP_FLASH_WRITE
printf("erase FLASH %08x: %04x, %08x: %04x, %08x: %04x\n",
offset, lduw_p(datap + offset),
offset + 0x800, lduw_p(datap + offset + 0x800),
offset + 0xffc, lduw_p(datap + offset + 0xffc));
#endif
break;
case 0x50: /* Block Erase */
#ifdef DEBUG_X49GP_FLASH_WRITE
printf("erase FLASH %08x %08x\n",
(offset & ~(flash->block_size - 1)),
flash->block_size);
#endif
memset(datap + (offset & ~(flash->block_size - 1)), 0xff, flash->block_size);
break;
default:
break;
}
flash_state_reset(flash);
break;
}
}
static uint32_t
flash_readb(void *opaque, target_phys_addr_t offset)
{
x49gp_flash_t *flash = opaque;
uint8_t *datap = flash->data;
unsigned short temp;
uint32_t shift;
unsigned char data;
if (flash->state == FLASH_STATE_NORMAL) {
data = *(datap + offset);
} else {
temp = flash_get_halfword(flash, offset & ~(1));
shift = (offset & 1) << 3;
data = (temp >> shift) & 0xff;
}
#ifdef DEBUG_X49GP_FLASH_READ
printf("read FLASH 1 (state %u) at offset %08lx: %02x\n",
flash->state, (unsigned long) offset, data);
#endif
return data;
}
static uint32_t
flash_readw(void *opaque, target_phys_addr_t offset)
{
x49gp_flash_t *flash = opaque;
uint8_t *datap = flash->data;
uint32_t data;
if (flash->state == FLASH_STATE_NORMAL) {
data = lduw_p(datap + offset);
} else {
data = flash_get_halfword(flash, offset);
}
#ifdef DEBUG_X49GP_FLASH_READ
printf("read FLASH 2 (state %u) at offset %08lx: %04x\n",
flash->state, (unsigned long) offset, data);
#endif
return data;
}
static uint32_t
flash_readl(void *opaque, target_phys_addr_t offset)
{
x49gp_flash_t *flash = opaque;
uint8_t *datap = flash->data;
uint32_t data;
if (flash->state == FLASH_STATE_NORMAL) {
data = ldl_p(datap + offset);
} else {
data = (flash_get_halfword(flash, offset + 2) << 16) |
(flash_get_halfword(flash, offset + 0) << 0);
}
#ifdef DEBUG_X49GP_FLASH_READ
printf("read FLASH 4 (state %u) at offset %08lx: %08x\n",
flash->state, (unsigned long) offset, data);
#endif
return data;
}
static void
flash_writeb(void *opaque, target_phys_addr_t offset, uint32_t data)
{
x49gp_flash_t *flash = opaque;
uint32_t shift;
data &= 0xff;
#ifdef DEBUG_X49GP_FLASH_WRITE
printf("write FLASH 1 (state %u) at offset %08lx: %02x\n",
flash->state, offset, data);
#endif
/*
* This does not issue read-modify-write, i.e. you will get
* broken data in FLASH memory. This would be the case with
* real hardware, too.
*/
shift = (offset & 1) << 3;
flash_put_halfword(flash, offset & ~(1), data << shift);
}
static void
flash_writew(void *opaque, target_phys_addr_t offset, uint32_t data)
{
x49gp_flash_t *flash = opaque;
data &= 0xffff;
#ifdef DEBUG_X49GP_FLASH_WRITE
printf("write FLASH 2 (state %u) at offset %08lx: %04x\n",
flash->state, offset, data);
#endif
flash_put_halfword(flash, offset, data);
}
static void
flash_writel(void *opaque, target_phys_addr_t offset, uint32_t data)
{
x49gp_flash_t *flash = opaque;
#ifdef DEBUG_X49GP_FLASH_WRITE
printf("write FLASH 4 (state %u) at offset %08lx: %08x\n",
flash->state, offset, data);
#endif
flash_put_halfword(flash, offset + 2, (data >> 16) & 0xffff);
flash_put_halfword(flash, offset + 0, (data >> 0) & 0xffff);
}
static int
flash_load(x49gp_module_t *module, GKeyFile *key)
{
x49gp_flash_t *flash = module->user_data;
x49gp_t *x49gp = module->x49gp;
x49gp_ui_t *ui = x49gp->ui;
int calc = ui->calculator;
char *filename;
struct stat st;
char *bootfile;
int bootfd, fwfd;
int error;
int i;
char bank_marker[5] = {0xf0, 0x02, 0x00, 0x00, 0x00};
int bytes_read;
#ifdef DEBUG_X49GP_MODULES
printf("%s: %s:%u\n", module->name, __FUNCTION__, __LINE__);
#endif
error = x49gp_module_get_filename(module, key, "filename", "flash",
&(flash->filename), &filename);
flash->fd = open(filename, O_RDWR | O_CREAT, 0644);
if (flash->fd < 0) {
error = -errno;
fprintf(stderr, "%s: %s:%u: open %s: %s\n",
module->name, __FUNCTION__, __LINE__,
filename, strerror(errno));
g_free(filename);
return error;
}
flash->size = SST29VF160_SIZE;
if (fstat(flash->fd, &st) < 0) {
error = -errno;
fprintf(stderr, "%s: %s:%u: fstat %s: %s\n",
module->name, __FUNCTION__, __LINE__,
filename, strerror(errno));
g_free(filename);
close(flash->fd);
flash->fd = -1;
return error;
}
if (ftruncate(flash->fd, flash->size) < 0) {
error = -errno;
fprintf(stderr, "%s: %s:%u: ftruncate %s: %s\n",
module->name, __FUNCTION__, __LINE__,
filename, strerror(errno));
g_free(filename);
close(flash->fd);
flash->fd = -1;
return error;
}
flash->data = mmap(phys_ram_base + flash->offset, flash->size,
PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED,
flash->fd, 0);
if (flash->data == (void *) -1) {
error = -errno;
fprintf(stderr, "%s: %s:%u: mmap %s: %s\n",
module->name, __FUNCTION__, __LINE__,
filename, strerror(errno));
g_free(filename);
close(flash->fd);
flash->fd = -1;
return error;
}
if (flash->size > st.st_size) {
fprintf(stderr, "Flash too small, rebuilding\n");
x49gp->startup_reinit = X49GP_REINIT_FLASH_FULL;
}
if (x49gp->startup_reinit >= X49GP_REINIT_FLASH) {
if (x49gp->startup_reinit == X49GP_REINIT_FLASH_FULL)
memset(phys_ram_base + flash->offset, 0xff,
flash->size - st.st_size);
bootfd = x49gp_module_open_rodata(module,
calc == UI_CALCULATOR_HP49GP ||
calc == UI_CALCULATOR_HP49GP_NEWRPL ?
"boot-49g+.bin" :
"boot-50g.bin",
&bootfile);
if (bootfd < 0) {
g_free(filename);
close(flash->fd);
flash->fd = -1;
return bootfd;
}
if (read(bootfd, phys_ram_base + flash->offset,
BOOT_SIZE) < 0) {
error = -errno;
fprintf(stderr, "%s: %s:%u: read %s: %s\n",
module->name, __FUNCTION__, __LINE__,
filename, strerror(errno));
g_free(filename);
g_free(bootfile);
close(bootfd);
close(flash->fd);
flash->fd = -1;
return error;
}
g_free(filename);
close(bootfd);
g_free(bootfile);
if (x49gp->startup_reinit == X49GP_REINIT_FLASH_FULL) {
/* The stock firmware expects special markers in certain
spots across the flash. Without these, the user banks
act up and are not usable, and PINIT apparently won't
fix it. Let's help it out; custom firmware will have
to deal with remnants of the user banks on real
calculators anyway, so if they break here, they will
too on actual hardware because that always comes with
the stock firmware and its user banks marked
properly. */
for (i=2;i<14;i++) {
bank_marker[1] = i;
memcpy(phys_ram_base + flash->offset + 0x40100 +
0x20000 * i, bank_marker, 5);
}
}
retry:
filename = NULL;
if (x49gp->firmware != NULL) {
filename = g_strdup(x49gp->firmware);
} else {
x49gp_ui_open_firmware(x49gp, &filename);
}
if (filename != NULL) {
fwfd = open(filename, O_RDONLY);
if (fwfd < 0) {
fprintf(stderr, "%s: %s:%u: open %s: %s\n",
module->name, __FUNCTION__, __LINE__,
filename, strerror(errno));
/* Mark firmware as invalid if there is one */
memset(phys_ram_base + flash->offset +
BOOT_SIZE, 0, 16);
if (x49gp->firmware != NULL) {
fprintf(stderr, "Warning: Could not "
"open selected firmware, "
"falling back to bootloader "
"recovery tools\n");
} else {
x49gp_ui_show_error(x49gp,
"Could not open "
"selected "
"firmware!");
goto retry;
}
} else {
bytes_read = read(fwfd, phys_ram_base +
flash->offset + BOOT_SIZE,
16);
if (bytes_read < 0) {
fprintf(stderr, "%s: %s:%u: read %s: %s\n",
module->name, __FUNCTION__,
__LINE__, filename,
strerror(errno));
/* Mark firmware as invalid
if there is one */
memset(phys_ram_base + flash->offset +
BOOT_SIZE, 0, 16);
if (x49gp->firmware != NULL) {
fprintf(stderr, "Warning: "
"Could not read "
"selected firmware, "
"falling back to "
"bootloader recovery "
"tools\n");
} else {
x49gp_ui_show_error(x49gp,
"Could not "
"read "
"selected "
"firmware!");
goto retry;
}
} else if (bytes_read < 16 ||
memcmp(phys_ram_base +
flash->offset + BOOT_SIZE,
"KINPOUPDATEIMAGE", 16)
!= 0) {
/* Mark firmware as invalid */
memset(phys_ram_base + flash->offset +
BOOT_SIZE, 0, 16);
if (x49gp->firmware != NULL) {
fprintf(stderr, "Warning: "
"Firmware is invalid, "
"falling back to "
"bootloader recovery "
"tools\n");
} else {
x49gp_ui_show_error(x49gp,
"Selected "
"firmware "
"is "
"invalid!");
goto retry;
}
/* The firmware may be shorter than
SST29VF160_SIZE - BOOT_SIZE, but if so,
read will just give us what it sees.
The space after that will remain empty. */
} else if (read(fwfd, phys_ram_base +
flash->offset + BOOT_SIZE + 16,
SST29VF160_SIZE -
(BOOT_SIZE + 16))
< 0) {
fprintf(stderr, "%s: %s:%u: read %s: %s\n",
module->name, __FUNCTION__,
__LINE__, filename,
strerror(errno));
/* Mark firmware as invalid
if there is one */
memset(phys_ram_base + flash->offset +
BOOT_SIZE, 0, 16);
if (x49gp->firmware != NULL) {
fprintf(stderr, "Warning: "
"Could not read "
"selected firmware, "
"falling back to "
"bootloader recovery "
"tools\n");
} else {
x49gp_ui_show_error(x49gp,
"Could not "
"read "
"selected "
"firmware!");
goto retry;
}
} else {
/* Mark firmware as valid in the same
way the bootloader does */
memcpy(phys_ram_base + flash->offset +
BOOT_SIZE, "Kinposhcopyright",
16);
}
close(fwfd);
}
g_free(filename);
}
} else {
g_free(filename);
}
return error;
}
static int
flash_save(x49gp_module_t *module, GKeyFile *key)
{
x49gp_flash_t *flash = module->user_data;
int error;
#ifdef DEBUG_X49GP_MODULES
printf("%s: %s:%u\n", module->name, __FUNCTION__, __LINE__);
#endif
x49gp_module_set_filename(module, key, "filename", flash->filename);
error = msync(flash->data, flash->size, MS_ASYNC);
if (error) {
fprintf(stderr, "%s:%u: msync: %s\n",
__FUNCTION__, __LINE__, strerror(errno));
return error;
}
error = fsync(flash->fd);
if (error) {
fprintf(stderr, "%s:%u: fsync: %s\n",
__FUNCTION__, __LINE__, strerror(errno));
return error;
}
return 0;
}
static int
flash_reset(x49gp_module_t *module, x49gp_reset_t reset)
{
x49gp_flash_t *flash = module->user_data;
#ifdef DEBUG_X49GP_MODULES
printf("%s: %s:%u\n", module->name, __FUNCTION__, __LINE__);
#endif
flash_state_reset(flash);
return 0;
}
static CPUReadMemoryFunc *flash_readfn[] =
{
flash_readb,
flash_readw,
flash_readl
};
static CPUWriteMemoryFunc *flash_writefn[] =
{
flash_writeb,
flash_writew,
flash_writel
};
static int
flash_init(x49gp_module_t *module)
{
x49gp_flash_t *flash;
#ifdef DEBUG_X49GP_MODULES
printf("%s: %s:%u\n", module->name, __FUNCTION__, __LINE__);
#endif
flash = malloc(sizeof(x49gp_flash_t));
if (NULL == flash) {
fprintf(stderr, "%s: %s:%u: Out of memory\n",
module->name, __FUNCTION__, __LINE__);
return -1;
}
memset(flash, 0, sizeof(x49gp_flash_t));
flash->vendor_ID = SST29VF160_VENDOR_ID;
flash->device_ID = SST29VF160_DEVICE_ID;
flash->cfi_data = sst29vf160_cfi_data;
flash->cfi_size = SST29VF160_CFI_SIZE;
flash->sector_size = SST29VF160_SECTOR_SIZE;
flash->block_size = SST29VF160_BLOCK_SIZE;
flash->fd = -1;
module->user_data = flash;
flash->iotype = cpu_register_io_memory(flash_readfn,
flash_writefn, flash);
flash->data = (void *) -1;
flash->offset = phys_ram_size;
phys_ram_size += SST29VF160_SIZE;
cpu_register_physical_memory(0x00000000, SST29VF160_SIZE,
flash->offset | flash->iotype | IO_MEM_ROMD);
return 0;
}
static int
flash_exit(x49gp_module_t *module)
{
x49gp_flash_t *flash;
#ifdef DEBUG_X49GP_MODULES
printf("%s: %s:%u\n", module->name, __FUNCTION__, __LINE__);
#endif
if (module->user_data) {
flash = module->user_data;
if (flash->data != (void *) -1) {
munmap(flash->data, flash->size);
}
if (flash->fd) {
close(flash->fd);
}
free(flash);
}
x49gp_module_unregister(module);
free(module);
return 0;
}
int
x49gp_flash_init(x49gp_t *x49gp)
{
x49gp_module_t *module;
if (x49gp_module_init(x49gp, "flash", flash_init, flash_exit,
flash_reset, flash_load, flash_save, NULL,
&module)) {
return -1;
}
return x49gp_module_register(module);
}