Marc Schink has uploaded this change for review. ( https://review.coreboot.org/28087
Change subject: Add initial J-Link SPI programmer ......................................................................
Add initial J-Link SPI programmer
Tested with SEGGER J-Link EDU, Flasher ARM and flash chip W25Q16.V.
Change-Id: Ie03a054a75457ec9e1cab36ea124bb53b10e8d7e Signed-off-by: Marc Schink flashrom-dev@marcschink.de --- M Makefile M README M flashrom.8.tmpl M flashrom.c A jlink_spi.c M programmer.h 6 files changed, 629 insertions(+), 1 deletion(-)
git pull ssh://review.coreboot.org:29418/flashrom refs/changes/87/28087/1
diff --git a/Makefile b/Makefile index cd72972..cf8ca9e 100644 --- a/Makefile +++ b/Makefile @@ -643,6 +643,9 @@ # Digilent Development board JTAG CONFIG_DIGILENT_SPI ?= yes
+# Enable J-Link for now. +CONFIG_JLINK_SPI ?= no + # Disable wiki printing by default. It is only useful if you have wiki access. CONFIG_PRINT_WIKI ?= no
@@ -944,6 +947,12 @@ NEED_LIBUSB1 += CONFIG_DIGILENT_SPI endif
+ifeq ($(CONFIG_JLINK_SPI), yes) +NEED_LIBJAYLINK += CONFIG_JLINK_SPI +FEATURE_CFLAGS += -D'CONFIG_JLINK_SPI=1' +PROGRAMMER_OBJS += jlink_spi.o +endif + ifneq ($(NEED_SERIAL), ) LIB_OBJS += serial.o custom_baud.o endif @@ -1017,6 +1026,12 @@ endif endif
+ifneq ($(NEED_LIBJAYLINK), ) +CHECK_LIBJAYLINK = yes +JAYLINKLIBS += $(call debug_shell,[ -n "$(PKG_CONFIG_LIBDIR)" ] && export PKG_CONFIG_LIBDIR="$(PKG_CONFIG_LIBDIR)"; $(PKG_CONFIG) --libs libjaylink) +override CPPFLAGS += $(call debug_shell,[ -n "$(PKG_CONFIG_LIBDIR)" ] && export PKG_CONFIG_LIBDIR="$(PKG_CONFIG_LIBDIR)"; $(PKG_CONFIG) --cflags-only-I libjaylink) +endif + ifeq ($(CONFIG_PRINT_WIKI), yes) FEATURE_CFLAGS += -D'CONFIG_PRINT_WIKI=1' CLI_OBJS += print_wiki.o @@ -1039,7 +1054,7 @@ endif
$(PROGRAM)$(EXEC_SUFFIX): $(OBJS) - $(CC) $(LDFLAGS) -o $(PROGRAM)$(EXEC_SUFFIX) $(OBJS) $(LIBS) $(PCILIBS) $(FEATURE_LIBS) $(USBLIBS) $(USB1LIBS) + $(CC) $(LDFLAGS) -o $(PROGRAM)$(EXEC_SUFFIX) $(OBJS) $(LIBS) $(PCILIBS) $(FEATURE_LIBS) $(USBLIBS) $(USB1LIBS) $(JAYLINKLIBS)
libflashrom.a: $(LIBFLASHROM_OBJS) $(AR) rcs $@ $^ @@ -1173,6 +1188,24 @@ endef export LIBUSB1_TEST
+define LIBJAYLINK_TEST +#include <stddef.h> +#include <libjaylink/libjaylink.h> +int main(int argc, char **argv) +{ + struct jaylink_context *ctx; + + (void)argc; + (void)argv; + + jaylink_init(&ctx); + jaylink_exit(ctx); + + return 0; +} +endef +export LIBJAYLINK_TEST + hwlibs: compiler @printf "" > .libdeps ifeq ($(CHECK_LIBPCI), yes) @@ -1251,6 +1284,28 @@ rm -f .test.c .test.o .test$(EXEC_SUFFIX); exit 1; }; } 2>>$(BUILD_DETAILS_FILE); echo $? >&3 ; } | tee -a $(BUILD_DETAILS_FILE) >&4; } 3>&1;} | { read rc ; exit ${rc}; } } 4>&1 @rm -f .test.c .test.o .test$(EXEC_SUFFIX) endif +ifeq ($(CHECK_LIBJAYLINK), yes) + @printf "Checking for libjaylink headers... " | tee -a $(BUILD_DETAILS_FILE) + @echo "$$LIBJAYLINK_TEST" > .test.c + @printf "\nexec: %s\n" "$(CC) -c $(CPPFLAGS) $(CFLAGS) .test.c -o .test.o" >>$(BUILD_DETAILS_FILE) + @{ { { { { $(CC) -c $(CPPFLAGS) $(CFLAGS) .test.c -o .test.o >&2 && \ + echo "found." || { echo "not found."; echo; \ + echo "The following feature requires libjaylink: $(NEED_LIBJAYLINK)."; \ + echo "Please install libjaylink headers or disable the feature"; \ + echo "mentioned above by specifying make CONFIG_JLINK_SPI=no"; \ + echo "See README for more information."; echo; \ + rm -f .test.c .test.o; exit 1; }; } 2>>$(BUILD_DETAILS_FILE); echo $? >&3 ; } | tee -a $(BUILD_DETAILS_FILE) >&4; } 3>&1;} | { read rc ; exit ${rc}; } } 4>&1 + @printf "Checking if libjaylink is usable... " | tee -a $(BUILD_DETAILS_FILE) + @printf "\nexec: %s\n" "$(CC) $(LDFLAGS) .test.o -o .test$(EXEC_SUFFIX) $(LIBS) $(JAYLINKLIBS)" >>$(BUILD_DETAILS_FILE) + @{ { { { { $(CC) $(LDFLAGS) .test.o -o .test$(EXEC_SUFFIX) $(LIBS) $(JAYLINKLIBS) >&2 && \ + echo "yes." || { echo "no."; \ + echo "The following feature requires libjaylink: $(NEED_LIBJAYLINK)."; \ + echo "Please install libjaylink or disable the feature"; \ + echo "mentioned above by specifying make CONFIG_JLINK_SPI=no"; \ + echo "See README for more information."; echo; \ + rm -f .test.c .test.o .test$(EXEC_SUFFIX); exit 1; }; } 2>>$(BUILD_DETAILS_FILE); echo $? >&3 ; } | tee -a $(BUILD_DETAILS_FILE) >&4; } 3>&1;} | { read rc ; exit ${rc}; } } 4>&1 + @rm -f .test.c .test.o .test$(EXEC_SUFFIX) +endif
.features: features
diff --git a/README b/README index b5ed946..4e7bd4f 100644 --- a/README +++ b/README @@ -50,6 +50,7 @@ * pciutils+libpci (if you want support for mainboard or PCI device flashing) * libusb (if you want FT2232, Dediprog or USB-Blaster support) * libftdi (if you want FT2232 or USB-Blaster support) + * libjaylink (if you want support for SEGGER J-Link and compatible devices)
Linux et al:
diff --git a/flashrom.8.tmpl b/flashrom.8.tmpl index 70af395..51cf2cd 100644 --- a/flashrom.8.tmpl +++ b/flashrom.8.tmpl @@ -305,6 +305,8 @@ .sp .BR "* digilent_spi" " (for SPI flash ROMs attached to iCEblink40 development boards)" .sp +.BR "* jlink_spi" " (for SPI flash ROMs attached to SEGGER J-Link and compatible devices)" +.sp Some programmers have optional or mandatory parameters which are described in detail in the .B PROGRAMMER-SPECIFIC INFORMATION @@ -1102,6 +1104,63 @@ (in Hz). The default is a frequency of 4 MHz. .sp .SS +.BR "jlink_spi " programmer +.IP +This module supports SEGGER J-Link and compatible devices. + +The \fBMOSI\fP signal of the flash chip must be attached to \fBTDI\fP pin of +the programmer, \fBMISO\fP to \fBTDO\fP and \fBSCK\fP to \fBTCK\fP. +The chip select (\fBCS\fP) signal of the flash chip can be attached to +different pins of the programmer which can be selected with the +.sp +.B " flashrom -p jlink_spi:cs=pin" +.sp +syntax where \fBpin\fP can be either \fBTRST\fP or \fBRESET\fP. +The default pin for chip select is \fBRESET\fP. +Note that, when using \fBRESET\fP, it is normal that the indicator LED blinks +orange or red. +.br +Additionally, the \fBVTref\fP pin of the programmer must be attached to the +logic level of the flash chip. +The programmer measures the voltage on this pin and generates the reference +voltage for its input comparators and adapts its output voltages to it. +.sp +Pinout for devices with 20-pin JTAG connector: +.sp + +-------+ + | 1 2 | 1: VTref 15: RESET + | 3 4 | 3: TRST 19: Power supply (5 V) + | 5 6 | 4: GND + +-+ 7 8 | 5: TDI + | 9 10 | 6: GND + | 11 12 | 8: GND + +-+ 13 14 | 9: TCK + | 15 16 | 10: GND + | 17 18 | 12: GND + | 19 20 | 13: TDO + +-------+ +.sp +If there is more than one compatible device connected, you can select which one +should be used by specifying its serial number with the +.sp +.B " flashrom -p jlink_spi:serial=number" +.sp +syntax where +.B number +is the serial number of the device (which can be found for example in the +output of lsusb -v). +.sp +The SPI speed can be selected by using the +.sp +.B " flashrom -p jlink_spi:spispeed=frequency" +.sp +syntax where \fBfrequency\fP is the SPI clock frequency in Hz. +The minimum SPI speed is 1 kHz whereas the maximum speed depends on the device +in use. +Note that a speed which is not a multiple of 1 kHz is automatically reduced to +such one. +.SS + .SH EXAMPLES To back up and update your BIOS, run .sp diff --git a/flashrom.c b/flashrom.c index 1866a18..2353d5c 100644 --- a/flashrom.c +++ b/flashrom.c @@ -425,6 +425,18 @@ }, #endif
+#if CONFIG_JLINK_SPI == 1 + { + .name = "jlink_spi", + .type = OTHER, + .init = jlink_spi_init, + .devs.note = "SEGGER J-Link and compatible devices\n", + .map_flash_region = fallback_map, + .unmap_flash_region = fallback_unmap, + .delay = internal_delay, + }, +#endif + {0}, /* This entry corresponds to PROGRAMMER_INVALID. */ };
diff --git a/jlink_spi.c b/jlink_spi.c new file mode 100644 index 0000000..d6c41b1 --- /dev/null +++ b/jlink_spi.c @@ -0,0 +1,490 @@ +/* + * This file is part of the flashrom project. + * + * Copyright (C) 2016 Marc Schink flashrom-dev@marcschink.de + * + * 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; version 2 of the License. + * + * 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 + */ + +/* Driver for the J-Link hardware by SEGGER. + * See https://www.segger.com/ for more info. + */ + +#include <stdlib.h> +#include <stdbool.h> +#include <stdint.h> +#include <string.h> +#include <errno.h> +#include <libjaylink/libjaylink.h> + +#include "flash.h" +#include "programmer.h" +#include "spi.h" + +/* + * Maximum number of bytes that can be transferred at once via the JTAG + * interface. + */ +#define JTAG_MAX_TRANSFER_SIZE (UINT16_MAX / 8) + +/* + * Default base frequency in Hz. Used when the base frequency can not be + * retrieved from the device. + */ +#define DEFAULT_FREQ 16000000 + +/* + * Default frequency divider. Used when the frequency divider can not be + * retrieved from the device. + */ +#define DEFAULT_FREQ_DIV 4 + +/* Minimum target voltage required for operation in mV. */ +#define MIN_TARGET_VOLTAGE 1200 + +static struct jaylink_context *jaylink_ctx; +static struct jaylink_device_handle *jaylink_devh; +static uint8_t *buffer; +static size_t buffer_size; +static bool reset_cs; + +static bool assert_cs(void) +{ + int ret; + + if (reset_cs) { + ret = jaylink_clear_reset(jaylink_devh); + + if (ret != JAYLINK_OK) { + msg_perr("jaylink_clear_reset() failed: %s.\n", + jaylink_strerror(ret)); + return false; + } + } else { + ret = jaylink_jtag_clear_trst(jaylink_devh); + + if (ret != JAYLINK_OK) { + msg_perr("jaylink_jtag_clear_trst() failed: %s.\n", + jaylink_strerror(ret)); + return false; + } + } + + return true; +} + +static bool deassert_cs(void) +{ + int ret; + + if (reset_cs) { + ret = jaylink_set_reset(jaylink_devh); + + if (ret != JAYLINK_OK) { + msg_perr("jaylink_clear_reset() failed: %s.\n", + jaylink_strerror(ret)); + return false; + } + } else { + ret = jaylink_jtag_set_trst(jaylink_devh); + + if (ret != JAYLINK_OK) { + msg_perr("jaylink_jtag_clear_trst() failed: %s.\n", + jaylink_strerror(ret)); + return false; + } + } + + return true; +} + +static int jlink_spi_send_command(struct flashctx *flash, + unsigned int writecnt, unsigned int readcnt, + const unsigned char *writearr, unsigned char *readarr) +{ + uint32_t length; + + length = writecnt + readcnt; + + if (length > JTAG_MAX_TRANSFER_SIZE) + return SPI_INVALID_LENGTH; + + if (length > buffer_size) { + uint8_t *tmp; + + tmp = realloc(buffer, length); + + if (!tmp) { + msg_perr("Memory allocation failed.\n"); + free(buffer); + return SPI_GENERIC_ERROR; + } + + buffer = tmp; + } + + /* Reverse all bytes because the device transfers data LSB first. */ + reverse_bytes(buffer, writearr, writecnt); + + memset(buffer + writecnt, 0x00, readcnt); + + if (!assert_cs()) + return SPI_PROGRAMMER_ERROR; + + int ret; + + ret = jaylink_jtag_io(jaylink_devh, buffer, buffer, buffer, length * 8, + JAYLINK_JTAG_VERSION_2); + + if (ret != JAYLINK_OK) { + msg_perr("jaylink_jag_io() failed: %s.\n", + jaylink_strerror(ret)); + return SPI_PROGRAMMER_ERROR; + } + + if (!deassert_cs()) + return SPI_PROGRAMMER_ERROR; + + /* Reverse all bytes because the device transfers data LSB first. */ + reverse_bytes(readarr, buffer + writecnt, readcnt); + + return 0; +} + +static const struct spi_master spi_master_jlink_spi = { + .type = SPI_CONTROLLER_JLINK_SPI, + /* Maximum data read size in one go (excluding opcode+address). */ + .max_data_read = JTAG_MAX_TRANSFER_SIZE, + /* Maximum data write size in one go (excluding opcode+address). */ + .max_data_write = JTAG_MAX_TRANSFER_SIZE, + .command = jlink_spi_send_command, + .multicommand = default_spi_send_multicommand, + .read = default_spi_read, + .write_256 = default_spi_write_256, + .write_aai = default_spi_write_aai, +}; + +static int jlink_spi_shutdown(void *data) +{ + free(buffer); + + if (jaylink_devh) + jaylink_close(jaylink_devh); + + jaylink_exit(jaylink_ctx); + + return 0; +} + +int jlink_spi_init(void) +{ + char *arg; + unsigned long speed = 0; + + register_shutdown(jlink_spi_shutdown, NULL); + + arg = extract_programmer_param("spispeed"); + + if (arg && strlen(arg) > 0) { + char *endptr; + + errno = 0; + speed = strtoul(arg, &endptr, 10); + + if (*endptr != '\0' || errno != 0) { + msg_perr("Invalid SPI speed specified: %s.\n", arg); + free(arg); + return 1; + } + + if (speed < 1000) { + msg_perr("SPI speed must be at least 1 kHz.\n"); + free(arg); + return 1; + } + + if (speed % 1000 > 0) { + speed -= speed % 1000; + msg_pinfo("Reduced SPI speed to %lu kHz (multiple of " + "1 kHz).\n", speed / 1000); + } + + speed /= 1000; + } + + free(arg); + + int ret; + bool use_serial_number; + uint32_t serial_number; + + arg = extract_programmer_param("serial"); + + if (arg && strlen(arg) > 0) { + ret = jaylink_parse_serial_number(arg, &serial_number); + + if (ret == JAYLINK_ERR) { + msg_perr("Invalid serial number specified: %s.\n", + arg); + free(arg); + return 1; + } if (ret != JAYLINK_OK) { + msg_perr("jaylink_parse_serial_number() failed: %s.\n", + jaylink_strerror(ret)); + free(arg); + return 1; + } + + use_serial_number = true; + } else { + use_serial_number = false; + } + + free(arg); + + reset_cs = true; + arg = extract_programmer_param("cs"); + + if (arg && strlen(arg) > 0) { + if (!strcasecmp(arg, "reset")) { + reset_cs = true; + } else if (!strcasecmp(arg, "trst")) { + reset_cs = false; + } else { + msg_perr("Invalid chip select pin specified: %s.\n", + arg); + free(arg); + return 1; + } + } + + free(arg); + + if (reset_cs) + msg_pdbg("Using RESET as chip select signal.\n"); + else + msg_pdbg("Using TRST as chip select signal.\n"); + + ret = jaylink_init(&jaylink_ctx); + + if (ret != JAYLINK_OK) { + msg_perr("jaylink_init() failed: %s.\n", + jaylink_strerror(ret)); + return 1; + } + + ret = jaylink_discovery_scan(jaylink_ctx, 0); + + if (ret != JAYLINK_OK) { + msg_perr("jaylink_discover_scan() failed: %s.\n", + jaylink_strerror(ret)); + return 1; + } + + struct jaylink_device **devs; + + ret = jaylink_get_devices(jaylink_ctx, &devs, NULL); + + if (ret != JAYLINK_OK) { + msg_perr("jaylink_get_devices() failed: %s.\n", + jaylink_strerror(ret)); + return 1; + } + + if (!use_serial_number) + msg_pdbg("No device selected, using first device.\n"); + + size_t i; + struct jaylink_device *dev; + bool device_found = false; + + for (i = 0; devs[i]; i++) { + if (use_serial_number) { + uint32_t tmp; + + ret = jaylink_device_get_serial_number(devs[i], &tmp); + + if (ret == JAYLINK_ERR_NOT_AVAILABLE) { + continue; + } else if (ret != JAYLINK_OK) { + msg_pwarn("jaylink_device_get_serial_number() " + "failed: %s.\n", + jaylink_strerror(ret)); + continue; + } + + if (serial_number != tmp) + continue; + } + + ret = jaylink_open(devs[i], &jaylink_devh); + + if (ret == JAYLINK_OK) { + dev = devs[i]; + device_found = true; + break; + } + + jaylink_devh = NULL; + } + + jaylink_free_devices(devs, true); + + if (!device_found) { + msg_perr("No J-Link device found.\n"); + return 1; + } + + size_t length; + char *firmware_version; + + ret = jaylink_get_firmware_version(jaylink_devh, &firmware_version, + &length); + + if (ret != JAYLINK_OK) { + msg_perr("jaylink_get_firmware_version() failed: %s.\n", + jaylink_strerror(ret)); + return 1; + } else if (length > 0) { + msg_pdbg("Firmware: %s\n", firmware_version); + free(firmware_version); + } + + ret = jaylink_device_get_serial_number(dev, &serial_number); + + if (ret == JAYLINK_OK) { + msg_pdbg("S/N: %" PRIu32 "\n", serial_number); + } else if (ret == JAYLINK_ERR_NOT_AVAILABLE) { + msg_pdbg("S/N: N/A\n"); + } else { + msg_perr("jaylink_device_get_serial_number() failed: %s.\n", + jaylink_strerror(ret)); + return 1; + } + + uint8_t caps[JAYLINK_DEV_EXT_CAPS_SIZE]; + + memset(caps, 0, JAYLINK_DEV_EXT_CAPS_SIZE); + ret = jaylink_get_caps(jaylink_devh, caps); + + if (ret != JAYLINK_OK) { + msg_perr("jaylink_get_caps() failed: %s.\n", + jaylink_strerror(ret)); + return 1; + } + + if (jaylink_has_cap(caps, JAYLINK_DEV_CAP_GET_EXT_CAPS)) { + ret = jaylink_get_extended_caps(jaylink_devh, caps); + + if (ret != JAYLINK_OK) { + msg_perr("jaylink_get_available_interfaces() failed: " + "%s.\n", jaylink_strerror(ret)); + return 1; + } + } + + uint32_t ifaces; + + ret = jaylink_get_available_interfaces(jaylink_devh, &ifaces); + + if (ret != JAYLINK_OK) { + msg_perr("jaylink_get_available_interfaces() failed: %s.\n", + jaylink_strerror(ret)); + return 1; + } + + if (!(ifaces & (1 << JAYLINK_TIF_JTAG))) { + msg_perr("Device does not support JTAG interface.\n"); + return 1; + } + + ret = jaylink_select_interface(jaylink_devh, JAYLINK_TIF_JTAG, NULL); + + if (ret != JAYLINK_OK) { + msg_perr("jaylink_select_interface() failed: %s.\n", + jaylink_strerror(ret)); + return 1; + } + + struct jaylink_hardware_status hwstat; + + ret = jaylink_get_hardware_status(jaylink_devh, &hwstat); + + if (ret != JAYLINK_OK) { + msg_perr("jaylink_get_hardware_status() failed: %s.\n", + jaylink_strerror(ret)); + return 1; + } + + msg_pdbg("VTarget: %u.%03u V\n", hwstat.target_voltage / 1000, + hwstat.target_voltage % 1000); + + if (hwstat.target_voltage < MIN_TARGET_VOLTAGE) { + msg_perr("Target voltage is below %u.%03u V. You need to " + "attach VTref to the I/O voltage of the chip.\n", + MIN_TARGET_VOLTAGE / 1000, + MIN_TARGET_VOLTAGE % 1000); + return 1; + } + + struct jaylink_speed device_speeds; + + device_speeds.freq = DEFAULT_FREQ; + device_speeds.div = DEFAULT_FREQ_DIV; + + if (jaylink_has_cap(caps, JAYLINK_DEV_CAP_GET_SPEEDS)) { + ret = jaylink_get_speeds(jaylink_devh, &device_speeds); + + if (ret != JAYLINK_OK) { + msg_perr("jaylink_get_speeds() failed: %s.\n", + jaylink_strerror(ret)); + return 1; + } + } + + device_speeds.freq /= 1000; + + msg_pdbg("Maximum SPI speed: %" PRIu32 " kHz\n", + device_speeds.freq / device_speeds.div); + + if (!speed) { + speed = device_speeds.freq / device_speeds.div; + msg_pdbg("SPI speed not specified, using %lu kHz.\n", speed); + } + + if (speed > (device_speeds.freq / device_speeds.div)) { + msg_perr("Specified SPI speed of %lu kHz is too high. Maximum " + "is %" PRIu32 " kHz.\n", speed, + device_speeds.freq / device_speeds.div); + return 1; + } + + ret = jaylink_set_speed(jaylink_devh, speed); + + if (ret != JAYLINK_OK) { + msg_perr("jaylink_set_speed() failed: %s.\n", + jaylink_strerror(ret)); + return 1; + } + + msg_pdbg("SPI speed: %lu kHz\n", speed); + + /* Ensure that the CS signal is not active initially. */ + if (!deassert_cs()) + return 1; + + register_spi_master(&spi_master_jlink_spi); + + return 0; +} diff --git a/programmer.h b/programmer.h index ff81036..d48bf05 100644 --- a/programmer.h +++ b/programmer.h @@ -115,6 +115,9 @@ #if CONFIG_DIGILENT_SPI == 1 PROGRAMMER_DIGILENT_SPI, #endif +#if CONFIG_JLINK_SPI == 1 + PROGRAMMER_JLINK_SPI, +#endif PROGRAMMER_INVALID /* This must always be the last entry. */ };
@@ -558,6 +561,11 @@ extern const struct dev_entry devs_digilent_spi[]; #endif
+/* jlink_spi.c */ +#if CONFIG_JLINK_SPI == 1 +int jlink_spi_init(void); +#endif + /* flashrom.c */ struct decode_sizes { uint32_t parallel; @@ -626,6 +634,9 @@ #if CONFIG_DIGILENT_SPI == 1 SPI_CONTROLLER_DIGILENT_SPI, #endif +#if CONFIG_JLINK_SPI == 1 + SPI_CONTROLLER_JLINK_SPI, +#endif };
#define MAX_DATA_UNSPECIFIED 0