glider/utils/wbf_waveform_dump/main.c

568 lines
17 KiB
C

/*******************************************************************************
* Eink waveform firmware dumper
* Based on https://github.com/fread-ink/inkwave and Linux kernel
*
* This is free software: you can redistribute it and/or modify it under the
* terms of the GNU General Public License as published by the Free Software
* Foundation, either version 2 of the License, or (at your option) any later
* version.
*
* This software is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License along with
* the software. If not, see <http://www.gnu.org/licenses/>.
*
* This file is partially derived from Linux kernel driver, with the following
* copyright information:
* Copyright 2004-2013 Freescale Semiconductor, Inc.
* Copyright 2005-2017 Amazon Technologies, Inc.
* Copyright 2018, 2021 Marc Juul
* Copyright (C) 2022 Samuel Holland <samuel@sholland.org>
* Copyright 2024 Wenting Zhang
******************************************************************************/
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include <inttypes.h>
#include <ctype.h>
struct waveform_data_header {
uint32_t checksum:32; // 0
uint32_t filesize:32; // 4
uint32_t serial:32; // 8 serial number
uint32_t run_type:8; // 12
uint32_t fpl_platform:8; // 13
uint32_t fpl_lot:16; // 14
uint32_t mode_version_or_adhesive_run_num:8; // 16
uint32_t waveform_version:8; // 17
uint32_t waveform_subversion:8; // 18
uint32_t waveform_type:8; // 19
uint32_t fpl_size:8; // 20 (aka panel_size)
uint32_t mfg_code:8; // 21 (aka amepd_part_number)
uint32_t waveform_tuning_bias_or_rev:8; // 22
uint32_t fpl_rate:8; // 23 (aka frame_rate)
uint32_t unknown0:8;
uint32_t vcom_shifted:8;
uint32_t unknown1:16;
uint32_t xwia:24; // address of extra waveform information
uint32_t cs1:8; // checksum 1
uint32_t wmta:24;
uint32_t fvsn:8;
uint32_t luts:8;
uint32_t mc:8; // mode count (length of mode table - 1)
uint32_t trc:8; // temperature range count (length of temperature table - 1)
uint32_t advanced_wfm_flags:8;
uint32_t eb:8;
uint32_t sb:8;
uint32_t reserved0_1:8;
uint32_t reserved0_2:8;
uint32_t reserved0_3:8;
uint32_t reserved0_4:8;
uint32_t reserved0_5:8;
uint32_t cs2:8; // checksum 2
}__attribute__((packed));
#define HEADER_SIZE sizeof(struct waveform_data_header)
#define MODE_MAX 10
// Mode version to mode string
struct mode_name_lut_t {
uint8_t versions[2];
char *mode_strings[MODE_MAX];
};
// Partially derived from linux kernel drm_epd_helper.c
// All GL series (GL GLR GLD) are marked as GL as the underlying wavetable seems to be identical
// Thus it's impossible to identify the correct ordering
// -R/ -D ghosting reduction is outside of the scope of this tool
const struct mode_name_lut_t mode_name_lut[] = {
{
// Example: ED050SC3
.versions = {0x01},
.mode_strings = {"INIT", "DU", "GL8", "GC8"}
},
{
// Example: ED097TC1
.versions = {0x03},
.mode_strings = {"INIT", "DU", "GL16", "GL16", "A2"}
},
{
// Example: ET073TC1
.versions = {0x09},
.mode_strings = {"INIT", "DU", "GC16", "A2"}
},
{
// Untested
.versions = {0x12},
.mode_strings = {"INIT", "DU", "NULL", "GC16", "A2", "GL16", "GL16", "DU4"}
},
{
// Example: ES133UT1
.versions = {0x15},
.mode_strings = {"INIT", "DU", "GC16", "GL16", "A2", "DU4", "GC4"}
},
{
// Untested
.versions = {0x16},
.mode_strings = {"INIT", "DU", "GC16", "GL16", "GL16", "GC16", "A2"}
},
{
// Example: ES108FC1
.versions = {0x18, 0x20},
.mode_strings = {"INIT", "DU", "GC16", "GL16", "GL16", "GL16", "A2"}
},
{
// Example: ES103TC1
.versions = {0x19, 0x43},
.mode_strings = {"INIT", "DU", "GC16", "GL16", "GL16", "GL16", "A2", "DU4"}
},
{
// Untested
.versions = {0x23},
.mode_strings = {"INIT", "DU", "GC16", "GL16", "A2", "DU4"}
},
{
// Untested
.versions = {0x54},
.mode_strings = {"INIT", "DU", "GC16", "GL16", "GL16", "A2"}
},
{
// Example: ES120MC1
.versions = {0x48},
.mode_strings = {"INIT", "DU", "GC16", "GL16", "GL16", "NULL", "A2"}
}
};
#define MODE_NAME_LUTS (sizeof(mode_name_lut) / sizeof(*mode_name_lut))
#define MAX_TABLE_LENGTH (1024*1024)
static uint32_t read_pointer(uint8_t *ptr) {
uint32_t addr = ((uint32_t)ptr[2] << 16) | ((uint32_t)ptr[1] << 8) | ((uint32_t)ptr[0]);
uint8_t checksum = ptr[0] + ptr[1] + ptr[2];
if (checksum != ptr[3]) {
printf("Pointer checksum mismatch\n");
}
return addr;
}
static uint8_t read_uint4(uint8_t* src, size_t addr) {
uint8_t val = src[addr >> 1];
if (addr & 1)
val = (val >> 4) & 0xf;
else
val = val & 0xf;
return val;
}
void compute_crc_table(unsigned int* crc_table) {
unsigned c;
int n, k;
for (n = 0; n < 256; n++) {
c = (unsigned) n;
for (k = 0; k < 8; k++) {
if (c & 1) {
c = 0xedb88320L ^ (c >> 1);
}
else {
c = c >> 1;
}
}
crc_table[n] = c;
}
}
unsigned int update_crc(unsigned int* crc_table, unsigned crc,
unsigned char *buf, int len) {
char b;
unsigned c = crc ^ 0xffffffff;
int i;
for(i=0; i < len; i++) {
if(!buf) {
b = 0;
} else {
b = buf[i];
}
c = crc_table[(c ^ b) & 0xff] ^ (c >> 8);
}
return c ^ 0xffffffff;
}
unsigned crc32(unsigned char *buf, int len) {
static unsigned int crc_table[256];
compute_crc_table(crc_table);
return update_crc(crc_table, 0, buf, len);
}
void dump_phases(FILE* fp, uint8_t* buffer, int bpp, int phases, int phase_per_byte) {
int states;
int i, j, k, x, y;
states = (bpp == 4) ? 16 : 32;
uint8_t luts[phases][states][states];
uint8_t *ptr = buffer;
for (i = 0; i < phases; i ++) {
for (x = 0; x < states; x++) {
for (y = 0; y < states; y+=phase_per_byte) {
uint8_t val = *ptr++;
if (phase_per_byte == 4) {
luts[i][y+0][x] = (val >> 0) & 0x3;
luts[i][y+1][x] = (val >> 2) & 0x3;
luts[i][y+2][x] = (val >> 4) & 0x3;
luts[i][y+3][x] = (val >> 6) & 0x3;
}
else if (phase_per_byte == 2) {
luts[i][y+0][x] = (val >> 0) & 0xf;
luts[i][y+1][x] = (val >> 4) & 0xf;
}
}
}
}
for (i = 0; i < states; i++) {
for (j = 0; j < states; j++) {
fprintf(fp, "%d,%d,", i, j);
for (k = 0; k < phases; k++) {
fprintf(fp, "%d,", luts[k][i][j]);
}
fprintf(fp, "\n");
}
}
}
int main(int argc, char **argv) {
fprintf(stderr, "Eink wbf waveform dumper\n");
if (argc != 3) {
fprintf(stderr, "Usage: wbf_wvfm_dump input_file output_prefix\n");
fprintf(stderr, "input_file: MXC EPDC firmware file, in .wbf format\n");
fprintf(stderr, "output_prefix: Prefix for output file name, without extension\n");
fprintf(stderr, "Example: wbf_wvfm_dump E060SCM.wbf e060scm\n");
return 1;
}
char *fw = argv[1];
char *prefix = argv[2];
FILE * fp;
size_t file_size;
char * file_buffer;
size_t result;
fp = fopen(fw, "rb");
assert(fp);
// obtain file size:
fseek(fp, 0, SEEK_END);
file_size = ftell(fp);
fseek(fp, 0, SEEK_SET);
// allocate memory to contain the whole file:
file_buffer = (char*)malloc(sizeof(char) * file_size);
assert(file_buffer);
// copy the file into the buffer:
assert(fread(file_buffer, file_size, 1, fp) == 1);
fclose(fp);
/* the whole file is now loaded in the memory buffer. */
struct waveform_data_header *header;
header = (struct waveform_data_header *)file_buffer;
printf("File size: %zu bytes.\n", file_size);
printf("Reported size: %d bytes.\n", header->filesize);
printf("Serial number: %d\n", header->serial);
printf("Run type: 0x%x\n", header->run_type);
printf("Mfg code: 0x%x\n", header->mfg_code);
printf("FPL platform: 0x%x\n", header->fpl_platform);
printf("FPL lot: 0x%x\n", header->fpl_lot);
printf("FPL size: 0x%x\n", header->fpl_size);
printf("FPL rate: 0x%x\n", header->fpl_rate);
printf("Mode version: 0x%x\n", header->mode_version_or_adhesive_run_num);
printf("Waveform version: %d\n", header->waveform_version);
printf("Waveform sub-version: %d\n", header->waveform_subversion);
printf("Waveform type: %d\n", header->waveform_type);
printf("Number of modes: %d\n", header->mc + 1);
printf("Number of temperature ranges: %d\n", header->trc + 1);
int bpp = ((header->luts & 0xC) == 0x4) ? 5: 4;
printf("BPP: %d (LUTS = 0x%02x)\n", bpp, header->luts);
bool acep = false;
if (header->luts == 0x15) {
printf("Looks like you've supplied a waveform for ACeP screens\n");
printf("Expect the checksum for the header to fail.\n");
bpp = 5;
acep = true;
}
// Compare checksum
static unsigned int crc_table[256];
compute_crc_table(crc_table);
unsigned int crc;
if (header->filesize != 0) {
crc = update_crc(crc_table, 0, NULL, 4);
crc = update_crc(crc_table, crc, (unsigned char *)file_buffer + 4, header->filesize-4);
printf("Checksum: 0x%08x\n", crc);
if (crc == header->checksum) {
printf("Checksum match.\n");
} else {
printf("Checksum mismatch! Expected: 0x%08x\n", header->checksum);
}
}
else {
printf("File size reported to be 0 in the header. Checksum check skipped.\n");
}
// Try to find mode description
char **mode_string = NULL;
uint8_t mode_version = header->mode_version_or_adhesive_run_num;
for (int i = 0; i < MODE_NAME_LUTS; i++) {
if ((mode_name_lut[i].versions[0] == mode_version) ||
(mode_name_lut[i].versions[1] == mode_version)) {
mode_string = (char **)(mode_name_lut[i].mode_strings);
}
}
if (mode_string) {
printf("Known mode version, mode names available.\n");
}
else {
printf("Unknown mode version, mode names won't be available.\n");
}
// Right following the header is the temperature range table
uint8_t checksum = 0;
int i, j;
int trt_entries; // temperature range table
uint8_t *temp_range_bounds;
trt_entries = header->trc + 1;
printf("Temperatures count: %d\n", trt_entries);
temp_range_bounds = (uint8_t*)malloc(trt_entries + 2);
memcpy(temp_range_bounds, file_buffer + HEADER_SIZE, trt_entries + 2);
for (i = 0; i < trt_entries; i++) {
printf("Temperature %d = %d°C\n", i, temp_range_bounds[i]);
checksum += temp_range_bounds[i];
}
printf("End bound: %d°C\n", temp_range_bounds[trt_entries]);
checksum += temp_range_bounds[trt_entries];
if (checksum != temp_range_bounds[trt_entries + 1]) {
printf("Temperature table checksum mismatch\n");
}
char xwia_buffer[128];
uint8_t xwia_len = 0;
if (header->xwia != 0) {
printf("Extra waveform information present:\n");
uint8_t *ptr = (uint8_t *)file_buffer + header->xwia;
xwia_len = *ptr++;
checksum = xwia_len;
j = 0;
for (i = 0; i < xwia_len; i++) {
char c = *ptr++;
if (isprint(c)) {
xwia_buffer[j++] = c;
}
else {
xwia_buffer[j++] = '\\';
xwia_buffer[j++] = 'x';
xwia_buffer[j++] = (c / 16) + '0';
xwia_buffer[j++] = (c % 16) + '0';
}
checksum += c;
}
xwia_buffer[j++] = '\0';
printf("%s", xwia_buffer);
printf("\n");
if (checksum != *ptr++) {
printf("XWIA checksum mismatch\n");
}
}
int wv_data_offs; // offset for waveform data
int waveform_buffer_size; // size for waveform data
int mode_count = header->mc + 1;
wv_data_offs = HEADER_SIZE + trt_entries + 2 + 1 + xwia_len + 1;
// This should yield the same result
if ((header->xwia) && (wv_data_offs != header->xwia + 1 + xwia_len + 1))
printf("Warning: data offset calculation mismatch\n");
printf("Waveform data offset: %d\n", wv_data_offs);
uint8_t *ptr = (uint8_t *)file_buffer + wv_data_offs;
uint32_t* wv_modes = malloc(sizeof(uint32_t) * mode_count);
uint32_t addr;
// get modes addr
for (i = 0; i < mode_count; i++) {
addr = read_pointer(ptr);
printf("wave #%d addr: %u\n", i, addr);
wv_modes[i] = addr;
ptr += 4;
}
// get modes temp addr
// Table offsets, ordered as first shown in the wbf, may not use up all space
uint32_t *table_offsets = malloc(sizeof(uint32_t) * mode_count * trt_entries + 1);
int *frame_counts = malloc(sizeof(uint32_t) * mode_count * trt_entries);
int tables = 0;
// Table index for each mode x temp
int *wv_modes_temps = malloc(sizeof(int) * mode_count * trt_entries);
for (i = 0; i < mode_count; i++) {
ptr = (uint8_t *)file_buffer + wv_modes[i];
for (j = 0; j < trt_entries; j++) {
addr = read_pointer(ptr);
// Find the table ID correspond to the address
// Ideally this should be an hash table, but given the extremely small
// problem size here, linear search is more than good enough.
int id = -1;
for (int k = 0; k < tables; k++)
if (table_offsets[k] == addr) {
id = k;
break;
}
if (id == -1) {
// New table
id = tables; // Assign ID
tables++;
table_offsets[id] = addr;
}
wv_modes_temps[i * trt_entries + j] = id;
printf("wave #%d, temp #%d: wavetable %d\n", i, j, id);
ptr += 4;
}
}
char* fn = malloc(strlen(prefix) + 14);
static uint8_t derle_buffer[MAX_TABLE_LENGTH];
for (i = 0; i < tables; i++) {
printf("Parsing table %d, addr %d (0x%06x)\n", i, table_offsets[i], table_offsets[i]);
ptr = (uint8_t *)file_buffer + table_offsets[i];
// Parse table
bool rle_mode = true;
uint32_t idx = 0;
uint8_t checksum = 0;
while(1) {
uint8_t chr = *ptr++;
checksum += chr;
if (chr == 0xfc) {
// Toggle RLE mode
rle_mode = !rle_mode;
}
else if (chr == 0xff) {
// End of block
break;
}
else if (!rle_mode) {
derle_buffer[idx++] = chr;
}
else {
uint8_t len = *ptr++;
checksum += len;
for (j = 0; j < (int)len + 1; j++) {
derle_buffer[idx++] = chr;
}
}
}
printf("Total %d bytes. Checksum: 0x%02x\n", idx, checksum);
if (*ptr++ != checksum) {
printf("Checksum mismatch!\n");
}
int phase_per_byte = acep ? 2 : 4;
int transitions = ((bpp == 4) ? (16 * 16) : (32 * 32));
int phases = idx * phase_per_byte / transitions;
frame_counts[i] = phases;
// Dump phases
sprintf(fn, "%s_TB%d.csv", prefix, i);
fp = fopen(fn, "w");
assert(fp);
size_t index = i * trt_entries + j;
dump_phases(fp, derle_buffer, bpp, phases, phase_per_byte);
fclose(fp);
}
sprintf(fn, "%s_desc.iwf", prefix);
fp = fopen(fn, "w");
assert(fp);
fprintf(fp, "[WAVEFORM]\n");
fprintf(fp, "VERSION = 2.0\n");
fprintf(fp, "PREFIX = %s\n", prefix);
fprintf(fp, "NAME = %s\n", xwia_buffer);
fprintf(fp, "BPP = %d\n", bpp);
fprintf(fp, "MODES = %d\n", mode_count);
fprintf(fp, "TEMPS = %d\n", trt_entries);
fprintf(fp, "TABLES = %d\n", tables);
fprintf(fp, "\n");
for (int i = 0; i < trt_entries; i++) {
fprintf(fp, "T%dRANGE = %d\n", i, temp_range_bounds[i]);
}
fprintf(fp, "TUPBOUND = %d\n", temp_range_bounds[trt_entries]);
fprintf(fp, "\n");
for (int i = 0; i < tables; i++) {
fprintf(fp, "TB%dFC = %d\n", i, frame_counts[i]);
}
fprintf(fp, "\n");
for (int i = 0; i < mode_count; i++) {
fprintf(fp, "[MODE%d]\n", i);
if (mode_string) {
fprintf(fp, "NAME = %s\n", mode_string[i]);
}
for (int j = 0; j < trt_entries; j++) {
fprintf(fp, "T%dTABLE = %d\n", j, wv_modes_temps[i * trt_entries + j]);
}
fprintf(fp, "\n");
}
fclose(fp);
printf("All done!\n");
free(fn);
free(temp_range_bounds);
free(table_offsets);
free(frame_counts);
free(wv_modes);
free(wv_modes_temps);
free(file_buffer);
return 0;
}