From a8f35f260462a26cfd4add46bc043a93f3869a79 Mon Sep 17 00:00:00 2001 From: Wenting Zhang Date: Tue, 14 May 2024 18:32:10 -0400 Subject: [PATCH] Check in waveform tools --- README.md | 5 + utils/mxc_waveform_asm/Makefile | 7 + utils/mxc_waveform_asm/csv.c | 143 ++++++ utils/mxc_waveform_asm/csv.h | 12 + utils/mxc_waveform_asm/fread_csv_line.c | 107 +++++ utils/mxc_waveform_asm/ini.c | 298 +++++++++++++ utils/mxc_waveform_asm/ini.h | 150 +++++++ utils/mxc_waveform_asm/main.c | 447 +++++++++++++++++++ utils/mxc_waveform_asm/split.c | 85 ++++ utils/mxc_waveform_dump/Makefile | 6 + utils/mxc_waveform_dump/main.c | 309 +++++++++++++ utils/wbf_flash_decompress/Makefile | 6 + utils/wbf_flash_decompress/main.c | 143 ++++++ utils/wbf_waveform_dump/Makefile | 6 + utils/wbf_waveform_dump/main.c | 568 ++++++++++++++++++++++++ 15 files changed, 2292 insertions(+) create mode 100644 utils/mxc_waveform_asm/Makefile create mode 100644 utils/mxc_waveform_asm/csv.c create mode 100644 utils/mxc_waveform_asm/csv.h create mode 100644 utils/mxc_waveform_asm/fread_csv_line.c create mode 100644 utils/mxc_waveform_asm/ini.c create mode 100644 utils/mxc_waveform_asm/ini.h create mode 100644 utils/mxc_waveform_asm/main.c create mode 100644 utils/mxc_waveform_asm/split.c create mode 100644 utils/mxc_waveform_dump/Makefile create mode 100644 utils/mxc_waveform_dump/main.c create mode 100644 utils/wbf_flash_decompress/Makefile create mode 100644 utils/wbf_flash_decompress/main.c create mode 100644 utils/wbf_waveform_dump/Makefile create mode 100644 utils/wbf_waveform_dump/main.c diff --git a/README.md b/README.md index 72c58ac..5f490ca 100755 --- a/README.md +++ b/README.md @@ -887,6 +887,11 @@ Here is a list of helpful references related to driving EPDs: * An early tool for reading Eink's wbf file format: https://github.com/fread-ink/inkwave * A more up-to-date Eink's wbf format parser: https://patchwork.kernel.org/project/linux-arm-kernel/patch/20220413221916.50995-2-samuel@sholland.org/ +On the topic of color screen resolution, color mapping, subpixel rendering: + +* Klompenhouwer, Michiel A., and Erno H. A. Langendijk. “59.4: Comparing the Effective Resolution of Various RGB Subpixel Layouts.” SID International Symposium Digest of Technical Papers, vol. 39, no. 1, 2008, pp. 907–10, https://doi.org/10.1889/1.3069822. +* Lai, Chih-chang, and Ching-chih Tsai. “A Modified Stripe-RGBW TFT-LCD with Image-Processing Engine for Mobile Phone Displays.” IEEE Transactions on Consumer Electronics, vol. 53, no. 4, 2007, pp. 1628–33, https://doi.org/10.1109/TCE.2007.4429262. + ## License This document, other than refereneces explicitly given with their corresponding license, is released into public domain. diff --git a/utils/mxc_waveform_asm/Makefile b/utils/mxc_waveform_asm/Makefile new file mode 100644 index 0000000..69e0da2 --- /dev/null +++ b/utils/mxc_waveform_asm/Makefile @@ -0,0 +1,7 @@ +all: mxc_wvfm_asm + +mxc_wvfm_asm: main.c ini.c csv.c fread_csv_line.c split.c + gcc -O1 -g main.c ini.c csv.c fread_csv_line.c split.c -o mxc_wvfm_asm + +clean: + rm -f mxc_wvfm_asm diff --git a/utils/mxc_waveform_asm/csv.c b/utils/mxc_waveform_asm/csv.c new file mode 100644 index 0000000..9a10873 --- /dev/null +++ b/utils/mxc_waveform_asm/csv.c @@ -0,0 +1,143 @@ +#include +#include + +void free_csv_line( char **parsed ) { + char **ptr; + + for ( ptr = parsed; *ptr; ptr++ ) { + free( *ptr ); + } + + free( parsed ); +} + +static int count_fields( const char *line ) { + const char *ptr; + int cnt, fQuote; + + for ( cnt = 1, fQuote = 0, ptr = line; *ptr; ptr++ ) { + if ( fQuote ) { + if ( *ptr == '\"' ) { + if ( ptr[1] == '\"' ) { + ptr++; + continue; + } + fQuote = 0; + } + continue; + } + + switch( *ptr ) { + case '\"': + fQuote = 1; + continue; + case ',': + cnt++; + continue; + default: + continue; + } + } + + if ( fQuote ) { + return -1; + } + + return cnt; +} + +/* + * Given a string containing no linebreaks, or containing line breaks + * which are escaped by "double quotes", extract a NULL-terminated + * array of strings, one for every cell in the row. + */ +char **parse_csv( const char *line ) { + char **buf, **bptr, *tmp, *tptr; + const char *ptr; + int fieldcnt, fQuote, fEnd; + + fieldcnt = count_fields( line ); + + if ( fieldcnt == -1 ) { + return NULL; + } + + buf = malloc( sizeof(char*) * (fieldcnt+1) ); + + if ( !buf ) { + return NULL; + } + + tmp = malloc( strlen(line) + 1 ); + + if ( !tmp ) { + free( buf ); + return NULL; + } + + bptr = buf; + + for ( ptr = line, fQuote = 0, *tmp = '\0', tptr = tmp, fEnd = 0; ; ptr++ ) { + if ( fQuote ) { + if ( !*ptr ) { + break; + } + + if ( *ptr == '\"' ) { + if ( ptr[1] == '\"' ) { + *tptr++ = '\"'; + ptr++; + continue; + } + fQuote = 0; + } + else { + *tptr++ = *ptr; + } + + continue; + } + + switch( *ptr ) { + case '\"': + fQuote = 1; + continue; + case '\0': + fEnd = 1; + case ',': + *tptr = '\0'; + *bptr = strdup( tmp ); + + if ( !*bptr ) { + for ( bptr--; bptr >= buf; bptr-- ) { + free( *bptr ); + } + free( buf ); + free( tmp ); + + return NULL; + } + + bptr++; + tptr = tmp; + + if ( fEnd ) { + break; + } else { + continue; + } + + default: + *tptr++ = *ptr; + continue; + } + + if ( fEnd ) { + break; + } + } + + *bptr = NULL; + free( tmp ); + return buf; +} diff --git a/utils/mxc_waveform_asm/csv.h b/utils/mxc_waveform_asm/csv.h new file mode 100644 index 0000000..55ad1aa --- /dev/null +++ b/utils/mxc_waveform_asm/csv.h @@ -0,0 +1,12 @@ +#ifndef CSV_DOT_H_INCLUDE_GUARD +#define CSV_DOT_H_INCLUDE_GUARD + +#define CSV_ERR_LONGLINE 0 +#define CSV_ERR_NO_MEMORY 1 + +char **parse_csv( const char *line ); +void free_csv_line( char **parsed ); +char **split_on_unescaped_newlines(const char *txt); +char *fread_csv_line(FILE *fp, int max_line_size, int *done, int *err, int rst); + +#endif diff --git a/utils/mxc_waveform_asm/fread_csv_line.c b/utils/mxc_waveform_asm/fread_csv_line.c new file mode 100644 index 0000000..ef4a515 --- /dev/null +++ b/utils/mxc_waveform_asm/fread_csv_line.c @@ -0,0 +1,107 @@ +#include +#include +#include +#include "csv.h" + +#define READ_BLOCK_SIZE 65536 +#define QUICK_GETC( ch, fp )\ +do\ +{\ + if ( read_ptr == read_end ) {\ + fread_len = fread( read_buf, sizeof(char), READ_BLOCK_SIZE, fp );\ + if ( fread_len < READ_BLOCK_SIZE ) {\ + read_buf[fread_len] = '\0';\ + }\ + read_ptr = read_buf;\ + }\ + ch = *read_ptr++;\ +}\ +while(0) + +/* + * Given a file pointer, read a CSV line from that file. + * File may include newlines escaped with "double quotes". + * + * Warning: This function is optimized for the use case where + * you repeatedly call it until the file is exhausted. It is + * very suboptimal for the use case of just grabbing one single + * line of CSV and stopping. Also, this function advances the + * file position (in the fseek/ftell sense) unpredictably. You + * should not change the file position between calls to + * fread_csv_line (e.g., don't use "getc" on the file in between + * calls to fread_csv_line). + * + * Other arguments: + * size_t max_line_size: Maximum line size, in bytes. + * int *done: Pointer to an int that will be set to 1 when file is exhausted. + * int *err: Pointer to an int where error code will be written. + * + * Warning: Calling this function on an exhausted file (as indicated by the + * 'done' flag) is undefined behavior. + * + * See csv.h for definitions of error codes. + */ +char *fread_csv_line(FILE *fp, int max_line_size, int *done, int *err, int rst) { + static FILE *bookmark; + static char read_buf[READ_BLOCK_SIZE], *read_ptr, *read_end; + static int fread_len, prev_max_line_size = -1; + static char *buf; + char *bptr, *limit; + char ch; + int fQuote; + + if ( max_line_size > prev_max_line_size ) { + if ( prev_max_line_size != -1 ) { + free( buf ); + } + buf = malloc( max_line_size + 1 ); + if ( !buf ) { + *err = CSV_ERR_NO_MEMORY; + prev_max_line_size = -1; + return NULL; + } + prev_max_line_size = max_line_size; + } + bptr = buf; + limit = buf + max_line_size; + + if (( bookmark != fp ) || ( rst )) { + read_ptr = read_end = read_buf + READ_BLOCK_SIZE; + bookmark = fp; + } + + for ( fQuote = 0; ; ) { + QUICK_GETC(ch, fp); + + if ( !ch || (ch == '\n' && !fQuote)) { + break; + } + + if ( bptr >= limit ) { + free( buf ); + *err = CSV_ERR_LONGLINE; + return NULL; + } + *bptr++ = ch; + + if ( fQuote ) { + if ( ch == '\"' ) { + QUICK_GETC(ch, fp); + + if ( ch != '\"' ) { + if ( !ch || ch == '\n' ) { + break; + } + fQuote = 0; + } + *bptr++ = ch; + } + } else if ( ch == '\"' ) { + fQuote = 1; + } + } + + *done = !ch; + *bptr = '\0'; + return strdup(buf); +} diff --git a/utils/mxc_waveform_asm/ini.c b/utils/mxc_waveform_asm/ini.c new file mode 100644 index 0000000..f8a3ea3 --- /dev/null +++ b/utils/mxc_waveform_asm/ini.c @@ -0,0 +1,298 @@ +/* inih -- simple .INI file parser + +SPDX-License-Identifier: BSD-3-Clause + +Copyright (C) 2009-2020, Ben Hoyt + +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: + +https://github.com/benhoyt/inih + +*/ + +#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) +#define _CRT_SECURE_NO_WARNINGS +#endif + +#include +#include +#include + +#include "ini.h" + +#if !INI_USE_STACK +#if INI_CUSTOM_ALLOCATOR +#include +void* ini_malloc(size_t size); +void ini_free(void* ptr); +void* ini_realloc(void* ptr, size_t size); +#else +#include +#define ini_malloc malloc +#define ini_free free +#define ini_realloc realloc +#endif +#endif + +#define MAX_SECTION 50 +#define MAX_NAME 50 + +/* Used by ini_parse_string() to keep track of string parsing state. */ +typedef struct { + const char* ptr; + size_t num_left; +} ini_parse_string_ctx; + +/* Strip whitespace chars off end of given string, in place. Return s. */ +static char* rstrip(char* s) +{ + char* p = s + strlen(s); + while (p > s && isspace((unsigned char)(*--p))) + *p = '\0'; + return s; +} + +/* Return pointer to first non-whitespace char in given string. */ +static char* lskip(const char* s) +{ + while (*s && isspace((unsigned char)(*s))) + s++; + return (char*)s; +} + +/* Return pointer to first char (of chars) or inline comment in given string, + or pointer to NUL at end of string if neither found. Inline comment must + be prefixed by a whitespace character to register as a comment. */ +static char* find_chars_or_comment(const char* s, const char* chars) +{ +#if INI_ALLOW_INLINE_COMMENTS + int was_space = 0; + while (*s && (!chars || !strchr(chars, *s)) && + !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) { + was_space = isspace((unsigned char)(*s)); + s++; + } +#else + while (*s && (!chars || !strchr(chars, *s))) { + s++; + } +#endif + return (char*)s; +} + +/* Similar to strncpy, but ensures dest (size bytes) is + NUL-terminated, and doesn't pad with NULs. */ +static char* strncpy0(char* dest, const char* src, size_t size) +{ + /* Could use strncpy internally, but it causes gcc warnings (see issue #91) */ + size_t i; + for (i = 0; i < size - 1 && src[i]; i++) + dest[i] = src[i]; + dest[i] = '\0'; + return dest; +} + +/* See documentation in header file. */ +int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user) +{ + /* Uses a fair bit of stack (use heap instead if you need to) */ +#if INI_USE_STACK + char line[INI_MAX_LINE]; + int max_line = INI_MAX_LINE; +#else + char* line; + size_t max_line = INI_INITIAL_ALLOC; +#endif +#if INI_ALLOW_REALLOC && !INI_USE_STACK + char* new_line; + size_t offset; +#endif + char section[MAX_SECTION] = ""; + char prev_name[MAX_NAME] = ""; + + char* start; + char* end; + char* name; + char* value; + int lineno = 0; + int error = 0; + +#if !INI_USE_STACK + line = (char*)ini_malloc(INI_INITIAL_ALLOC); + if (!line) { + return -2; + } +#endif + +#if INI_HANDLER_LINENO +#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno) +#else +#define HANDLER(u, s, n, v) handler(u, s, n, v) +#endif + + /* Scan through stream line by line */ + while (reader(line, (int)max_line, stream) != NULL) { +#if INI_ALLOW_REALLOC && !INI_USE_STACK + offset = strlen(line); + while (offset == max_line - 1 && line[offset - 1] != '\n') { + max_line *= 2; + if (max_line > INI_MAX_LINE) + max_line = INI_MAX_LINE; + new_line = ini_realloc(line, max_line); + if (!new_line) { + ini_free(line); + return -2; + } + line = new_line; + if (reader(line + offset, (int)(max_line - offset), stream) == NULL) + break; + if (max_line >= INI_MAX_LINE) + break; + offset += strlen(line + offset); + } +#endif + + lineno++; + + start = line; +#if INI_ALLOW_BOM + if (lineno == 1 && (unsigned char)start[0] == 0xEF && + (unsigned char)start[1] == 0xBB && + (unsigned char)start[2] == 0xBF) { + start += 3; + } +#endif + start = lskip(rstrip(start)); + + if (strchr(INI_START_COMMENT_PREFIXES, *start)) { + /* Start-of-line comment */ + } +#if INI_ALLOW_MULTILINE + else if (*prev_name && *start && start > line) { + /* Non-blank line with leading whitespace, treat as continuation + of previous name's value (as per Python configparser). */ + if (!HANDLER(user, section, prev_name, start) && !error) + error = lineno; + } +#endif + else if (*start == '[') { + /* A "[section]" line */ + end = find_chars_or_comment(start + 1, "]"); + if (*end == ']') { + *end = '\0'; + strncpy0(section, start + 1, sizeof(section)); + *prev_name = '\0'; +#if INI_CALL_HANDLER_ON_NEW_SECTION + if (!HANDLER(user, section, NULL, NULL) && !error) + error = lineno; +#endif + } + else if (!error) { + /* No ']' found on section line */ + error = lineno; + } + } + else if (*start) { + /* Not a comment, must be a name[=:]value pair */ + end = find_chars_or_comment(start, "=:"); + if (*end == '=' || *end == ':') { + *end = '\0'; + name = rstrip(start); + value = end + 1; +#if INI_ALLOW_INLINE_COMMENTS + end = find_chars_or_comment(value, NULL); + if (*end) + *end = '\0'; +#endif + value = lskip(value); + rstrip(value); + + /* Valid name[=:]value pair found, call handler */ + strncpy0(prev_name, name, sizeof(prev_name)); + if (!HANDLER(user, section, name, value) && !error) + error = lineno; + } + else if (!error) { + /* No '=' or ':' found on name[=:]value line */ +#if INI_ALLOW_NO_VALUE + *end = '\0'; + name = rstrip(start); + if (!HANDLER(user, section, name, NULL) && !error) + error = lineno; +#else + error = lineno; +#endif + } + } + +#if INI_STOP_ON_FIRST_ERROR + if (error) + break; +#endif + } + +#if !INI_USE_STACK + ini_free(line); +#endif + + return error; +} + +/* See documentation in header file. */ +int ini_parse_file(FILE* file, ini_handler handler, void* user) +{ + return ini_parse_stream((ini_reader)fgets, file, handler, user); +} + +/* See documentation in header file. */ +int ini_parse(const char* filename, ini_handler handler, void* user) +{ + FILE* file; + int error; + + file = fopen(filename, "r"); + if (!file) + return -1; + error = ini_parse_file(file, handler, user); + fclose(file); + return error; +} + +/* An ini_reader function to read the next line from a string buffer. This + is the fgets() equivalent used by ini_parse_string(). */ +static char* ini_reader_string(char* str, int num, void* stream) { + ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream; + const char* ctx_ptr = ctx->ptr; + size_t ctx_num_left = ctx->num_left; + char* strp = str; + char c; + + if (ctx_num_left == 0 || num < 2) + return NULL; + + while (num > 1 && ctx_num_left != 0) { + c = *ctx_ptr++; + ctx_num_left--; + *strp++ = c; + if (c == '\n') + break; + num--; + } + + *strp = '\0'; + ctx->ptr = ctx_ptr; + ctx->num_left = ctx_num_left; + return str; +} + +/* See documentation in header file. */ +int ini_parse_string(const char* string, ini_handler handler, void* user) { + ini_parse_string_ctx ctx; + + ctx.ptr = string; + ctx.num_left = strlen(string); + return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler, + user); +} diff --git a/utils/mxc_waveform_asm/ini.h b/utils/mxc_waveform_asm/ini.h new file mode 100644 index 0000000..007b9ef --- /dev/null +++ b/utils/mxc_waveform_asm/ini.h @@ -0,0 +1,150 @@ +/* inih -- simple .INI file parser +SPDX-License-Identifier: BSD-3-Clause +Copyright (C) 2009-2020, Ben Hoyt +inih is released under the New BSD license (see LICENSE.txt). Go to the project +home page for more info: +https://github.com/benhoyt/inih +*/ + +#ifndef INI_H +#define INI_H + +/* Make this header file easier to include in C++ code */ +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/* Nonzero if ini_handler callback should accept lineno parameter. */ +#ifndef INI_HANDLER_LINENO +#define INI_HANDLER_LINENO 0 +#endif + +/* Typedef for prototype of handler function. */ +#if INI_HANDLER_LINENO +typedef int (*ini_handler)(void* user, const char* section, + const char* name, const char* value, + int lineno); +#else +typedef int (*ini_handler)(void* user, const char* section, + const char* name, const char* value); +#endif + +/* Typedef for prototype of fgets-style reader function. */ +typedef char* (*ini_reader)(char* str, int num, void* stream); + +/* Parse given INI-style file. May have [section]s, name=value pairs + (whitespace stripped), and comments starting with ';' (semicolon). Section + is "" if name=value pair parsed before any section heading. name:value + pairs are also supported as a concession to Python's configparser. + For each name=value pair parsed, call handler function with given user + pointer as well as section, name, and value (data only valid for duration + of handler call). Handler should return nonzero on success, zero on error. + Returns 0 on success, line number of first error on parse error (doesn't + stop on first error), -1 on file open error, or -2 on memory allocation + error (only when INI_USE_STACK is zero). +*/ +int ini_parse(const char* filename, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't + close the file when it's finished -- the caller must do that. */ +int ini_parse_file(FILE* file, ini_handler handler, void* user); + +/* Same as ini_parse(), but takes an ini_reader function pointer instead of + filename. Used for implementing custom or string-based I/O (see also + ini_parse_string). */ +int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, + void* user); + +/* Same as ini_parse(), but takes a zero-terminated string with the INI data +instead of a file. Useful for parsing INI data from a network socket or +already in memory. */ +int ini_parse_string(const char* string, ini_handler handler, void* user); + +/* Nonzero to allow multi-line value parsing, in the style of Python's + configparser. If allowed, ini_parse() will call the handler with the same + name for each subsequent line parsed. */ +#ifndef INI_ALLOW_MULTILINE +#define INI_ALLOW_MULTILINE 1 +#endif + +/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of + the file. See https://github.com/benhoyt/inih/issues/21 */ +#ifndef INI_ALLOW_BOM +#define INI_ALLOW_BOM 1 +#endif + +/* Chars that begin a start-of-line comment. Per Python configparser, allow + both ; and # comments at the start of a line by default. */ +#ifndef INI_START_COMMENT_PREFIXES +#define INI_START_COMMENT_PREFIXES ";#" +#endif + +/* Nonzero to allow inline comments (with valid inline comment characters + specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match + Python 3.2+ configparser behaviour. */ +#ifndef INI_ALLOW_INLINE_COMMENTS +#define INI_ALLOW_INLINE_COMMENTS 1 +#endif +#ifndef INI_INLINE_COMMENT_PREFIXES +#define INI_INLINE_COMMENT_PREFIXES ";" +#endif + +/* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */ +#ifndef INI_USE_STACK +#define INI_USE_STACK 1 +#endif + +/* Maximum line length for any line in INI file (stack or heap). Note that + this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */ +#ifndef INI_MAX_LINE +#define INI_MAX_LINE 200 +#endif + +/* Nonzero to allow heap line buffer to grow via realloc(), zero for a + fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is + zero. */ +#ifndef INI_ALLOW_REALLOC +#define INI_ALLOW_REALLOC 0 +#endif + +/* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK + is zero. */ +#ifndef INI_INITIAL_ALLOC +#define INI_INITIAL_ALLOC 200 +#endif + +/* Stop parsing on first error (default is to keep parsing). */ +#ifndef INI_STOP_ON_FIRST_ERROR +#define INI_STOP_ON_FIRST_ERROR 0 +#endif + +/* Nonzero to call the handler at the start of each new section (with + name and value NULL). Default is to only call the handler on + each name=value pair. */ +#ifndef INI_CALL_HANDLER_ON_NEW_SECTION +#define INI_CALL_HANDLER_ON_NEW_SECTION 0 +#endif + +/* Nonzero to allow a name without a value (no '=' or ':' on the line) and + call the handler with value NULL in this case. Default is to treat + no-value lines as an error. */ +#ifndef INI_ALLOW_NO_VALUE +#define INI_ALLOW_NO_VALUE 0 +#endif + +/* Nonzero to use custom ini_malloc, ini_free, and ini_realloc memory + allocation functions (INI_USE_STACK must also be 0). These functions must + have the same signatures as malloc/free/realloc and behave in a similar + way. ini_realloc is only needed if INI_ALLOW_REALLOC is set. */ +#ifndef INI_CUSTOM_ALLOCATOR +#define INI_CUSTOM_ALLOCATOR 0 +#endif + + +#ifdef __cplusplus +} +#endif + +#endif /* INI_H */ diff --git a/utils/mxc_waveform_asm/main.c b/utils/mxc_waveform_asm/main.c new file mode 100644 index 0000000..b1d5a1e --- /dev/null +++ b/utils/mxc_waveform_asm/main.c @@ -0,0 +1,447 @@ +/******************************************************************************* + * Freescale/NXP i.MX EPDC waveform assembler + * + * This tools converts human readable .csv waveform file into .fw file used + * by i.MX EPDC driver. + * + * Copyright 2021 Wenting Zhang + * + * 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 . + * + * This file is partially derived from Linux kernel driver, with the following + * copyright information: + * Copyright (C) 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2017 NXP + * + * inih is used in this project, which is licensed under BSD-3-Clause. + * csv_parser is used in this project, which is licensed under MIT. + ******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include "ini.h" +#include "csv.h" + +#define MAX_MODES (32) // Maximum waveform modes supported +#define MAX_TEMPS (32) // Maximum temperature ranges supported + +#define MAX_CSV_LINE (1024) + +#define GREYSCALE_BPP (2) +#define GREYSCALE_LEVEL (16) + +typedef struct { + unsigned int wi0; + unsigned int wi1; + unsigned int wi2; + unsigned int wi3; + unsigned int wi4; + unsigned int wi5; + unsigned int wi6; + + unsigned int xwia: 24; // address of extra waveform information + unsigned int cs1: 8; // checksum 1 + + unsigned int wmta: 24; + unsigned int fvsn: 8; + unsigned int luts: 8; + unsigned int mc: 8; // mode count + unsigned int trc: 8; // temperature range count + unsigned int advanced_wfm_flags: 8; + unsigned int eb: 8; + unsigned int sb: 8; + unsigned int reserved0_1: 8; + unsigned int reserved0_2: 8; + unsigned int reserved0_3: 8; + unsigned int reserved0_4: 8; + unsigned int reserved0_5: 8; + unsigned int cs2: 8; // checksum 2 +} waveform_data_header_t; + +typedef struct { + waveform_data_header_t wdh; + uint8_t data[]; /* Temperature Range Table + Waveform Data */ +} waveform_data_file_t; + +typedef struct { + char *prefix; + int modes; + char **mode_names; + int *frame_counts; // frame_counts[mode * temps + temp] + int temps; + int *temp_ranges; + uint8_t ***luts; // luts[mode][temp][frame count * 256 + dst * 16 + src] +} context_t; + +static int ini_parser_handler(void* user, const char* section, const char* name, + const char* value) { + context_t* pcontext = (context_t*)user; + + if (strcmp(section, "WAVEFORM") == 0) { + if (strcmp(name, "VERSION") == 0) { + assert(strcmp(value, "1.0") == 0); + } + else if (strcmp(name, "PREFIX") == 0) { + pcontext->prefix = strdup(value); + } + else if (strcmp(name, "MODES") == 0) { + // Allocate memory for modes + pcontext->modes = atoi(value); + assert(pcontext->modes <= MAX_MODES); + pcontext->mode_names = malloc(sizeof(char*) * pcontext->modes); + assert(pcontext->mode_names); + } + else if (strcmp(name, "TEMPS") == 0) { + // Allocate memory for temp ranges + pcontext->temps = atoi(value); + assert(pcontext->temps <= MAX_TEMPS); + pcontext->temp_ranges = malloc(sizeof(int) * pcontext->temps); + assert(pcontext->temp_ranges); + pcontext->frame_counts = malloc(sizeof(int) * pcontext->modes * + pcontext->temps); + assert(pcontext->frame_counts); + } + else { + size_t len = strlen(name); + if ((len >= 7) && (name[0] == 'T') && + (strncmp(name + (len - 5), "RANGE", 5) == 0)) { + // Temperature Range + char *temp_id_s = strdup(name); + temp_id_s[len - 5] = '\0'; + int temp_id = atoi(temp_id_s + 1); + free(temp_id_s); + pcontext->temp_ranges[temp_id] = atoi(value); + } + else { + fprintf(stderr, "Unknown name %s=%s\n", name, value); + return 0; // Unknown name + } + } + } + else if (strncmp(section, "MODE", 4) == 0) { + int mode_id = atoi(section + 4); + assert(mode_id >= 0); + assert(mode_id < pcontext->modes); + size_t len = strlen(name); + if (strcmp(name, "NAME") == 0) { + // Mode Name + pcontext->mode_names[mode_id] = strdup(value); + } + else if ((len >= 4) && (name[0] == 'T') && + (strncmp(name + (len - 2), "FC", 2) == 0)) { + // Frame Count + char *temp_id_s = strdup(name); + temp_id_s[len - 2] = '\0'; + int temp_id = atoi(temp_id_s + 1); + free(temp_id_s); + pcontext->frame_counts[pcontext->temps * mode_id + temp_id] + = atoi(value); + } + } + else { + fprintf(stderr, "Unknown section %s\n", section); + return 0; // Unknown section + } + return 1; +} + +static void write_uint64_le(uint8_t* dst, uint64_t val) { + dst[7] = (val >> 56) & 0xff; + dst[6] = (val >> 48) & 0xff; + dst[5] = (val >> 40) & 0xff; + dst[4] = (val >> 32) & 0xff; + dst[3] = (val >> 24) & 0xff; + dst[2] = (val >> 16) & 0xff; + dst[1] = (val >> 8) & 0xff; + dst[0] = (val) & 0xff; +} + +static void parse_range(const char* str, int* begin, int* end) { + // Parse range specified in the waveform. + // Example: + // 2 - 2 to 2 + // 0:15 - 0 to 15 + // 4:7 - 4 to 7 + char* delim = strchr(str, ':'); + if (delim) { + *begin = atoi(str); + *end = atoi(delim + 1); + } + else { + *begin = atoi(str); + *end = *begin; + } +} + +static void load_waveform_csv(const char* filename, int frame_count, + uint8_t* lut) { + FILE* fp = fopen(filename, "r"); + assert(fp); + + // Unspecified parts of LUT will be filled with 3 instead of 0 for debugging + memset(lut, 3, frame_count * 256); + + char* line; + int done = 0; + int err = 0; + int rst = 1; // Reset fread_csv_line internal state in the first call + while (!done) { + line = fread_csv_line(fp, MAX_CSV_LINE, &done, &err, rst); + rst = 0; + if (!line) continue; + char** parsed = parse_csv(line); + if (!parsed) continue; + // Parse source/ destination range + int src0, src1, dst0, dst1; + // Skip empty lines + if (!parsed[0]) continue; + if (!parsed[1]) continue; + parse_range(parsed[0], &src0, &src1); + parse_range(parsed[1], &dst0, &dst1); + // Fill in LUT + for (int i = 0; i < frame_count; i++) { + assert(parsed[i]); + uint8_t val = atoi(parsed[i + 2]); + for (int src = src0; src <= src1; src++) { + for (int dst = dst0; dst <= dst1; dst++) { + lut[i * 256 + dst * 16 + src] = val; + } + } + } + free_csv_line(parsed); + free(line); + } + + fclose(fp); +} + +static void dump_lut(int frame_count, uint8_t* lut) { + for (int src = 0; src < 16; src++) { + for (int dst = 0; dst < 16; dst++) { + printf("%x -> %x: ", src, dst); + for (int frame = 0; frame < frame_count; frame++) { + printf("%d ", lut[frame * 256 + dst * 16 + src]); + } + printf("\n"); + } + } +} + +static void copy_lut(uint8_t* dst, uint8_t* src, size_t src_count, int ver) { + if (ver == 1) { + memcpy(dst, src, src_count); + } + else { + for (size_t i = 0; i < src_count / 2; i++) { + uint8_t val; + val = *src++; + val <<= 4; + val |= *src++; + *dst++ = val; + } + } +} + +int main(int argc, char *argv[]) { + context_t context; + + printf("Freescale/NXP i.MX EPDC waveform assembler\n"); + + // Load waveform descriptor + if (argc < 4) { + fprintf(stderr, "Usage: mxc_wvfm_asm version input_file output_file\n"); + fprintf(stderr, "version: EPDC version, possible values: v1, v2\n"); + fprintf(stderr, "input_file: Waveform file, in .iwf format\n"); + fprintf(stderr, "output_file: MXC EPDC firmware file, in .fw format\n"); + fprintf(stderr, "Example: mxc_wvfm_asm v1 e060scm_desc.iwf epdc_E060SCM.fw\n"); + return 1; + } + + char* ver_string = argv[1]; + char* input_fn = argv[2]; + char* output_fn = argv[3]; + int ver; + if (strcmp(ver_string, "v1") == 0) { + ver = 1; + } + else if (strcmp(ver_string, "v2") == 0) { + ver = 2; + } + else { + fprintf(stderr, "Invalid EPDC version %s\n", ver_string); + fprintf(stderr, "Possible values: v1, v2.\n"); + fprintf(stderr, "i.MX6DL/SL uses EPDCv1, i.MX7D uses EPDCv2. " + "i.MX5 EPDC is not supported.\n"); + return 1; + } + + if (ini_parse(input_fn, ini_parser_handler, &context) < 0) { + fprintf(stderr, "Failed to load waveform descriptor.\n"); + return 1; + } + + // Set default name if not provided + char* default_name = "Unknown"; + for (int i = 0; i < context.modes; i++) { + if (!context.mode_names[i]) + context.mode_names[i] = strdup(default_name); + } + + // Print loaded info + printf("Prefix: %s\n", context.prefix); + + for (int i = 0; i < context.modes; i++) { + printf("Mode %d: %s\n", i, context.mode_names[i]); + for (int j = 0; j < context.temps; j++) { + printf("\tTemp %d: %d frames\n", j, + context.frame_counts[i * context.temps + j]); + } + } + + for (int i = 0; i < context.temps; i++) { + printf("Temp %d: %d degC\n", i, context.temp_ranges[i]); + } + + assert(context.modes < 100); + assert(context.temps < 100); + + // Load actual waveform + char *dir = dirname(input_fn); // Return val of dirname shall not be free()d + size_t dirlen = strlen(dir); + context.luts = malloc(context.modes * sizeof(uint8_t**)); + assert(context.luts); + for (int i = 0; i < context.modes; i++) { + context.luts[i] = malloc(context.temps * sizeof(uint8_t*)); + assert(context.luts[i]); + for (int j = 0; j < context.temps; j++) { + int frame_count = context.frame_counts[i * context.temps + j]; + context.luts[i][j] = malloc(frame_count * 256); // LUT always in 8b + assert(context.luts[i][j]); + char* fn = malloc(dirlen + strlen(context.prefix) + 14); + assert(fn); + sprintf(fn, "%s/%s_M%d_T%d.csv", dir, context.prefix, i, j); + printf("Loading %s...\n", fn); + load_waveform_csv(fn, frame_count, context.luts[i][j]); + free(fn); + } + } + + // Calculate file size and offset + uint64_t header_size = sizeof(waveform_data_header_t); + uint64_t temp_table_size = sizeof(uint8_t) * context.temps; + uint64_t mode_offset_table_size = sizeof(uint64_t) * context.modes; + uint64_t temp_offset_table_size = sizeof(uint64_t) * context.temps; + + // 1st level mode offset table + uint64_t* mode_offset_table = malloc(mode_offset_table_size); + // global offset table + uint64_t* data_offset_table = + malloc(sizeof(uint64_t) * context.modes * context.temps); + uint64_t total_size = 0; + uint64_t data_region_offset = temp_table_size + 1; + total_size += mode_offset_table_size; + uint64_t lut_size = (ver == 1) ? 256 : 128; + + for (int i = 0; i < context.modes; i++) { + // Set the offset of the current mode + mode_offset_table[i] = total_size; + total_size += temp_offset_table_size; + for (int j = 0; j < context.temps; j++) { + uint64_t data_size = context.frame_counts[i * context.temps + j] + * lut_size + sizeof(uint64_t); + data_offset_table[i * context.temps + j] = total_size; + printf("Mode %d Temp %d data offset %08"PRIx64" (%"PRId64")\n", + i, j, total_size, total_size); + total_size += data_size; + } + } + total_size += header_size + temp_table_size + 1; + printf("Total file size %"PRId64"\n", total_size); + + // Allocate memory for waveform buffer + waveform_data_file_t* pwvfm_file = malloc(total_size); + assert(pwvfm_file); + + // Fill waveform header + memset(&pwvfm_file->wdh, 0, sizeof(waveform_data_header_t)); + pwvfm_file->wdh.trc = context.temps - 1; + pwvfm_file->wdh.mc = context.modes - 1; + // Other fields (including checksums) are generally directly imported from + // wbf file. They are not used in MXC EPDC driver. No need to fill them. + + // Fill temperature table + for (int i = 0; i < context.temps; i++) { + pwvfm_file->data[i] = context.temp_ranges[i]; + } + + // Set 1 byte padding + pwvfm_file->data[context.temps] = 0; + + // Fill waveform offset table and temp offset table + uint8_t* wvfm_data_region = &pwvfm_file->data[data_region_offset]; + for (int i = 0; i < context.modes; i++) { + write_uint64_le(&wvfm_data_region[i * 8], + mode_offset_table[i]); + for (int j = 0; j < context.temps; j++) { + write_uint64_le(&wvfm_data_region[mode_offset_table[i] + j * 8], + data_offset_table[i * context.temps + j]); + } + } + + // Fill waveform data + for (int i = 0; i < context.modes; i++) { + for (int j = 0; j < context.temps; j++) { + size_t index = i * context.temps + j; + uint8_t* wvfm_wr_ptr = &wvfm_data_region[data_offset_table[index]]; + int frame_count = context.frame_counts[index]; + write_uint64_le(wvfm_wr_ptr, frame_count); + wvfm_wr_ptr += 8; + copy_lut(wvfm_wr_ptr, context.luts[i][j], frame_count * 256, ver); + } + } + + // Write waveform file + FILE *outFile = fopen(output_fn, "wb"); + assert(outFile); + + size_t written = fwrite((uint8_t *)pwvfm_file, total_size, 1, outFile); + assert(written == 1); + + fclose(outFile); + + printf("Finished.\n"); + + // Free buffers + free(pwvfm_file); + free(context.prefix); + for (int i = 0; i < context.modes; i++) { + free(context.mode_names[i]); + for (int j = 0; j < context.temps; j++) { + free(context.luts[i][j]); + } + free(context.luts[i]); + } + free(context.mode_names); + free(context.luts); + free(context.frame_counts); + free(context.temp_ranges); + free(mode_offset_table); + free(data_offset_table); + + return 0; +} diff --git a/utils/mxc_waveform_asm/split.c b/utils/mxc_waveform_asm/split.c new file mode 100644 index 0000000..d733984 --- /dev/null +++ b/utils/mxc_waveform_asm/split.c @@ -0,0 +1,85 @@ +#include +#include + +/* + * Given a string which might contain unescaped newlines, split it up into + * lines which do not contain unescaped newlines, returned as a + * NULL-terminated array of malloc'd strings. + */ +char **split_on_unescaped_newlines(const char *txt) { + const char *ptr, *lineStart; + char **buf, **bptr; + int fQuote, nLines; + + /* First pass: count how many lines we will need */ + for ( nLines = 1, ptr = txt, fQuote = 0; *ptr; ptr++ ) { + if ( fQuote ) { + if ( *ptr == '\"' ) { + if ( ptr[1] == '\"' ) { + ptr++; + continue; + } + fQuote = 0; + } + } else if ( *ptr == '\"' ) { + fQuote = 1; + } else if ( *ptr == '\n' ) { + nLines++; + } + } + + buf = malloc( sizeof(char*) * (nLines+1) ); + + if ( !buf ) { + return NULL; + } + + /* Second pass: populate results */ + lineStart = txt; + for ( bptr = buf, ptr = txt, fQuote = 0; ; ptr++ ) { + if ( fQuote ) { + if ( *ptr == '\"' ) { + if ( ptr[1] == '\"' ) { + ptr++; + continue; + } + fQuote = 0; + continue; + } else if ( *ptr ) { + continue; + } + } + + if ( *ptr == '\"' ) { + fQuote = 1; + } else if ( *ptr == '\n' || !*ptr ) { + size_t len = ptr - lineStart; + + if ( len == 0 ) { + *bptr = NULL; + return buf; + } + + *bptr = malloc( len + 1 ); + + if ( !*bptr ) { + for ( bptr--; bptr >= buf; bptr-- ) { + free( *bptr ); + } + free( buf ); + return NULL; + } + + memcpy( *bptr, lineStart, len ); + (*bptr)[len] = '\0'; + + if ( *ptr ) { + lineStart = ptr + 1; + bptr++; + } else { + bptr[1] = NULL; + return buf; + } + } + } +} diff --git a/utils/mxc_waveform_dump/Makefile b/utils/mxc_waveform_dump/Makefile new file mode 100644 index 0000000..a0f57fb --- /dev/null +++ b/utils/mxc_waveform_dump/Makefile @@ -0,0 +1,6 @@ +all: mxc_wvfm_dump + +mxc_wvfm_dump: main.c + gcc -O1 -g main.c -o mxc_wvfm_dump +clean: + rm -f mxc_wvfm_dump \ No newline at end of file diff --git a/utils/mxc_waveform_dump/main.c b/utils/mxc_waveform_dump/main.c new file mode 100644 index 0000000..e76b468 --- /dev/null +++ b/utils/mxc_waveform_dump/main.c @@ -0,0 +1,309 @@ +/******************************************************************************* + * Freescale/NXP EPDC waveform firmware dumper + * Based on https://github.com/julbouln/ice40_eink_controller + * + * 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 . + * + * This file is partially derived from Linux kernel driver, with the following + * copyright information: + * Copyright (C) 2014-2016 Freescale Semiconductor, Inc. + * Copyright 2017 NXP + ******************************************************************************/ + +#include +#include +#include +#include +#include +#include + +struct waveform_data_header { + unsigned int wi0; + unsigned int wi1; + unsigned int wi2; + unsigned int wi3; + unsigned int wi4; + unsigned int wi5; + unsigned int wi6; + + unsigned int xwia: 24; + unsigned int cs1: 8; + + unsigned int wmta: 24; + unsigned int fvsn: 8; + unsigned int luts: 8; + unsigned int mc: 8; + unsigned int trc: 8; + unsigned int advanced_wfm_flags: 8; + unsigned int eb: 8; + unsigned int sb: 8; + unsigned int reserved0_1: 8; + unsigned int reserved0_2: 8; + unsigned int reserved0_3: 8; + unsigned int reserved0_4: 8; + unsigned int reserved0_5: 8; + unsigned int cs2: 8; +}; + +struct mxcfb_waveform_data_file { + struct waveform_data_header wdh; + uint32_t *data; /* Temperature Range Table + Waveform Data */ +}; + +static uint64_t read_uint64_le(uint8_t* src) { + return ((uint64_t)src[7] << 56) | + ((uint64_t)src[6] << 48) | + ((uint64_t)src[5] << 40) | + ((uint64_t)src[4] << 32) | + ((uint64_t)src[3] << 24) | + ((uint64_t)src[2] << 16) | + ((uint64_t)src[1] << 8) | + (uint64_t)src[0]; +} + +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 dump_phases(FILE* fp, uint8_t* buffer, int phases, int ver) { + int i, j, k, l, x, y; + + uint8_t luts[phases][16][16]; + + k = 0; + for (i = 0; i < phases * 256; i += 256) { + j = 0; + for (x = 0; x < 16; x++) { + for (y = 0; y < 16; y++) { + uint8_t val; + if (ver == 2) + val = read_uint4(buffer + 8, i + j); + else + val = buffer[8 + i + j]; + luts[k][y][x] = val; + j++; + } + } + k++; + } + + for (i = 0; i < 16; i++) { + for (j = 0; j < 16; 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, "MXC EPDC waveform dumper\n"); + + if (argc < 4) { + fprintf(stderr, "Usage: mxc_wvfm_dump version input_file output_prefix\n"); + fprintf(stderr, "version: EPDC version, possible values: v1, v2\n"); + fprintf(stderr, "input_file: MXC EPDC firmware file, in .fw format\n"); + fprintf(stderr, "output_prefix: Prefix for output file name, without extension\n"); + fprintf(stderr, "Example: mxc_wvfm_dump v1 epdc_E060SCM.fw e060scm\n"); + return 1; + } + + char *ver_string = argv[1]; + char *fw = argv[2]; + char *prefix = argv[3]; + int ver; + + if (strcmp(ver_string, "v1") == 0) { + ver = 1; + } + else if (strcmp(ver_string, "v2") == 0) { + ver = 2; + } + else { + fprintf(stderr, "Invalid EPDC version %s\n", ver_string); + fprintf(stderr, "Possible values: v1, v2.\n"); + fprintf(stderr, "i.MX6DL/SL uses EPDCv1, i.MX7D uses EPDCv2. i.MX5 EPDC is not supported.\n"); + return 1; + } + + 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 mxcfb_waveform_data_file *wv_file; + wv_file = (struct mxcfb_waveform_data_file *)file_buffer; + + printf("wi0: %08x\n", wv_file->wdh.wi0); + printf("wi1: %08x\n", wv_file->wdh.wi1); + printf("wi2: %08x\n", wv_file->wdh.wi2); + printf("wi3: %08x\n", wv_file->wdh.wi3); + printf("wi4: %08x\n", wv_file->wdh.wi4); + printf("wi5: %08x\n", wv_file->wdh.wi5); + printf("wi6: %08x\n", wv_file->wdh.wi6); + + printf("xwia: %d\n", wv_file->wdh.xwia); + printf("cs1: %d\n", wv_file->wdh.cs1); + + printf("wmta: %d\n", wv_file->wdh.wmta); + printf("fvsn: %d\n", wv_file->wdh.fvsn); + printf("luts: %d\n", wv_file->wdh.luts); + printf("mc: %d\n", wv_file->wdh.mc); + printf("trc: %d\n", wv_file->wdh.trc); + printf("advanced_wfm_flags: %d\n", wv_file->wdh.advanced_wfm_flags); + printf("eb: %d\n", wv_file->wdh.eb); + printf("sb: %d\n", wv_file->wdh.sb); + printf("reserved0_1: %d\n", wv_file->wdh.reserved0_1); + printf("reserved0_2: %d\n", wv_file->wdh.reserved0_2); + printf("reserved0_3: %d\n", wv_file->wdh.reserved0_3); + printf("reserved0_4: %d\n", wv_file->wdh.reserved0_4); + printf("reserved0_5: %d\n", wv_file->wdh.reserved0_5); + printf("cs2: %d\n", wv_file->wdh.cs2); + + int i, j; + int trt_entries; // temperature range table + int wv_data_offs; // offset for waveform data + int waveform_buffer_size; // size for waveform data + int mode_count = wv_file->wdh.mc + 1; + + uint8_t *temp_range_bounds; + trt_entries = wv_file->wdh.trc + 1; + + printf("Temperatures count: %d\n", trt_entries); + + temp_range_bounds = (uint8_t*)malloc(trt_entries); + + memcpy(temp_range_bounds, &wv_file->data, trt_entries); + + for (i = 0; i < trt_entries; i++) { + printf("Temperature %d = %d°C\n", i, temp_range_bounds[i]); + } + + wv_data_offs = sizeof(wv_file->wdh) + trt_entries + 1; + waveform_buffer_size = file_size - wv_data_offs; + + printf("Waveform data offset: %d, size: %d\n", wv_data_offs, waveform_buffer_size); + + if ((wv_file->wdh.luts & 0xC) == 0x4) { + printf("waveform 5bit\n"); + } else { + printf("waveform 4bit\n"); + } + + uint8_t *waveform_buffer; + waveform_buffer = (uint8_t *)malloc(waveform_buffer_size); + memcpy(waveform_buffer, (uint8_t *)(file_buffer) + wv_data_offs, + waveform_buffer_size); + + uint64_t* wv_modes = malloc(sizeof(uint64_t) * mode_count); + + uint64_t addr; + // get modes addr + for (i = 0; i < mode_count; i++) { + addr = read_uint64_le(&waveform_buffer[i * 8]); + printf("wave #%d addr: %08"PRIx64"\n", i, addr); + wv_modes[i] = addr; + } + + // get modes temp addr + uint64_t* wv_modes_temps = malloc(sizeof(uint64_t) * mode_count * trt_entries); + uint64_t* frame_counts = malloc(sizeof(uint64_t) * mode_count * trt_entries); + uint64_t last_addr = wv_modes[0]; + for (i = 0; i < mode_count; i++) { + uint64_t m = wv_modes[i]; + for (j = 0; j < trt_entries; j++) { + addr = read_uint64_le(&waveform_buffer[m + j * 8]); + wv_modes_temps[i * trt_entries + j] = addr; + uint64_t frame_count = read_uint64_le(&waveform_buffer[addr]); + frame_counts[i * trt_entries + j] = frame_count; + printf("wave #%d, temp #%d addr: %08"PRIx64", %"PRId64" phases (Addr diff = %"PRId64", Size = %"PRId64")\n", i, j, + addr, frame_count, addr - last_addr, frame_count * 256); + last_addr = addr; + } + } + + char* fn = malloc(strlen(prefix) + 14); + sprintf(fn, "%s_desc.iwf", prefix); + fp = fopen(fn, "w"); + assert(fp); + fprintf(fp, "[WAVEFORM]\n"); + fprintf(fp, "VERSION = 1.0\n"); + fprintf(fp, "PREFIX = %s\n", prefix); + fprintf(fp, "MODES = %d\n", mode_count); + fprintf(fp, "TEMPS = %d\n", trt_entries); + fprintf(fp, "\n"); + for (int i = 0; i < trt_entries; i++) { + fprintf(fp, "T%dRANGE = %d\n", i, temp_range_bounds[i]); + } + fprintf(fp, "\n"); + for (int i = 0; i < mode_count; i++) { + fprintf(fp, "[MODE%d]\n", i); + for (int j = 0; j < trt_entries; j++) { + fprintf(fp, "T%dFC = %"PRId64"\n", j, frame_counts[i * trt_entries + j]); + } + fprintf(fp, "\n"); + } + fclose(fp); + + for (int i = 0; i < mode_count; i++) { + for (int j = 0; j < trt_entries; j++) { + sprintf(fn, "%s_M%d_T%d.csv", prefix, i, j); + fp = fopen(fn, "w"); + assert(fp); + size_t index = i * trt_entries + j; + uint64_t frame_count = frame_counts[index]; + uint8_t* lut = &waveform_buffer[wv_modes_temps[index]]; + dump_phases(fp, lut, frame_count, ver); + fclose(fp); + } + } + + free(fn); + free(temp_range_bounds); + free(waveform_buffer); + free(frame_counts); + free(wv_modes); + free(wv_modes_temps); + + free(file_buffer); + + return 0; +} \ No newline at end of file diff --git a/utils/wbf_flash_decompress/Makefile b/utils/wbf_flash_decompress/Makefile new file mode 100644 index 0000000..b7fbd55 --- /dev/null +++ b/utils/wbf_flash_decompress/Makefile @@ -0,0 +1,6 @@ +all: wbf_flash_decompress + +wbf_flash_decompress: main.c + gcc -O1 -g main.c -o wbf_flash_decompress +clean: + rm -f wbf_flash_decompress \ No newline at end of file diff --git a/utils/wbf_flash_decompress/main.c b/utils/wbf_flash_decompress/main.c new file mode 100644 index 0000000..7e7d834 --- /dev/null +++ b/utils/wbf_flash_decompress/main.c @@ -0,0 +1,143 @@ +// Eink waveform flash decompressor +// Copyright 2024 Wenting Zhang +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAX_DECOMP_SIZE (0x100000) + +static uint32_t read_uint16_be(uint8_t *ptr) { + uint32_t b0 = (uint32_t)(*ptr++) << 8; + uint32_t b1 = (uint32_t)(*ptr++); + return b0 | b1; +} + +static uint32_t read_uint16_le(uint8_t *ptr) { + uint32_t b0 = (uint32_t)(*ptr++); + uint32_t b1 = (uint32_t)(*ptr++) << 8; + return b0 | b1; +} + +static uint32_t read_uint32_be(uint8_t *ptr) { + uint32_t b0 = (uint32_t)(*ptr++) << 24; + uint32_t b1 = (uint32_t)(*ptr++) << 16; + uint32_t b2 = (uint32_t)(*ptr++) << 8; + uint32_t b3 = (uint32_t)(*ptr++); + return b0 | b1 | b2 | b3; +} + +int main(int argc, char **argv) { + fprintf(stderr, "Eink waveform flash decompressor\n"); + + if (argc != 3) { + fprintf(stderr, "Usage: wbf_flash_decompress input_file output_file\n"); + fprintf(stderr, "input_file: Compressed flash ROM (like a flash dump)\n"); + fprintf(stderr, "output_file: WBF file name\n"); + return 1; + } + + char *bin = argv[1]; + char *wbf = argv[2]; + + FILE * fp; + size_t file_size; + char * file_buffer; + size_t result; + + fp = fopen(bin, "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); + + printf("File size: %zu bytes\n", file_size); + + uint8_t *ptr = (uint8_t *)file_buffer; + uint32_t compressed_len = read_uint32_be(ptr); + ptr += 4; + uint32_t header_version = read_uint32_be(ptr); + + printf("Compressed length: %d bytes\n", compressed_len); + + if ((compressed_len + 16) > file_size) { + printf("Error: file size smaller than expected.\n"); + return -1; + } + else if ((compressed_len + 16) < file_size) { + printf("Warning: file size larger than expected.\n"); + } + + printf("Header version: %d\n", header_version); + + if (header_version != 1) { + printf("Unsupported file version\n"); + return -1; + } + + // Start decompress + ptr = (uint8_t *)file_buffer + 16; // skip header + static uint8_t decomp_buffer[MAX_DECOMP_SIZE]; + uint32_t wrptr = 0; + uint8_t *ptr_end = ptr + compressed_len; + while (ptr < ptr_end) { + uint32_t wrptr_snapshot = wrptr; + uint32_t offset = (uint32_t)read_uint16_le(ptr); + ptr += 2; + uint8_t len = *ptr++; + uint8_t byte = *ptr++; + printf("Ptr %d, Offset %d, Len %d, Byte 0x%02x\n", wrptr_snapshot, offset, len, byte); + if (len != 0) { + printf("Src: %d\n", wrptr_snapshot - offset); + } + for (int i = 0; i < len; i++) { + // len could be zero, in this case the loop doesn't execute + uint8_t rd = decomp_buffer[wrptr_snapshot - offset + i]; + decomp_buffer[wrptr++] = rd; + } + decomp_buffer[wrptr++] = byte; + } + + printf("Decompressed size: %d bytes\n", wrptr); + + fp = fopen(wbf, "wb"); + fwrite(decomp_buffer, wrptr, 1, fp); + fclose(fp); + printf("Done\n"); + + return 0; +} \ No newline at end of file diff --git a/utils/wbf_waveform_dump/Makefile b/utils/wbf_waveform_dump/Makefile new file mode 100644 index 0000000..4d2166b --- /dev/null +++ b/utils/wbf_waveform_dump/Makefile @@ -0,0 +1,6 @@ +all: wbf_wvfm_dump + +wbf_wvfm_dump: main.c + gcc -O1 -g main.c -o wbf_wvfm_dump +clean: + rm -f wbf_wvfm_dump \ No newline at end of file diff --git a/utils/wbf_waveform_dump/main.c b/utils/wbf_waveform_dump/main.c new file mode 100644 index 0000000..4a7bc22 --- /dev/null +++ b/utils/wbf_waveform_dump/main.c @@ -0,0 +1,568 @@ +/******************************************************************************* + * 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 . + * + * 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 + * Copyright 2024 Wenting Zhang + ******************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +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; +} \ No newline at end of file