nicamd: Introduce new programmer driver for AMD NICs
On a PCnet32 FAST card with a nice PLCC32 socket, only 18 of the 20 ad-
dress lines were connected. It's not easy to detect this, but we'll do
our best: Check the ROM contents for repeating patterns, and allow the
user to override the guessed size. We use the ROM BAR here, though not
for the actual flash reading / writing. According to the datasheet the
ROM BAR interface always reads four bytes, and doesn't seem to support
writes.
Otherwise tested successfully with the first 256KiB of an SST39VF040.
Datasheet used:
https://www.amd.com/content/dam/amd/en/documents/archived-tech-docs/datasheets/20550.pdf
Change-Id: I3bddfcd1e242d28790a0916197ebd8b8bba5a70a
Signed-off-by: Nico Huber <nico.h@gmx.de>
Reviewed-on: https://review.sourcearcade.org/c/flashprog/+/530
Reviewed-by: Arthur Heymans <arthur@aheymans.xyz>
diff --git a/nicamd.c b/nicamd.c
new file mode 100644
index 0000000..b447a94
--- /dev/null
+++ b/nicamd.c
@@ -0,0 +1,212 @@
+/*
+ * This file is part of the flashprog project.
+ *
+ * Copyright (C) 2024 Nico Huber <nico.h@gmx.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 <stdint.h>
+#include <stdlib.h>
+#include <flash.h>
+#include <hwaccess_x86_io.h>
+#include <hwaccess_physmap.h>
+#include <platform/pci.h>
+#include <programmer.h>
+
+#define PCI_VENDOR_ID_AMD 0x1022
+
+static const struct dev_entry nics_amd[] = {
+ {PCI_VENDOR_ID_AMD, 0x2000, OK, "AMD", "79C97x [PCnet32 LANCE]"},
+
+ {0},
+};
+
+static uint8_t readb(const struct par_master *, chipaddr);
+static void writeb(const struct par_master *, uint8_t val, chipaddr);
+static int shutdown(void *);
+
+static const struct par_master par_master = {
+ .chip_readb = readb,
+ .chip_readw = fallback_chip_readw,
+ .chip_readl = fallback_chip_readl,
+ .chip_readn = fallback_chip_readn,
+ .chip_writeb = writeb,
+ .chip_writew = fallback_chip_writew,
+ .chip_writel = fallback_chip_writel,
+ .chip_writen = fallback_chip_writen,
+ .shutdown = shutdown,
+};
+
+#define ROM_BAR_SIZE (1*MiB)
+
+/* ----- i/o space ----- */
+
+#define ANY_IO_RDP 0x10 /* Register Data Port */
+#define WORD_IO_RAP 0x12
+#define WORD_IO_RESET 0x14
+#define WORD_IO_BDP 0x16
+
+#define DWORD_IO_RAP 0x14 /* Register Address Port */
+#define DWORD_IO_RESET 0x18
+#define DWORD_IO_BDP 0x1c /* BCR (Bus Configuration Register) Data Port */
+
+/* ----- BCR space ----- */
+
+#define EXP_BUS_ADDR_LOWER 28
+#define EXP_BUS_ADDR_UPPER 29
+#define EBADDRU_FLASH (1 << 15)
+#define EXP_BUS_DATA_PORT 30
+
+/* all BCR registers are 16-bit wide */
+
+static uint16_t bcr_read16(unsigned int io_base, unsigned int reg)
+{
+ OUTL(reg, io_base + DWORD_IO_RAP);
+ return INL(io_base + DWORD_IO_BDP);
+}
+
+static void bcr_write16(unsigned int io_base, unsigned int reg, uint16_t val)
+{
+ OUTL(reg, io_base + DWORD_IO_RAP);
+ OUTL(val, io_base + DWORD_IO_BDP);
+}
+
+static int init(struct flashprog_programmer *const prog)
+{
+ size_t max_decode = 0;
+ char *const decode_override = extract_programmer_param("max_decode_kb");
+ if (decode_override) {
+ char *endptr;
+ max_decode = strtoul(decode_override, &endptr, 10);
+ if (*endptr || max_decode < 16 || max_decode > 1024 || (max_decode & (max_decode - 1))) {
+ msg_perr("Error: Invalid ROM decode override: \"%s\".\n"
+ "Valid values are powers of 2 from 16 through 1024 (KiB).\n",
+ decode_override);
+ free(decode_override);
+ return 1;
+ }
+ max_decode *= KiB;
+ }
+ free(decode_override);
+
+ if (rget_io_perms())
+ return 1;
+
+ struct pci_dev *const dev = pcidev_init(nics_amd, PCI_BASE_ADDRESS_0);
+ if (!dev)
+ return 1;
+
+ uintptr_t io_base = pcidev_readbar(dev, PCI_BASE_ADDRESS_0);
+ if (!io_base)
+ return 1;
+
+ if (!max_decode) {
+ msg_pinfo("Trying to guess addressable size based on read contents and patterns. If this\n"
+ "doesn't work, you can override probing with `-p nicamd:max_decode_kb=<size>`.\n");
+
+ const uint16_t command = pci_read_word(dev, PCI_COMMAND);
+ if (!(command & PCI_COMMAND_MEMORY)) {
+ msg_perr("Error: PCI memory access is disabled.\n");
+ return 1;
+ }
+
+ const uint32_t rom_base = pci_read_long(dev, PCI_ROM_ADDRESS);
+ if (!rom_base) {
+ msg_perr("Error: PCI ROM base is not configured.\n");
+ return 1;
+ }
+
+ void *physmap(const char *descr, uintptr_t phys_addr, size_t len);
+ void *const rom = physmap("ROM BAR", rom_base & ~1u, ROM_BAR_SIZE);
+ if (rom == ERROR_PTR)
+ return 1;
+
+ if (!(rom_base & 1u))
+ pci_write_long(dev, PCI_ROM_ADDRESS, rom_base | 1);
+
+ max_decode = estimate_addressable_size(rom, ROM_BAR_SIZE);
+
+ if (!(rom_base & 1u))
+ pci_write_long(dev, PCI_ROM_ADDRESS, rom_base);
+
+ physunmap(rom, ROM_BAR_SIZE);
+
+ if (!max_decode) {
+ max_decode = 256*KiB;
+ msg_pwarn("Estimating addressable size failed. Using default of %zuKiB.\n",
+ max_decode / KiB);
+ }
+ }
+
+ /*
+ * Now comes the fun part: This controller knows two i/o modes,
+ * word and dword. Writing with the wrong width is illegal, and
+ * it's impossible to tell the current mode without writing.
+ *
+ * The datasheet was reasoned with and we concluded:
+ * * A soft reset can be performed with read requests only.
+ * * Trying a dword-i/o reset in word i/o mode is only a read
+ * from a reserved register and shouldn't hurt.
+ * * Trying a word-i/o reset in dword i/o mode is only a read
+ * with the wrong length.
+ * * After reset we still don't know the mode (it is decided
+ * by EEPROM bits), though we can switch to dword i/o mode,
+ * blindly.
+ */
+ INW(io_base + WORD_IO_RESET);
+ INL(io_base + DWORD_IO_RESET);
+ OUTL(0, io_base + ANY_IO_RDP); /* supposed to enable dword i/o mode */
+
+ OUTL(18, io_base + DWORD_IO_RAP);
+ msg_pdbg2("BBCR: 0x%04x\n", INL(io_base + DWORD_IO_BDP) & 0xffff);
+ OUTL(0x98e1, io_base + DWORD_IO_BDP);
+
+ msg_pdbg2("BCR25: 0x%04x\n", bcr_read16(io_base, 25));
+ bcr_write16(io_base, 25, 0);
+
+ return register_par_master(&par_master, BUS_PARALLEL, 0, max_decode, (void *)io_base);
+}
+
+static uint8_t readb(const struct par_master *par, chipaddr addr)
+{
+ uintptr_t io_base = (uintptr_t)par->data;
+
+ bcr_write16(io_base, EXP_BUS_ADDR_UPPER, EBADDRU_FLASH | addr >> 16);
+ bcr_write16(io_base, EXP_BUS_ADDR_LOWER, addr);
+ return bcr_read16(io_base, EXP_BUS_DATA_PORT);
+}
+
+static void writeb(const struct par_master *par, uint8_t val, chipaddr addr)
+{
+ uintptr_t io_base = (uintptr_t)par->data;
+
+ bcr_write16(io_base, EXP_BUS_ADDR_UPPER, EBADDRU_FLASH | addr >> 16);
+ bcr_write16(io_base, EXP_BUS_ADDR_LOWER, addr);
+ bcr_write16(io_base, EXP_BUS_DATA_PORT, val);
+}
+
+static int shutdown(void *data)
+{
+ uintptr_t io_base = (uintptr_t)data;
+
+ /* Reset just in case. */
+ INL(io_base + DWORD_IO_RESET);
+
+ return 0;
+}
+
+const struct programmer_entry programmer_nicamd = {
+ .name = "nicamd",
+ .type = PCI,
+ .devs.dev = nics_amd,
+ .init = init,
+};