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.
Tested by reading of a 2048 kB flash chip on a Qualcomm MSM8916 SoC
@800 MHz, ran the following command:
time flashrom -p linux_gpiod:gpiochip=0,cs=18,sck=19,mosi=13,miso=56 -r test.bin
This command uses /dev/gpiochip0 with the specified GPIO numbers for the
SPI lines. All arguments are mandatory.
Output:
[...]
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>
Signed-off-by: Nico Huber <nico.h@gmx.de>
Reviewed-on: https://review.coreboot.org/c/flashrom-stable/+/73290
Tested-by: build bot (Jenkins) <no-reply@coreboot.org>
diff --git a/Documentation/building_meson.md b/Documentation/building_meson.md
index 21d400e..74dc65b 100644
--- a/Documentation/building_meson.md
+++ b/Documentation/building_meson.md
@@ -11,6 +11,7 @@
* libusb1 >=1.0.9 ***
* libftdi1 ***
* libjaylink ***
+ * libgpiod (Linux only) ***
\* Compile time dependency
\*** Runtime / Programmer specific
@@ -46,14 +47,14 @@
```
apt-get install -y gcc meson ninja-build pkg-config \
linux-headers-generic libpci-dev libusb-1.0-0-dev libftdi1-dev \
- libjaylink-dev
+ libjaylink-dev libgpiod-dev
```
### ArchLinux / Manjaro
* __libjaylink__ is not available through the package manager
```
pacman -S --noconfirm gcc meson ninja pkg-config \
- pciutils libusb libftdi
+ pciutils libusb libftdi libgpiod
```
### NixOS / Nixpkgs
@@ -62,20 +63,20 @@
```
or
```
-nix-shell -p meson ninja pkg-config pciutils libusb1 libftdi1 libjaylink
+nix-shell -p meson ninja pkg-config pciutils libusb1 libftdi1 libjaylink libgpiod
```
### OpenSUSE
```
zypper install -y gcc meson ninja pkg-config \
pciutils-devel libusb-1_0-devel libftdi1-devel \
- libjaylink-devel
+ libjaylink-devel libgpiod-devel
```
### Alpine
```
apk add build-base meson ninja pkgconf pciutils-dev libusb-dev \
- libftdi1-dev libjaylink-dev linux-headers
+ libftdi1-dev libjaylink-dev linux-headers libgpiod-dev
```
### Freebsd / DragonFly BSD
diff --git a/Makefile b/Makefile
index bcf795b..5db5e03 100644
--- a/Makefile
+++ b/Makefile
@@ -104,6 +104,7 @@
DEPENDS_ON_BITBANG_SPI := \
CONFIG_DEVELOPERBOX_SPI \
CONFIG_INTERNAL_X86 \
+ CONFIG_LINUX_GPIOD \
CONFIG_NICINTEL_SPI \
CONFIG_OGP_SPI \
CONFIG_PONY_SPI \
@@ -177,6 +178,9 @@
DEPENDS_ON_LINUX_I2C := \
CONFIG_MSTARDDC_SPI \
+DEPENDS_ON_LIBGPIOD := \
+ CONFIG_LINUX_GPIOD \
+
ifeq ($(CONFIG_ENABLE_LIBUSB1_PROGRAMMERS), no)
$(call disable_all,$(DEPENDS_ON_LIBUSB1))
endif
@@ -231,6 +235,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
@@ -245,6 +253,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))
@@ -351,6 +360,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
@@ -497,9 +510,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
@@ -731,6 +745,11 @@
PROGRAMMER_OBJS += linux_spi.o
endif
+ifeq ($(CONFIG_LINUX_GPIOD), yes)
+FEATURE_FLAGS += -D'CONFIG_LINUX_GPIOD=1'
+PROGRAMMER_OBJS += linux_gpio_spi.o
+endif
+
ifeq ($(CONFIG_MSTARDDC_SPI), yes)
FEATURE_FLAGS += -D'CONFIG_MSTARDDC_SPI=1'
PROGRAMMER_OBJS += mstarddc_spi.o
@@ -839,6 +858,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)
@@ -917,6 +942,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/README b/README
index 71e8735..ac30d6a 100644
--- a/README
+++ b/README
@@ -57,6 +57,7 @@
* pciutils / libpci
* pciutils-devel / pciutils-dev / libpci-dev
* zlib-devel / zlib1g-dev (needed if libpci was compiled with libz support)
+ * libgpiod-dev (if you want support for Linux GPIO devices)
On FreeBSD, you need the following ports:
diff --git a/flashrom.8.tmpl b/flashrom.8.tmpl
index 87a9587..fd5fc2a 100644
--- a/flashrom.8.tmpl
+++ b/flashrom.8.tmpl
@@ -331,6 +331,8 @@
.sp
.BR "* ogp_spi" " (for SPI flash ROMs on Open Graphics Project graphics card)"
.sp
+.BR "* linux_gpio_spi" " (for SPI flash ROMs attached to a GPIO chip device accessible via /dev/gpiochipX on Linux)"
+.sp
.BR "* linux_mtd" " (for SPI flash ROMs accessible via /dev/mtdX on Linux)"
.sp
.BR "* linux_spi" " (for SPI flash ROMs accessible via /dev/spidevX.Y on Linux)"
@@ -1132,6 +1134,34 @@
.B nic3com et al.\&
section above.
.SS
+.BR "linux_gpio_spi " programmer
+.IP
+Either the GPIO device node or the chip number as well as the GPIO numbers
+of the SPI lines must be specified like in the following examples:
+.sp
+.B " flashrom \-p linux_gpio_spi:dev=/dev/gpiochip0,cs=8,sck=11,mosi=10,miso=9"
+.sp
+or
+.sp
+.B " flashrom \-p linux_gpio_spi:gpiochip=0,cs=8,sck=11,mosi=10,miso=9"
+.sp
+Here,
+.B gpiochip=0
+selects the GPIO chip 0, accessible through Linux device node /dev/gpiochip0, and the
+.B cs, sck, mosi, miso
+arguments select the GPIO numbers used as SPI lines connected to the flash ROM chip. In this example
+the GPIO numbers of the hardware SPI lines of of a Raspberry Pi single board computer are specified.
+All programmer arguments are mandatory.
+Note that this is a bitbanged driver, and if your device has a hardware SPI controller, use the
+.B linux_spi
+programmer driver instead for better performance.
+.sp
+Refer to the output of the
+.B gpioinfo
+utility to make sure the GPIO numbers are correct and unused.
+.sp
+Please note that the linux_gpio_spi driver only works on Linux, and depends on libgpiod.
+.SS
.BR "linux_mtd " programmer
.IP
You may specify the MTD device to use with the
@@ -1584,6 +1614,8 @@
.br
Stephan Guilloux
.br
+Steve Markgraf
+.br
Steven James
.br
Urja Rannikko
diff --git a/include/programmer.h b/include/programmer.h
index b0eac19..6798851 100644
--- a/include/programmer.h
+++ b/include/programmer.h
@@ -76,6 +76,7 @@
extern const struct programmer_entry programmer_internal;
extern const struct programmer_entry programmer_it8212;
extern const struct programmer_entry programmer_jlink_spi;
+extern const struct programmer_entry programmer_linux_gpio_spi;
extern const struct programmer_entry programmer_linux_mtd;
extern const struct programmer_entry programmer_linux_spi;
extern const struct programmer_entry programmer_mstarddc_spi;
diff --git a/linux_gpio_spi.c b/linux_gpio_spi.c
new file mode 100644
index 0000000..0103d19
--- /dev/null
+++ b/linux_gpio_spi.c
@@ -0,0 +1,183 @@
+/*
+ * 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 <limits.h>
+#include <errno.h>
+#include <gpiod.h>
+#include "programmer.h"
+#include "spi.h"
+#include "flash.h"
+
+#define CONSUMER "flashrom"
+
+struct linux_gpio_spi {
+ struct gpiod_chip *chip;
+ struct gpiod_line_bulk bulk;
+ struct gpiod_line *cs_line, *sck_line, *mosi_line, *miso_line;
+};
+
+static void linux_gpio_spi_bitbang_set_cs(int val, void *spi_data)
+{
+ struct linux_gpio_spi *data = spi_data;
+ if (gpiod_line_set_value(data->cs_line, val) < 0)
+ msg_perr("Setting cs line failed\n");
+}
+
+static void linux_gpio_spi_bitbang_set_sck(int val, void *spi_data)
+{
+ struct linux_gpio_spi *data = spi_data;
+ if (gpiod_line_set_value(data->sck_line, val) < 0)
+ msg_perr("Setting sck line failed\n");
+}
+
+static void linux_gpio_spi_bitbang_set_mosi(int val, void *spi_data)
+{
+ struct linux_gpio_spi *data = spi_data;
+ if (gpiod_line_set_value(data->mosi_line, val) < 0)
+ msg_perr("Setting sck line failed\n");
+}
+
+static int linux_gpio_spi_bitbang_get_miso(void *spi_data)
+{
+ struct linux_gpio_spi *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_gpio_spi_bitbang_set_cs,
+ .set_sck = linux_gpio_spi_bitbang_set_sck,
+ .set_mosi = linux_gpio_spi_bitbang_set_mosi,
+ .get_miso = linux_gpio_spi_bitbang_get_miso,
+};
+
+static int linux_gpio_spi_shutdown(void *spi_data)
+{
+ struct linux_gpio_spi *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_gpio_spi_init(void)
+{
+ struct linux_gpio_spi *data = NULL;
+ struct gpiod_chip *chip = NULL;
+ const char *param_str[] = { "cs", "sck", "mosi", "miso", "gpiochip" };
+ const bool param_required[] = { true, true, true, true, false };
+ unsigned int param_int[ARRAY_SIZE(param_str)];
+ unsigned int i;
+ int r;
+
+ data = calloc(1, sizeof(*data));
+ if (!data) {
+ msg_perr("Unable to allocate space for SPI master data\n");
+ return 1;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(param_str); i++) {
+ char *param = extract_programmer_param(param_str[i]);
+ char *endptr;
+ r = 1;
+
+ if (param) {
+ errno = 0;
+ param_int[i] = strtoul(param, &endptr, 10);
+ r = (*endptr != '\0') || (errno != 0);
+ free(param);
+ } else {
+ param_int[i] = UINT_MAX;
+ }
+
+ if ((param_required[i] || param) && r) {
+ msg_perr("Missing or invalid required programmer "
+ "parameter %s=<n>\n", param_str[i]);
+ goto err_exit;
+ }
+ }
+
+ char *const dev = extract_programmer_param("dev");
+ if (!dev && param_int[4] == UINT_MAX) {
+ msg_perr("Either a 'dev' or 'gpiochip' parameter must be specified.\n");
+ goto err_exit;
+ }
+ if (dev && param_int[4] != UINT_MAX) {
+ msg_perr("Only one of 'dev' or 'gpiochip' parameters can be specified.\n");
+ free(dev);
+ goto err_exit;
+ }
+
+ if (dev) {
+ chip = gpiod_chip_open(dev);
+ free(dev);
+ } else {
+ 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_gpio_spi_shutdown, data))
+ goto err_exit;
+
+ /* shutdown function does the cleanup */
+ return register_spi_bitbang_master(&bitbang_spi_master_gpiod, data);
+
+err_exit:
+ linux_gpio_spi_shutdown(data);
+ return 1;
+}
+
+const struct programmer_entry programmer_linux_gpio_spi = {
+ .name = "linux_gpio_spi",
+ .type = OTHER,
+ .devs.note = "Device file /dev/gpiochip<n>\n",
+ .init = linux_gpio_spi_init,
+};
diff --git a/meson.build b/meson.build
index cd98e5b..3090808 100644
--- a/meson.build
+++ b/meson.build
@@ -109,6 +109,7 @@
group_i2c = get_option('programmer').contains('group_i2c')
group_serial = get_option('programmer').contains('group_serial')
group_jlink = get_option('programmer').contains('group_jlink')
+group_gpiod = get_option('programmer').contains('group_gpiod')
group_internal = get_option('programmer').contains('group_internal')
group_external = get_option('programmer').contains('group_external')
@@ -116,6 +117,7 @@
libusb1 = dependency('libusb-1.0', required : group_usb)
libftdi1 = dependency('libftdi1', required : group_ftdi)
libjaylink = dependency('libjaylink', required : group_jlink)
+libgpiod = dependency('libgpiod', required : group_gpiod)
subdir('platform')
@@ -284,6 +286,13 @@
'flags' : [ '-DCONFIG_JLINK_SPI=1' ],
'default' : false,
},
+ 'linux_gpio_spi' : {
+ 'systems' : [ 'linux' ],
+ 'deps' : [ libgpiod ],
+ 'groups' : [ group_gpiod, group_external ],
+ 'srcs' : files('linux_gpio_spi.c'),
+ 'flags' : [ '-DCONFIG_LINUX_GPIOD=1' ],
+ },
'linux_mtd' : {
'systems' : [ 'linux' ],
'deps' : [ linux_headers ],
diff --git a/meson_options.txt b/meson_options.txt
index 2d7f5a6..616a9f4 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -10,8 +10,8 @@
'group_ftdi', 'group_i2c', 'group_jlink', 'group_pci', 'group_serial', 'group_usb',
'atahpt', 'atapromise', 'atavia', 'buspirate_spi', 'ch341a_spi', 'ch347_spi', 'dediprog', 'developerbox_spi',
'digilent_spi', 'dirtyjtag_spi', 'drkaiser', 'dummy', 'ft2232_spi', 'gfxnvidia', 'internal', 'it8212',
- 'jlink_spi', 'linux_mtd', 'linux_spi', 'mediatek_i2c_spi', 'mstarddc_spi', 'nic3com', 'nicintel',
- 'nicintel_eeprom', 'nicintel_spi', 'nicnatsemi', 'nicrealtek', 'ogp_spi', 'parade_lspcon',
+ 'jlink_spi', 'linux_gpio_spi', 'linux_mtd', 'linux_spi', 'mediatek_i2c_spi', 'mstarddc_spi', 'nic3com',
+ 'nicintel', 'nicintel_eeprom', 'nicintel_spi', 'nicnatsemi', 'nicrealtek', 'ogp_spi', 'parade_lspcon',
'pickit2_spi', 'pony_spi', 'raiden_debug_spi', 'rayer_spi', 'realtek_mst_i2c_spi', 'satamv',
'satasii', 'serprog', 'stlinkv3_spi', 'usbblaster_spi',
], description: 'Active programmers')
diff --git a/programmer_table.c b/programmer_table.c
index 7df088c..5e716eb 100644
--- a/programmer_table.c
+++ b/programmer_table.c
@@ -124,6 +124,10 @@
&programmer_satamv,
#endif
+#if CONFIG_LINUX_GPIOD == 1
+ &programmer_linux_gpio_spi,
+#endif
+
#if CONFIG_LINUX_MTD == 1
&programmer_linux_mtd,
#endif
diff --git a/util/manibuilder/Dockerfile.alpine b/util/manibuilder/Dockerfile.alpine
index 58d89c8..7f738e1 100644
--- a/util/manibuilder/Dockerfile.alpine
+++ b/util/manibuilder/Dockerfile.alpine
@@ -8,6 +8,8 @@
apk add ca-certificates build-base linux-headers git ccache \
pciutils-dev libusb-compat-dev libusb-dev
+RUN apk add libgpiod-dev || true
+
# fix weird permissions in armhf-v3.11
RUN [ -d /usr/share/git-core/templates ] && \
chmod -R a+r /usr/share/git-core/templates
diff --git a/util/manibuilder/Dockerfile.debian-debootstrap b/util/manibuilder/Dockerfile.debian-debootstrap
index e363fd2..3f5f0cb 100644
--- a/util/manibuilder/Dockerfile.debian-debootstrap
+++ b/util/manibuilder/Dockerfile.debian-debootstrap
@@ -6,7 +6,7 @@
apt-get -qq upgrade && \
apt-get -qqy install gcc make git doxygen ccache pkg-config \
libpci-dev libusb-dev libftdi-dev libusb-1.0-0-dev && \
- { apt-get -qqy install libjaylink-dev || true; } && \
+ { apt-get -qqy install libjaylink-dev libgpiod-dev || true; } && \
apt-get clean
ENV GIT_SSL_NO_VERIFY=1
diff --git a/util/manibuilder/Dockerfile.fedora b/util/manibuilder/Dockerfile.fedora
index 8a22fa8..179c45f 100644
--- a/util/manibuilder/Dockerfile.fedora
+++ b/util/manibuilder/Dockerfile.fedora
@@ -5,6 +5,7 @@
dnf install -q -y ca-certificates git gcc ccache make systemd-devel \
pciutils-devel libusb-devel libusbx-devel libftdi-devel \
libjaylink-devel && \
+ { dnf install -q -y libgpiod-devel || true; } && \
dnf clean -q -y all
ENV GIT_SSL_NO_VERIFY=1
diff --git a/util/manibuilder/Dockerfile.ubuntu-debootstrap b/util/manibuilder/Dockerfile.ubuntu-debootstrap
index f1088c5..c30a672 100644
--- a/util/manibuilder/Dockerfile.ubuntu-debootstrap
+++ b/util/manibuilder/Dockerfile.ubuntu-debootstrap
@@ -19,7 +19,7 @@
apt-get -qq upgrade && \
apt-get -qqy install gcc make git doxygen ccache pkg-config \
libpci-dev libusb-dev libftdi-dev libusb-1.0-0-dev && \
- { apt-get -qqy install libjaylink-dev || true; } && \
+ { apt-get -qqy install libjaylink-dev libgpiod-dev || true; } && \
apt-get clean
ENV GIT_SSL_NO_VERIFY=1
diff --git a/util/shell.nix b/util/shell.nix
index c870ebc..3c983fe 100644
--- a/util/shell.nix
+++ b/util/shell.nix
@@ -9,6 +9,7 @@
libftdi1
libjaylink
libusb1
+ libgpiod
meson
ninja
pciutils