Steve Markgraf has uploaded this change for review.
programmer: Add bitbanging programmer driver for Linux libgpiod
With this driver, any single board computer, old smartphone, etc. with
a few spare GPIOs can be used for flashrom.
An example invocation looks like this:
flashrom -p linux_gpiod:gpiochip=0,cs=18,sck=19,mosi=13,miso=56
This uses /dev/gpiochip0 with the specified GPIO numbers for the SPI
lines. All arguments are required.
Reading of a 2048 kB flash chip on a Qualcomm MSM8916 SoC @800 MHz:
Found GigaDevice flash chip "GD25LQ16" (2048 kB, SPI) on linux_gpiod.
real 1m 33.96s
Change-Id: Icad3eb7764f28feaea51bda3a7893da724c86d06
Signed-off-by: Steve Markgraf <steve@steve-m.de>
---
M Makefile
M include/programmer.h
A linux_gpiod.c
M programmer_table.c
4 files changed, 225 insertions(+), 1 deletion(-)
git pull ssh://review.coreboot.org:29418/flashrom refs/changes/01/71801/1
diff --git a/Makefile b/Makefile
index 425b58c..33de024 100644
--- a/Makefile
+++ b/Makefile
@@ -179,6 +179,9 @@
CONFIG_REALTEK_MST_I2C_SPI \
CONFIG_MEDIATEK_I2C_SPI \
+DEPENDS_ON_LIBGPIOD := \
+ CONFIG_LINUX_GPIOD \
+
ifeq ($(CONFIG_ENABLE_LIBUSB1_PROGRAMMERS), no)
$(call disable_all,$(DEPENDS_ON_LIBUSB1))
endif
@@ -233,6 +236,10 @@
CONFIG_LIBPCI_CFLAGS := $(call dependency_cflags, libpci)
CONFIG_LIBPCI_LDFLAGS := $(call dependency_ldflags, libpci)
+CONFIG_LIBGPIOD_VERSION := $(call dependency_version, libgpiod)
+CONFIG_LIBGPIOD_CFLAGS := $(call dependency_cflags, libgpiod)
+CONFIG_LIBGPIOD_LDFLAGS := $(call dependency_ldflags, libgpiod)
+
# Determine the destination OS, architecture and endian
# IMPORTANT: The following lines must be placed before TARGET_OS, ARCH or ENDIAN
# is ever used (of course), but should come after any lines setting CC because
@@ -247,6 +254,7 @@
HAS_LIBJAYLINK := $(call find_dependency, libjaylink)
HAS_LIBUSB1 := $(call find_dependency, libusb-1.0)
HAS_LIBPCI := $(call find_dependency, libpci)
+HAS_LIBGPIOD := $(call find_dependency, libgpiod)
HAS_PCI_OLD_GET_DEV := $(call c_compile_test, Makefile.d/pci_old_get_dev_test.c, $(CONFIG_LIBPCI_CFLAGS))
HAS_FT232H := $(call c_compile_test, Makefile.d/ft232h_test.c, $(CONFIG_LIBFTDI1_CFLAGS))
@@ -350,6 +358,10 @@
$(call mark_unsupported,$(DEPENDS_ON_LIBUSB1))
endif
+ifeq ($(HAS_LIBGPIOD), no)
+$(call mark_unsupported,$(DEPENDS_ON_LIBGPIOD))
+endif
+
ifeq ($(HAS_SERIAL), no)
$(call mark_unsupported, $(DEPENDS_ON_SERIAL))
endif
@@ -506,9 +518,10 @@
# Always enable Marvell SATA controllers for now.
CONFIG_SATAMV ?= yes
-# Enable Linux spidev and MTD interfaces by default. We disable them on non-Linux targets.
+# Enable Linux spidev, MTD and gpiod interfaces by default. We disable them on non-Linux targets.
CONFIG_LINUX_MTD ?= yes
CONFIG_LINUX_SPI ?= yes
+CONFIG_LINUX_GPIOD ?= yes
# Always enable ITE IT8212F PATA controllers for now.
CONFIG_IT8212 ?= yes
@@ -757,6 +770,11 @@
PROGRAMMER_OBJS += linux_spi.o
endif
+ifeq ($(CONFIG_LINUX_GPIOD), yes)
+FEATURE_FLAGS += -D'CONFIG_LINUX_GPIOD=1'
+PROGRAMMER_OBJS += linux_gpiod.o
+endif
+
ifeq ($(CONFIG_MSTARDDC_SPI), yes)
FEATURE_FLAGS += -D'CONFIG_MSTARDDC_SPI=1'
PROGRAMMER_OBJS += mstarddc_spi.o
@@ -865,6 +883,12 @@
endif
endif
+USE_LIBGPIOD := $(if $(call filter_deps,$(DEPENDS_ON_LIBGPIOD)),yes,no)
+ifeq ($(USE_LIBGPIOD), yes)
+override CFLAGS += $(CONFIG_LIBGPIOD_CFLAGS)
+override LDFLAGS += $(CONFIG_LIBGPIOD_LDFLAGS)
+endif
+
USE_LIB_NI845X := $(if $(call filter_deps,$(DEPENDS_ON_LIB_NI845X)),yes,no)
ifeq ($(USE_LIB_NI845X), yes)
override CFLAGS += $(CONFIG_LIB_NI845X_CFLAGS)
@@ -943,6 +967,11 @@
echo " CFLAGS: $(CONFIG_LIBFTDI1_CFLAGS)"; \
echo " LDFLAGS: $(CONFIG_LIBFTDI1_LDFLAGS)"; \
fi
+ @echo Dependency libgpiod found: $(HAS_LIBGPIOD) $(CONFIG_LIBGPIOD_VERSION)
+ @if [ $(HAS_LIBGPIOD) = yes ]; then \
+ echo " CFLAGS: $(CONFIG_LIBGPIOD_CFLAGS)"; \
+ echo " LDFLAGS: $(CONFIG_LIBGPIOD_LDFLAGS)"; \
+ fi
@echo "Checking for header \"mtd/mtd-user.h\": $(HAS_LINUX_MTD)"
@echo "Checking for header \"linux/spi/spidev.h\": $(HAS_LINUX_SPI)"
@echo "Checking for header \"linux/i2c-dev.h\": $(HAS_LINUX_I2C)"
diff --git a/include/programmer.h b/include/programmer.h
index 67e7b4a..e364c63 100644
--- a/include/programmer.h
+++ b/include/programmer.h
@@ -72,6 +72,7 @@
extern const struct programmer_entry programmer_jlink_spi;
extern const struct programmer_entry programmer_linux_mtd;
extern const struct programmer_entry programmer_linux_spi;
+extern const struct programmer_entry programmer_linux_gpiod;
extern const struct programmer_entry programmer_parade_lspcon;
extern const struct programmer_entry programmer_mediatek_i2c_spi;
extern const struct programmer_entry programmer_mstarddc_spi;
diff --git a/linux_gpiod.c b/linux_gpiod.c
new file mode 100644
index 0000000..1e0fa67
--- /dev/null
+++ b/linux_gpiod.c
@@ -0,0 +1,165 @@
+/*
+ * This file is part of the flashrom project.
+ *
+ * Copyright (C) 2023 Steve Markgraf <steve@steve-m.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; 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.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <gpiod.h>
+#include "programmer.h"
+#include "spi.h"
+#include "flash.h"
+
+#define CONSUMER "flashrom"
+
+struct gpiod_spi_data {
+ struct gpiod_chip *chip;
+ struct gpiod_line_bulk bulk;
+ struct gpiod_line *cs_line, *sck_line, *mosi_line, *miso_line;
+};
+
+static void linux_gpiod_bitbang_set_cs(int val, void *spi_data)
+{
+ struct gpiod_spi_data *data = spi_data;
+ if (gpiod_line_set_value(data->cs_line, val) < 0)
+ msg_perr("Setting cs line failed\n");
+}
+
+static void linux_gpiod_bitbang_set_sck(int val, void *spi_data)
+{
+ struct gpiod_spi_data *data = spi_data;
+ if (gpiod_line_set_value(data->sck_line, val) < 0)
+ msg_perr("Setting sck line failed\n");
+}
+
+static void linux_gpiod_bitbang_set_mosi(int val, void *spi_data)
+{
+ struct gpiod_spi_data *data = spi_data;
+ if (gpiod_line_set_value(data->mosi_line, val) < 0)
+ msg_perr("Setting sck line failed\n");
+}
+
+static int linux_gpiod_bitbang_get_miso(void *spi_data)
+{
+ struct gpiod_spi_data *data = spi_data;
+ int r = gpiod_line_get_value(data->miso_line);
+ if (r < 0)
+ msg_perr("Getting miso line failed\n");
+ return r;
+}
+
+static const struct bitbang_spi_master bitbang_spi_master_gpiod = {
+ .set_cs = linux_gpiod_bitbang_set_cs,
+ .set_sck = linux_gpiod_bitbang_set_sck,
+ .set_mosi = linux_gpiod_bitbang_set_mosi,
+ .get_miso = linux_gpiod_bitbang_get_miso,
+};
+
+static int linux_gpiod_spi_shutdown(void *spi_data)
+{
+ struct gpiod_spi_data *data = spi_data;
+
+ if (gpiod_line_bulk_num_lines(&data->bulk) > 0)
+ gpiod_line_release_bulk(&data->bulk);
+
+ if (data->chip)
+ gpiod_chip_close(data->chip);
+
+ free(data);
+
+ return 0;
+}
+
+static int linux_gpiod_spi_init(const struct programmer_cfg *cfg)
+{
+ struct gpiod_spi_data *data = NULL;
+ struct gpiod_chip *chip = NULL;
+ const char *param_str[] = { "cs", "sck", "mosi", "miso", "gpiochip" };
+ unsigned int param_int[ARRAY_SIZE(param_str)];
+ unsigned int i;
+ int r;
+
+ for (i = 0; i < ARRAY_SIZE(param_str); i++) {
+ char *param = extract_programmer_param_str(cfg, param_str[i]);
+ if (param) {
+ param_int[i] = atoi(param);
+ free(param);
+ } else {
+ msg_perr("Missing required programmer parameter %s=<n>\n", param_str[i]);
+ goto err_exit;
+ }
+ }
+
+ data = calloc(1, sizeof(*data));
+ if (!data) {
+ msg_perr("Unable to allocate space for SPI master data\n");
+ goto err_exit;
+ }
+
+ chip = gpiod_chip_open_by_number(param_int[4]);
+ if (!chip) {
+ msg_perr("Failed to open gpiochip: %s\n", strerror(errno));
+ goto err_exit;
+ }
+
+ data->chip = chip;
+
+ if (gpiod_chip_get_lines(chip, param_int, 4, &data->bulk)) {
+ msg_perr("Error getting GPIO lines\n");
+ goto err_exit;
+ }
+
+ data->cs_line = gpiod_line_bulk_get_line(&data->bulk, 0);
+ data->sck_line = gpiod_line_bulk_get_line(&data->bulk, 1);
+ data->mosi_line = gpiod_line_bulk_get_line(&data->bulk, 2);
+ data->miso_line = gpiod_line_bulk_get_line(&data->bulk, 3);
+
+ r = gpiod_line_request_output(data->cs_line, CONSUMER, 1);
+ r |= gpiod_line_request_output(data->sck_line, CONSUMER, 1);
+ r |= gpiod_line_request_output(data->mosi_line, CONSUMER, 1);
+ r |= gpiod_line_request_input(data->miso_line, CONSUMER);
+
+ if (r < 0) {
+ msg_perr("Requesting GPIO lines failed\n");
+ goto err_exit;
+ }
+
+ if (register_shutdown(linux_gpiod_spi_shutdown, data))
+ goto err_exit;
+
+ if (register_spi_bitbang_master(&bitbang_spi_master_gpiod, data))
+ return 1; /* shutdown function does the cleanup */
+
+ return 0;
+
+err_exit:
+ if (data) {
+ if (gpiod_line_bulk_num_lines(&data->bulk) > 0)
+ gpiod_line_release_bulk(&data->bulk);
+
+ free(data);
+ }
+
+ if (chip)
+ gpiod_chip_close(chip);
+ return 1;
+}
+
+const struct programmer_entry programmer_linux_gpiod = {
+ .name = "linux_gpiod",
+ .type = OTHER,
+ .devs.note = "Device file /dev/gpiochip<n>\n",
+ .init = linux_gpiod_spi_init,
+};
diff --git a/programmer_table.c b/programmer_table.c
index d58a155..848cd4e 100644
--- a/programmer_table.c
+++ b/programmer_table.c
@@ -124,6 +124,10 @@
&programmer_linux_spi,
#endif
+#if CONFIG_LINUX_GPIOD == 1
+ &programmer_linux_gpiod,
+#endif
+
#if CONFIG_PARADE_LSPCON == 1
&programmer_parade_lspcon,
#endif
To view, visit change 71801. To unsubscribe, or for help writing mail filters, visit settings.