| Nico Huber | dd4744e | 2026-03-22 15:47:25 +0100 | [diff] [blame] | 1 | /* |
| 2 | * This file is part of the flashprog project. |
| 3 | * |
| 4 | * Copyright (C) 2024 Nico Huber <nico.h@gmx.de> |
| 5 | * |
| 6 | * This program is free software; you can redistribute it and/or modify |
| 7 | * it under the terms of the GNU General Public License as published by |
| 8 | * the Free Software Foundation; either version 2 of the License, or |
| 9 | * (at your option) any later version. |
| 10 | * |
| 11 | * This program is distributed in the hope that it will be useful, |
| 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 14 | * GNU General Public License for more details. |
| 15 | */ |
| 16 | |
| 17 | #include <stdint.h> |
| 18 | #include <stdlib.h> |
| 19 | #include <flash.h> |
| 20 | #include <hwaccess_x86_io.h> |
| 21 | #include <hwaccess_physmap.h> |
| 22 | #include <platform/pci.h> |
| 23 | #include <programmer.h> |
| 24 | |
| 25 | #define PCI_VENDOR_ID_AMD 0x1022 |
| 26 | |
| 27 | static const struct dev_entry nics_amd[] = { |
| 28 | {PCI_VENDOR_ID_AMD, 0x2000, OK, "AMD", "79C97x [PCnet32 LANCE]"}, |
| 29 | |
| 30 | {0}, |
| 31 | }; |
| 32 | |
| 33 | static uint8_t readb(const struct par_master *, chipaddr); |
| 34 | static void writeb(const struct par_master *, uint8_t val, chipaddr); |
| 35 | static int shutdown(void *); |
| 36 | |
| 37 | static const struct par_master par_master = { |
| 38 | .chip_readb = readb, |
| 39 | .chip_readw = fallback_chip_readw, |
| 40 | .chip_readl = fallback_chip_readl, |
| 41 | .chip_readn = fallback_chip_readn, |
| 42 | .chip_writeb = writeb, |
| 43 | .chip_writew = fallback_chip_writew, |
| 44 | .chip_writel = fallback_chip_writel, |
| 45 | .chip_writen = fallback_chip_writen, |
| 46 | .shutdown = shutdown, |
| 47 | }; |
| 48 | |
| 49 | #define ROM_BAR_SIZE (1*MiB) |
| 50 | |
| 51 | /* ----- i/o space ----- */ |
| 52 | |
| 53 | #define ANY_IO_RDP 0x10 /* Register Data Port */ |
| 54 | #define WORD_IO_RAP 0x12 |
| 55 | #define WORD_IO_RESET 0x14 |
| 56 | #define WORD_IO_BDP 0x16 |
| 57 | |
| 58 | #define DWORD_IO_RAP 0x14 /* Register Address Port */ |
| 59 | #define DWORD_IO_RESET 0x18 |
| 60 | #define DWORD_IO_BDP 0x1c /* BCR (Bus Configuration Register) Data Port */ |
| 61 | |
| 62 | /* ----- BCR space ----- */ |
| 63 | |
| 64 | #define EXP_BUS_ADDR_LOWER 28 |
| 65 | #define EXP_BUS_ADDR_UPPER 29 |
| 66 | #define EBADDRU_FLASH (1 << 15) |
| 67 | #define EXP_BUS_DATA_PORT 30 |
| 68 | |
| 69 | /* all BCR registers are 16-bit wide */ |
| 70 | |
| 71 | static uint16_t bcr_read16(unsigned int io_base, unsigned int reg) |
| 72 | { |
| 73 | OUTL(reg, io_base + DWORD_IO_RAP); |
| 74 | return INL(io_base + DWORD_IO_BDP); |
| 75 | } |
| 76 | |
| 77 | static void bcr_write16(unsigned int io_base, unsigned int reg, uint16_t val) |
| 78 | { |
| 79 | OUTL(reg, io_base + DWORD_IO_RAP); |
| 80 | OUTL(val, io_base + DWORD_IO_BDP); |
| 81 | } |
| 82 | |
| 83 | static int init(struct flashprog_programmer *const prog) |
| 84 | { |
| 85 | size_t max_decode = 0; |
| 86 | char *const decode_override = extract_programmer_param("max_decode_kb"); |
| 87 | if (decode_override) { |
| 88 | char *endptr; |
| 89 | max_decode = strtoul(decode_override, &endptr, 10); |
| 90 | if (*endptr || max_decode < 16 || max_decode > 1024 || (max_decode & (max_decode - 1))) { |
| 91 | msg_perr("Error: Invalid ROM decode override: \"%s\".\n" |
| 92 | "Valid values are powers of 2 from 16 through 1024 (KiB).\n", |
| 93 | decode_override); |
| 94 | free(decode_override); |
| 95 | return 1; |
| 96 | } |
| 97 | max_decode *= KiB; |
| 98 | } |
| 99 | free(decode_override); |
| 100 | |
| 101 | if (rget_io_perms()) |
| 102 | return 1; |
| 103 | |
| 104 | struct pci_dev *const dev = pcidev_init(nics_amd, PCI_BASE_ADDRESS_0); |
| 105 | if (!dev) |
| 106 | return 1; |
| 107 | |
| 108 | uintptr_t io_base = pcidev_readbar(dev, PCI_BASE_ADDRESS_0); |
| 109 | if (!io_base) |
| 110 | return 1; |
| 111 | |
| 112 | if (!max_decode) { |
| 113 | msg_pinfo("Trying to guess addressable size based on read contents and patterns. If this\n" |
| 114 | "doesn't work, you can override probing with `-p nicamd:max_decode_kb=<size>`.\n"); |
| 115 | |
| 116 | const uint16_t command = pci_read_word(dev, PCI_COMMAND); |
| 117 | if (!(command & PCI_COMMAND_MEMORY)) { |
| 118 | msg_perr("Error: PCI memory access is disabled.\n"); |
| 119 | return 1; |
| 120 | } |
| 121 | |
| 122 | const uint32_t rom_base = pci_read_long(dev, PCI_ROM_ADDRESS); |
| 123 | if (!rom_base) { |
| 124 | msg_perr("Error: PCI ROM base is not configured.\n"); |
| 125 | return 1; |
| 126 | } |
| 127 | |
| 128 | void *physmap(const char *descr, uintptr_t phys_addr, size_t len); |
| 129 | void *const rom = physmap("ROM BAR", rom_base & ~1u, ROM_BAR_SIZE); |
| 130 | if (rom == ERROR_PTR) |
| 131 | return 1; |
| 132 | |
| 133 | if (!(rom_base & 1u)) |
| 134 | pci_write_long(dev, PCI_ROM_ADDRESS, rom_base | 1); |
| 135 | |
| 136 | max_decode = estimate_addressable_size(rom, ROM_BAR_SIZE); |
| 137 | |
| 138 | if (!(rom_base & 1u)) |
| 139 | pci_write_long(dev, PCI_ROM_ADDRESS, rom_base); |
| 140 | |
| 141 | physunmap(rom, ROM_BAR_SIZE); |
| 142 | |
| 143 | if (!max_decode) { |
| 144 | max_decode = 256*KiB; |
| 145 | msg_pwarn("Estimating addressable size failed. Using default of %zuKiB.\n", |
| 146 | max_decode / KiB); |
| 147 | } |
| 148 | } |
| 149 | |
| 150 | /* |
| 151 | * Now comes the fun part: This controller knows two i/o modes, |
| 152 | * word and dword. Writing with the wrong width is illegal, and |
| 153 | * it's impossible to tell the current mode without writing. |
| 154 | * |
| 155 | * The datasheet was reasoned with and we concluded: |
| 156 | * * A soft reset can be performed with read requests only. |
| 157 | * * Trying a dword-i/o reset in word i/o mode is only a read |
| 158 | * from a reserved register and shouldn't hurt. |
| 159 | * * Trying a word-i/o reset in dword i/o mode is only a read |
| 160 | * with the wrong length. |
| 161 | * * After reset we still don't know the mode (it is decided |
| 162 | * by EEPROM bits), though we can switch to dword i/o mode, |
| 163 | * blindly. |
| 164 | */ |
| 165 | INW(io_base + WORD_IO_RESET); |
| 166 | INL(io_base + DWORD_IO_RESET); |
| 167 | OUTL(0, io_base + ANY_IO_RDP); /* supposed to enable dword i/o mode */ |
| 168 | |
| 169 | OUTL(18, io_base + DWORD_IO_RAP); |
| 170 | msg_pdbg2("BBCR: 0x%04x\n", INL(io_base + DWORD_IO_BDP) & 0xffff); |
| 171 | OUTL(0x98e1, io_base + DWORD_IO_BDP); |
| 172 | |
| 173 | msg_pdbg2("BCR25: 0x%04x\n", bcr_read16(io_base, 25)); |
| 174 | bcr_write16(io_base, 25, 0); |
| 175 | |
| 176 | return register_par_master(&par_master, BUS_PARALLEL, 0, max_decode, (void *)io_base); |
| 177 | } |
| 178 | |
| 179 | static uint8_t readb(const struct par_master *par, chipaddr addr) |
| 180 | { |
| 181 | uintptr_t io_base = (uintptr_t)par->data; |
| 182 | |
| 183 | bcr_write16(io_base, EXP_BUS_ADDR_UPPER, EBADDRU_FLASH | addr >> 16); |
| 184 | bcr_write16(io_base, EXP_BUS_ADDR_LOWER, addr); |
| 185 | return bcr_read16(io_base, EXP_BUS_DATA_PORT); |
| 186 | } |
| 187 | |
| 188 | static void writeb(const struct par_master *par, uint8_t val, chipaddr addr) |
| 189 | { |
| 190 | uintptr_t io_base = (uintptr_t)par->data; |
| 191 | |
| 192 | bcr_write16(io_base, EXP_BUS_ADDR_UPPER, EBADDRU_FLASH | addr >> 16); |
| 193 | bcr_write16(io_base, EXP_BUS_ADDR_LOWER, addr); |
| 194 | bcr_write16(io_base, EXP_BUS_DATA_PORT, val); |
| 195 | } |
| 196 | |
| 197 | static int shutdown(void *data) |
| 198 | { |
| 199 | uintptr_t io_base = (uintptr_t)data; |
| 200 | |
| 201 | /* Reset just in case. */ |
| 202 | INL(io_base + DWORD_IO_RESET); |
| 203 | |
| 204 | return 0; |
| 205 | } |
| 206 | |
| 207 | const struct programmer_entry programmer_nicamd = { |
| 208 | .name = "nicamd", |
| 209 | .type = PCI, |
| 210 | .devs.dev = nics_amd, |
| 211 | .init = init, |
| 212 | }; |