diff --git a/Makefile b/Makefile index 4ebde1efb..88130c812 100644 --- a/Makefile +++ b/Makefile @@ -1357,6 +1357,12 @@ install: $(PROGRAM)$(EXEC_SUFFIX) $(PROGRAM).8 $(INSTALL) -m 0755 $(PROGRAM)$(EXEC_SUFFIX) $(DESTDIR)$(PREFIX)/sbin $(INSTALL) -m 0644 $(PROGRAM).8 $(DESTDIR)$(MANDIR)/man8 +libinstall: libflashrom.a libflashrom.h + mkdir -p $(DESTDIR)$(PREFIX)/lib + $(INSTALL) -m 0644 libflashrom.a $(DESTDIR)$(PREFIX)/lib + mkdir -p $(DESTDIR)$(PREFIX)/include + $(INSTALL) -m 0644 libflashrom.h $(DESTDIR)$(PREFIX)/include + export: $(PROGRAM).8 @rm -rf $(EXPORTDIR)/flashrom-$(RELEASENAME) @svn export -r BASE . $(EXPORTDIR)/flashrom-$(RELEASENAME) diff --git a/flash.h b/flash.h index b383edaf3..1da7e4121 100644 --- a/flash.h +++ b/flash.h @@ -138,7 +138,8 @@ enum test_state { #define TEST_BAD_PRE (struct tested){ .probe = BAD, .read = BAD, .erase = BAD, .write = NT } #define TEST_BAD_PREW (struct tested){ .probe = BAD, .read = BAD, .erase = BAD, .write = BAD } -struct flashctx; +struct flashrom_flashctx; +#define flashctx flashrom_flashctx /* TODO: Agree on a name and convert all occurences. */ typedef int (erasefunc_t)(struct flashctx *flash, unsigned int addr, unsigned int blocklen); struct flashchip { @@ -204,7 +205,7 @@ struct flashchip { enum write_granularity gran; }; -struct flashctx { +struct flashrom_flashctx { struct flashchip *chip; /* FIXME: The memory mappings should be saved in a more structured way. */ /* The physical_* fields store the respective addresses in the physical address space of the CPU. */ @@ -218,6 +219,12 @@ struct flashctx { struct registered_master *mst; const struct flashrom_layout *layout; struct single_layout fallback_layout; + struct { + bool force; + bool force_boardmismatch; + bool verify_after_write; + bool verify_whole_chip; + } flags; }; /* Timing used in probe routines. ZERO is -2 to differentiate between an unset diff --git a/flashrom.c b/flashrom.c index 273eb9971..3be3d3214 100644 --- a/flashrom.c +++ b/flashrom.c @@ -409,6 +409,7 @@ const struct programmer_entry programmer_table[] = { #define SHUTDOWN_MAXFN 32 static int shutdown_fn_count = 0; +/** @private */ struct shutdown_func_data { int (*func) (void *data); void *data; @@ -1835,7 +1836,7 @@ static int erase_block(struct flashctx *const flashctx, * @return 0 on success, * 1 if all available erase functions failed. */ -int erase_by_layout(struct flashctx *const flashctx) +static int erase_by_layout(struct flashctx *const flashctx) { struct walk_info info = { 0 }; return walk_by_layout(flashctx, &info, &erase_block); @@ -1946,8 +1947,8 @@ _free_ret: * @return 0 on success, * 1 if anything has gone wrong. */ -int write_by_layout(struct flashctx *const flashctx, - void *const curcontents, const void *const newcontents) +static int write_by_layout(struct flashctx *const flashctx, + void *const curcontents, const void *const newcontents) { struct walk_info info; info.curcontents = curcontents; @@ -1968,8 +1969,8 @@ int write_by_layout(struct flashctx *const flashctx, * 1 if reading failed, * 3 if the contents don't match. */ -int verify_by_layout(struct flashctx *const flashctx, - void *const curcontents, const uint8_t *const newcontents) +static int verify_by_layout(struct flashctx *const flashctx, + void *const curcontents, const uint8_t *const newcontents) { const struct flashrom_layout *const layout = get_layout(flashctx); @@ -2484,3 +2485,301 @@ out: free(newcontents); return ret; } + +/** @private */ +static int prepare_flash_access(struct flashctx *const flash, + const bool read_it, const bool write_it, + const bool erase_it, const bool verify_it) +{ + if (chip_safety_check(flash, flash->flags.force, read_it, write_it, erase_it, verify_it)) { + msg_cerr("Aborting.\n"); + return 1; + } + + if (flash->layout == get_global_layout() && normalize_romentries(flash)) { + msg_cerr("Requested regions can not be handled. Aborting.\n"); + return 1; + } + + if (map_flash(flash) != 0) + return 1; + + /* Given the existence of read locks, we want to unlock for read, + erase and write. */ + if (flash->chip->unlock) + flash->chip->unlock(flash); + + return 0; +} + +/** @private */ +static void finalize_flash_access(struct flashctx *const flash) +{ + unmap_flash(flash); +} + +/** + * @addtogroup flashrom-flash + * @{ + */ + +/** + * @brief Erase the specified ROM chip. + * + * If a layout is set in the given flash context, only included regions + * will be erased. + * + * @param flashctx The context of the flash chip to erase. + * @return 0 on success. + */ +int flashrom_flash_erase(struct flashctx *const flashctx) +{ + if (prepare_flash_access(flashctx, false, false, true, false)) + return 1; + + const int ret = erase_by_layout(flashctx); + + finalize_flash_access(flashctx); + + return ret; +} + +/** @} */ /* end flashrom-flash */ + +/** + * @defgroup flashrom-ops Operations + * @{ + */ + +/** + * @brief Read the current image from the specified ROM chip. + * + * If a layout is set in the specified flash context, only included regions + * will be read. + * + * @param flashctx The context of the flash chip. + * @param buffer Target buffer to write image to. + * @param buffer_len Size of target buffer in bytes. + * @return 0 on success, + * 2 if buffer_len is too short for the flash chip's contents, + * or 1 on any other failure. + */ +int flashrom_image_read(struct flashctx *const flashctx, void *const buffer, const size_t buffer_len) +{ + const size_t flash_size = flashctx->chip->total_size * 1024; + + if (flash_size > buffer_len) + return 2; + + if (prepare_flash_access(flashctx, true, false, false, false)) + return 1; + + msg_cinfo("Reading flash... "); + + int ret = 1; + if (read_by_layout(flashctx, buffer)) { + msg_cerr("Read operation failed!\n"); + msg_cinfo("FAILED.\n"); + goto _finalize_ret; + } + msg_cinfo("done.\n"); + ret = 0; + +_finalize_ret: + finalize_flash_access(flashctx); + return ret; +} + +static void combine_image_by_layout(const struct flashctx *const flashctx, + uint8_t *const newcontents, const uint8_t *const oldcontents) +{ + const struct flashrom_layout *const layout = get_layout(flashctx); + + size_t i; + for (i = 0; i < layout->num_entries; ++i) { + if (layout->entries[i].included) + continue; + + const chipoff_t region_start = layout->entries[i].start; + const chipsize_t region_len = layout->entries[i].end - layout->entries[i].start + 1; + + memcpy(newcontents + region_start, oldcontents + region_start, region_len); + } +} + +/** + * @brief Write the specified image to the ROM chip. + * + * If a layout is set in the specified flash context, only erase blocks + * containing included regions will be touched. + * + * @param flashctx The context of the flash chip. + * @param buffer Source buffer to read image from. + * @param buffer_len Size of source buffer in bytes. + * @return 0 on success, + * 4 if buffer_len doesn't match the size of the flash chip, + * 3 if write was tried but nothing has changed, + * 2 if write failed and flash contents changed, + * or 1 on any other failure. + */ +int flashrom_image_write(struct flashctx *const flashctx, void *const buffer, const size_t buffer_len) +{ + const size_t flash_size = flashctx->chip->total_size * 1024; + const bool verify_all = flashctx->flags.verify_whole_chip; + const bool verify = flashctx->flags.verify_after_write; + + if (buffer_len != flash_size) + return 4; + + int ret = 1; + + uint8_t *const newcontents = buffer; + uint8_t *const curcontents = malloc(flash_size); + uint8_t *oldcontents = NULL; + if (verify_all) + oldcontents = malloc(flash_size); + if (!curcontents || (verify_all && !oldcontents)) { + msg_gerr("Out of memory!\n"); + goto _free_ret; + } + +#if CONFIG_INTERNAL == 1 + if (programmer == PROGRAMMER_INTERNAL && cb_check_image(newcontents, flash_size) < 0) { + if (flashctx->flags.force_boardmismatch) { + msg_pinfo("Proceeding anyway because user forced us to.\n"); + } else { + msg_perr("Aborting. You can override this with " + "-p internal:boardmismatch=force.\n"); + goto _free_ret; + } + } +#endif + + if (prepare_flash_access(flashctx, false, true, false, verify)) + goto _free_ret; + + /* + * Read the whole chip to be able to check whether regions need to be + * erased and to give better diagnostics in case write fails. + * The alternative is to read only the regions which are to be + * preserved, but in that case we might perform unneeded erase which + * takes time as well. + */ + msg_cinfo("Reading old flash chip contents... "); + if (verify_all) { + if (flashctx->chip->read(flashctx, oldcontents, 0, flash_size)) { + msg_cinfo("FAILED.\n"); + goto _finalize_ret; + } + memcpy(curcontents, oldcontents, flash_size); + } else { + if (read_by_layout(flashctx, curcontents)) { + msg_cinfo("FAILED.\n"); + goto _finalize_ret; + } + } + msg_cinfo("done.\n"); + + if (write_by_layout(flashctx, curcontents, newcontents)) { + msg_cerr("Uh oh. Erase/write failed. "); + ret = 2; + if (verify_all) { + msg_cerr("Checking if anything has changed.\n"); + msg_cinfo("Reading current flash chip contents... "); + if (!flashctx->chip->read(flashctx, curcontents, 0, flash_size)) { + msg_cinfo("done.\n"); + if (!memcmp(oldcontents, curcontents, flash_size)) { + nonfatal_help_message(); + goto _finalize_ret; + } + msg_cerr("Apparently at least some data has changed.\n"); + } else + msg_cerr("Can't even read anymore!\n"); + emergency_help_message(); + goto _finalize_ret; + } else { + msg_cerr("\n"); + } + emergency_help_message(); + goto _finalize_ret; + } + + /* Verify only if we actually changed something. */ + if (verify && !all_skipped) { + const struct flashrom_layout *const layout_bak = flashctx->layout; + + msg_cinfo("Verifying flash... "); + + /* Work around chips which need some time to calm down. */ + programmer_delay(1000*1000); + + if (verify_all) { + combine_image_by_layout(flashctx, newcontents, oldcontents); + flashctx->layout = NULL; + } + ret = verify_by_layout(flashctx, curcontents, newcontents); + flashctx->layout = layout_bak; + /* If we tried to write, and verification now fails, we + might have an emergency situation. */ + if (ret) + emergency_help_message(); + else + msg_cinfo("VERIFIED.\n"); + } else { + /* We didn't change anything. */ + ret = 0; + } + +_finalize_ret: + finalize_flash_access(flashctx); +_free_ret: + free(oldcontents); + free(curcontents); + return ret; +} + +/** + * @brief Verify the ROM chip's contents with the specified image. + * + * If a layout is set in the specified flash context, only included regions + * will be verified. + * + * @param flashctx The context of the flash chip. + * @param buffer Source buffer to verify with. + * @param buffer_len Size of source buffer in bytes. + * @return 0 on success, + * 3 if the chip's contents don't match, + * 2 if buffer_len doesn't match the size of the flash chip, + * or 1 on any other failure. + */ +int flashrom_image_verify(struct flashctx *const flashctx, const void *const buffer, const size_t buffer_len) +{ + const size_t flash_size = flashctx->chip->total_size * 1024; + + if (buffer_len != flash_size) + return 2; + + const uint8_t *const newcontents = buffer; + uint8_t *const curcontents = malloc(flash_size); + if (!curcontents) { + msg_gerr("Out of memory!\n"); + return 1; + } + + int ret = 1; + + if (prepare_flash_access(flashctx, false, false, false, true)) + goto _free_ret; + + msg_cinfo("Verifying flash... "); + ret = verify_by_layout(flashctx, curcontents, newcontents); + if (!ret) + msg_cinfo("VERIFIED.\n"); + + finalize_flash_access(flashctx); +_free_ret: + free(curcontents); + return ret; +} + +/** @} */ /* end flashrom-ops */ diff --git a/libflashrom.c b/libflashrom.c new file mode 100644 index 000000000..5447bae9e --- /dev/null +++ b/libflashrom.c @@ -0,0 +1,334 @@ +/* + * This file is part of the flashrom project. + * + * Copyright (C) 2012, 2016 secunet Security Networks AG + * (Written by Nico Huber for secunet) + * + * This program 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 program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ +/** + * @mainpage + * + * Have a look at the Modules section for a function reference. + */ + +#include +#include +#include + +#include "flash.h" +#include "programmer.h" +#include "layout.h" +#include "libflashrom.h" + +/** + * @defgroup flashrom-general General + * @{ + */ + +/** Pointer to log callback function. */ +static flashrom_log_callback *global_log_callback = NULL; + +/** + * @brief Initialize libflashrom. + * + * @param perform_selfcheck If not zero, perform a self check. + * @return 0 on success + */ +int flashrom_init(const int perform_selfcheck) +{ + if (perform_selfcheck && selfcheck()) + return 1; + myusec_calibrate_delay(); + return 0; +} + +/** + * @brief Shut down libflashrom. + * @return 0 on success + */ +int flashrom_shutdown(void) +{ + return 0; /* TODO: nothing to do? */ +} + +/* TODO: flashrom_set_loglevel()? do we need it? + For now, let the user decide in his callback. */ + +/** + * @brief Set the log callback function. + * + * Set a callback function which will be invoked whenever libflashrom wants + * to output messages. This allows frontends to do whatever they see fit with + * such messages, e.g. write them to syslog, or to file, or print them in a + * GUI window, etc. + * + * @param log_callback Pointer to the new log callback function. + */ +void flashrom_set_log_callback(flashrom_log_callback *const log_callback) +{ + global_log_callback = log_callback; +} +/** @private */ +int print(const enum msglevel level, const char *const fmt, ...) +{ + if (global_log_callback) { + int ret; + va_list args; + va_start(args, fmt); + ret = global_log_callback(level, fmt, args); + va_end(args); + return ret; + } + return 0; +} + +/** @} */ /* end flashrom-general */ + + + +/** + * @defgroup flashrom-query Querying + * @{ + */ + +/* TBD */ + +/** @} */ /* end flashrom-query */ + + + +/** + * @defgroup flashrom-prog Programmers + * @{ + */ + +/** + * @brief Initialize the specified programmer. + * + * Currently, only one programmer may be initialized at a time. + * + * @param[out] flashprog Points to a pointer of type struct flashrom_programmer + * that will be set if programmer initialization succeeds. + * *flashprog has to be shutdown by the caller with @ref + * flashrom_programmer_shutdown. + * @param[in] prog_name Name of the programmer to initialize. + * @param[in] prog_param Pointer to programmer specific parameters. + * @return 0 on success + */ +int flashrom_programmer_init(struct flashrom_programmer **const flashprog, + const char *const prog_name, const char *const prog_param) +{ + unsigned prog; + + for (prog = 0; prog < PROGRAMMER_INVALID; prog++) { + if (strcmp(prog_name, programmer_table[prog].name) == 0) + break; + } + if (prog >= PROGRAMMER_INVALID) { + msg_ginfo("Error: Unknown programmer \"%s\". Valid choices are:\n", prog_name); + list_programmers_linebreak(0, 80, 0); + return 1; + } + return programmer_init(prog, prog_param); +} + +/** + * @brief Shut down the initialized programmer. + * + * @param flashprog The programmer to shut down. + * @return 0 on success + */ +int flashrom_programmer_shutdown(struct flashrom_programmer *const flashprog) +{ + return programmer_shutdown(); +} + +/* TODO: flashrom_programmer_capabilities()? */ + +/** @} */ /* end flashrom-prog */ + + + +/** + * @defgroup flashrom-flash Flash chips + * @{ + */ + +/** + * @brief Probe for a flash chip. + * + * Probes for a flash chip and returns a flash context, that can be used + * later with flash chip and @ref flashrom-ops "image operations", if + * exactly one matching chip is found. + * + * @param[out] flashctx Points to a pointer of type struct flashrom_flashctx + * that will be set if exactly one chip is found. *flashctx + * has to be freed by the caller with @ref flashrom_flash_release. + * @param[in] flashprog The flash programmer used to access the chip. + * @param[in] chip_name Name of a chip to probe for, or NULL to probe for + * all known chips. + * @return 0 on success, + * 3 if multiple chips were found, + * 2 if no chip was found, + * or 1 on any other error. + */ +int flashrom_flash_probe(struct flashrom_flashctx **const flashctx, + const struct flashrom_programmer *const flashprog, + const char *const chip_name) +{ + int i, ret = 2; + struct flashrom_flashctx second_flashctx = { 0, }; + + chip_to_probe = chip_name; /* chip_to_probe is global in flashrom.c */ + + *flashctx = malloc(sizeof(**flashctx)); + if (!*flashctx) + return 1; + memset(*flashctx, 0, sizeof(**flashctx)); + + for (i = 0; i < registered_master_count; ++i) { + int flash_idx = -1; + if (!ret || (flash_idx = probe_flash(®istered_masters[i], 0, *flashctx, 0)) != -1) { + ret = 0; + /* We found one chip, now check that there is no second match. */ + if (probe_flash(®istered_masters[i], flash_idx + 1, &second_flashctx, 0) != -1) { + ret = 3; + break; + } + } + } + if (ret) { + free(*flashctx); + *flashctx = NULL; + } + return ret; +} + +/** + * @brief Returns the size of the specified flash chip in bytes. + * + * @param flashctx The queried flash context. + * @return Size of flash chip in bytes. + */ +size_t flashrom_flash_getsize(const struct flashrom_flashctx *const flashctx) +{ + return flashctx->chip->total_size * 1024; +} + +/** + * @brief Free a flash context. + * + * @param flashctx Flash context to free. + */ +void flashrom_flash_release(struct flashrom_flashctx *const flashctx) +{ + free(flashctx); +} + +/** + * @brief Set a flag in the given flash context. + * + * @param flashctx Flash context to alter. + * @param flag Flag that is to be set / cleared. + * @param value Value to set. + */ +void flashrom_flag_set(struct flashrom_flashctx *const flashctx, + const enum flashrom_flag flag, const bool value) +{ + switch (flag) { + case FLASHROM_FLAG_FORCE: flashctx->flags.force = value; break; + case FLASHROM_FLAG_FORCE_BOARDMISMATCH: flashctx->flags.force_boardmismatch = value; break; + case FLASHROM_FLAG_VERIFY_AFTER_WRITE: flashctx->flags.verify_after_write = value; break; + case FLASHROM_FLAG_VERIFY_WHOLE_CHIP: flashctx->flags.verify_whole_chip = value; break; + } +} + +/** + * @brief Return the current value of a flag in the given flash context. + * + * @param flashctx Flash context to read from. + * @param flag Flag to be read. + * @return Current value of the flag. + */ +bool flashrom_flag_get(const struct flashrom_flashctx *const flashctx, const enum flashrom_flag flag) +{ + switch (flag) { + case FLASHROM_FLAG_FORCE: return flashctx->flags.force; + case FLASHROM_FLAG_FORCE_BOARDMISMATCH: return flashctx->flags.force_boardmismatch; + case FLASHROM_FLAG_VERIFY_AFTER_WRITE: return flashctx->flags.verify_after_write; + case FLASHROM_FLAG_VERIFY_WHOLE_CHIP: return flashctx->flags.verify_whole_chip; + default: return false; + } +} + +/** @} */ /* end flashrom-flash */ + + + +/** + * @defgroup flashrom-layout Layout handling + * @{ + */ + +/** + * @brief Mark given region as included. + * + * @param layout The layout to alter. + * @param name The name of the region to include. + * + * @return 0 on success, + * 1 if the given name can't be found. + */ +int flashrom_layout_include_region(struct flashrom_layout *const layout, const char *name) +{ + size_t i; + for (i = 0; i < layout->num_entries; ++i) { + if (!strcmp(layout->entries[i].name, name)) { + layout->entries[i].included = true; + return 0; + } + } + return 1; +} + +/** + * @brief Free a layout. + * + * @param layout Layout to free. + */ +void flashrom_layout_release(struct flashrom_layout *const layout) +{ + if (layout == get_global_layout()) + return; + + free(layout); +} + +/** + * @brief Set the active layout for a flash context. + * + * Note: This just sets a pointer. The caller must not release the layout + * as long as he uses it through the given flash context. + * + * @param flashctx Flash context whose layout will be set. + * @param layout Layout to bet set. + */ +void flashrom_layout_set(struct flashrom_flashctx *const flashctx, const struct flashrom_layout *const layout) +{ + flashctx->layout = layout; +} + +/** @} */ /* end flashrom-layout */ diff --git a/libflashrom.h b/libflashrom.h new file mode 100644 index 000000000..42b02f9fa --- /dev/null +++ b/libflashrom.h @@ -0,0 +1,70 @@ +/* + * This file is part of the flashrom project. + * + * Copyright (C) 2012 secunet Security Networks AG + * (Written by Nico Huber for secunet) + * + * This program 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 program 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 this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __LIBFLASHROM_H__ +#define __LIBFLASHROM_H__ 1 + +#include + +int flashrom_init(int perform_selfcheck); +int flashrom_shutdown(void); +/** @ingroup flashrom-general */ +enum flashrom_log_level { /* This has to match enum msglevel. */ + FLASHROM_MSG_ERROR = 0, + FLASHROM_MSG_INFO = 1, + FLASHROM_MSG_DEBUG = 2, + FLASHROM_MSG_DEBUG2 = 3, + FLASHROM_MSG_SPEW = 4, +}; +/** @ingroup flashrom-general */ +typedef int(flashrom_log_callback)(enum flashrom_log_level, const char *format, va_list); +void flashrom_set_log_callback(flashrom_log_callback *); + +struct flashrom_programmer; +int flashrom_programmer_init(struct flashrom_programmer **, const char *prog_name, const char *prog_params); +int flashrom_programmer_shutdown(struct flashrom_programmer *); + +struct flashrom_flashctx; +int flashrom_flash_probe(struct flashrom_flashctx **, const struct flashrom_programmer *, const char *chip_name); +size_t flashrom_flash_getsize(const struct flashrom_flashctx *); +int flashrom_flash_erase(struct flashrom_flashctx *); +void flashrom_flash_release(struct flashrom_flashctx *); + +/** @ingroup flashrom-flash */ +enum flashrom_flag { + FLASHROM_FLAG_FORCE, + FLASHROM_FLAG_FORCE_BOARDMISMATCH, + FLASHROM_FLAG_VERIFY_AFTER_WRITE, + FLASHROM_FLAG_VERIFY_WHOLE_CHIP, +}; +void flashrom_flag_set(struct flashrom_flashctx *, enum flashrom_flag, bool value); +bool flashrom_flag_get(const struct flashrom_flashctx *, enum flashrom_flag); + +int flashrom_image_read(struct flashrom_flashctx *, void *buffer, size_t buffer_len); +int flashrom_image_write(struct flashrom_flashctx *, const void *buffer, size_t buffer_len); +int flashrom_image_verify(struct flashrom_flashctx *, const void *buffer, size_t buffer_len); + +struct flashrom_layout; +int flashrom_layout_include_region(struct flashrom_layout *, const char *name); +void flashrom_layout_release(struct flashrom_layout *); +void flashrom_layout_set(struct flashrom_flashctx *, const struct flashrom_layout *); + +#endif /* !__LIBFLASHROM_H__ */