Hi everyone,

I have spent the past few days implementing support for the AMD Am79C971, in the family of AMD Lance network controllers. Please see attached source code.

So far, testing with SST39SF020A memory has shown positive results for reading flash memory. However, erasure and writing fails now and then, often with just one or two bytes being erroneous. Sometimes it fails with “magic data” being written, sometimes with random data at random locations. Please see the attached log for a full example failure.

"Verifying flash... FAILED at 0x00005555! Expected=0xef, Found=0xaa, failed byte count from 0x00000000-0x0003ffff: 0x1" "Verifying flash... FAILED at 0x000008b9! Expected=0x00, Found=0xb9, failed byte count from 0x00000000-0x0003ffff: 0x1" "Verifying flash... FAILED at 0x0001fd86! Expected=0x86, Found=0x85, failed byte count from 0x00000000-0x0003ffff: 0x1"

I have never worked with PCI devices nor flash memory before, and I would really appreciate some ideas of where to start poking around to fix the erasure and writing issues. Also, comments on the code is highly appreciated!

The datasheet for the network controller can be found here: https://datasheet.octopart.com/AM79C971AKC-W-AMD-datasheet-5389196.pdf

Kind regards,
Alexander Lundén


flashrom v1.0 on Linux 4.17.3-1-ARCH (x86_64)
flashrom was built with libpci 3.2.1, GCC 4.9.2, little endian
Command line (9 args): ./flashrom -VVV -p nicamd -c SST39SF020A -w 
../rom/pattern.bin -o ../log/20180729/nicamd-rc2-write5.log
Using clock_gettime for delay loops (clk_id: 1, resolution: 1ns).
Initializing nicamd programmer
Found "AMD PCNET32: Am79C971 PCI 10/100 Mbps" (1022:2000, BDF 07:04.0).
===
This PCI device is UNTESTED. Please report the 'flashrom -p xxxx' output 
to [email protected] if it works for you. Please add the name of your
PCI device to the subject. Thank you for your help!
===
PCI header type 0x00
Requested BAR is of type I/O
PCI header type 0x00
Requested BAR is of type I/O
[nicamd] Enabling/verifying 32-bit I/O.
[nicamd] Entering STOP mode.
The following protocols are supported: Parallel.
Probing for SST SST39SF020A, 256 kB: programmer_map_flash_region: mapping 
SST39SF020A from 0x00000000fffc0000 to 0x0000000000000000
probe_jedec_common: id1 0xbf, id2 0xb6
Found SST flash chip "SST39SF020A" (256 kB, Parallel) on nicamd.
programmer_unmap_flash_region: unmapped 0x0000000000000000
programmer_map_flash_region: mapping SST39SF020A from 0x00000000fffc0000 to 
0x0000000000000000
Reading old flash chip contents... done.
Erasing and writing flash chip... Trying erase function 0... 
0x000000-0x000fff:EW, 0x001000-0x001fff:EW, 0x002000-0x002fff:EW, 
0x003000-0x003fff:EW, 0x004000-0x004fff:EW, 0x005000-0x005fff:EW, 
0x006000-0x006fff:EW, 0x007000-0x007fff:EW, 0x008000-0x008fff:EW, 
0x009000-0x009fff:EW, 0x00a000-0x00afff:EW, 0x00b000-0x00bfff:EW, 
0x00c000-0x00cfff:EW, 0x00d000-0x00dfff:EW, 0x00e000-0x00efff:EW, 
0x00f000-0x00ffff:EW, 0x010000-0x010fff:EW, 0x011000-0x011fff:EW, 
0x012000-0x012fff:EW, 0x013000-0x013fff:EW, 0x014000-0x014fff:EW, 
0x015000-0x015fff:EW, 0x016000-0x016fff:EW, 0x017000-0x017fff:EW, 
0x018000-0x018fff:EW, 0x019000-0x019fff:EW, 0x01a000-0x01afff:EW, 
0x01b000-0x01bfff:EW, 0x01c000-0x01cfff:EW, 0x01d000-0x01dfff:EW, 
0x01e000-0x01efff:EW, 0x01f000-0x01ffff:EW, 0x020000-0x020fff:EW, 
0x021000-0x021fff:EW, 0x022000-0x022fff:EW, 0x023000-0x023fff:EW, 
0x024000-0x024fff:EW, 0x025000-0x025fff:EW, 0x026000-0x026fff:EW, 
0x027000-0x027fff:EW, 0x028000-0x028fff:EW, 0x029000-0x029fff:EW, 
0x02a000-0x02afff:EW, 0x02b000-0x02bfff:EW, 0x02c000-0x02cfff:EW, 
0x02d000-0x02dfff:EW, 0x02e000-0x02efff:EW, 0x02f000-0x02ffff:EW, 
0x030000-0x030fff:EW, 0x031000-0x031fff:EW, 0x032000-0x032fff:EW, 
0x033000-0x033fff:EW, 0x034000-0x034fff:EW, 0x035000-0x035fff:EW, 
0x036000-0x036fff:EW, 0x037000-0x037fff:EW, 0x038000-0x038fff:EW, 
0x039000-0x039fff:EW, 0x03a000-0x03afff:EW, 0x03b000-0x03bfff:EW, 
0x03c000-0x03cfff:EW, 0x03d000-0x03dfff:EW, 0x03e000-0x03efff:EW, 
0x03f000-0x03ffff:EW
Erase/write done.
Verifying flash... FAILED at 0x0001fd86! Expected=0x86, Found=0x85, failed byte 
count from 0x00000000-0x0003ffff: 0x1
Your flash chip is in an unknown state.
Please report this on IRC at chat.freenode.net (channel #flashrom) or
mail [email protected], thanks!
programmer_unmap_flash_region: unmapped 0x0000000000000000
/*
 * This file is part of the flashrom project.
 *
 * Copyright (C) 2018 Alexander Lundén <[email protected]>
 *
 * 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.
 *
 * 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
 */

/**
* nicamd.c - flashrom programmer module for AMD PCnet/Lance network controllers.
*
* ---- Theory of operation ----
* The AMD Am79C971 exposes two ways of accessing its expansion ROM/Flash memory.
* Method 1 maps the ROM into host memory, but only supports read access.
* Method 2, "Direct Flash Access", uses a set of registers to give read and 
* write access to the ROM.
* In either mode the Am79C971 supports up to 1 Mbyte of Flash memory.
* This module uses method 2.
* ([1] pp. 84-89)
*
* Configuration Registers:
* The Am79C971 has three types of configuration registers: PCI Configuration 
* Registers, Control and Status Registers (CSR) and Bus Control Registers (BCR).
* CSR and BCR are used to configure and control the device's operation.
* To select a CSR or BCR, write its number to the Register Address Port (RAP), 
* then read or write to RDP (for CSR) or BDP (for BCR) to access the register.
* ([1] p. 103, pp. 114-180)
*
* I/O modes:
* The Am79C971 offers 16-bit I/O (WIO, legacy) and 32-bit I/O (DWIO).
* WIO and DWIO use different memory offsets to the I/O Base Address.
* One can switch from WIO to DWIO, but not the other way around. Switching from 
* WIO to DWIO is performed by a DWORD write operation to RDP. IO mode is checked
* by reading bit 7 of BCR18 (0 = WIO, 1 = DWIO). Use 32-bit I/O.
* ([1] pp. 104-105)
*
* STOP mode:
* The controller must be in STOP mode to allow access to its expansion ROM.
* STOP mode is activated by setting bit 2 of CSR0.
* ([1] p. 102, p. 117)
*
* Direct Flash Access:
* The Am79C971 offers a direct data path to the flash chip through three BCR's.
* BCR29 (EPADDRU[3:0]) is loaded with the upper 4 bits of the ROM address, and 
* the FLASH bit (bit 15). Then, BCR28 (EPADDRL[15:0]) is loaded with the lower 
* 16 bits of the ROM address. The Flash is accessed by a reading or writing 
* to BCR30 (EBD).
* ([1] p. 86)
* 
* ---- References ----
* [1] Am79C971, PCnet™-FAST Single-Chip Full-Duplex 10/100 Mbps Ethernet 
*     Controller for PCI Local Bus (Publication# 20550, Rev: E, Issued May 2000)
*     https://datasheet.octopart.com/AM79C971AKC-W-AMD-datasheet-5389196.pdf
*
* ---- Glossary ----
* WIO: WORD I/O (16-bit)
* DWIO: DWORD I/O (32-bit)
* RAP: Register Address Port
* CSR: Control and Status Register
* BCR: Bus Control Register
* RDP: Register Data Port (for CSR)
* BDP: Bus register Data Port (for BCR)
* EPADDRU: Expansion Port Address Upper
* EPADDRU: Expansion Port Address Lower
* EBD: Expansion Bus Data port
*/

#if defined(__i386__) || defined(__x86_64__)

#include <stdlib.h>
#include "flash.h"
#include "programmer.h"
#include "hwaccess.h"

#define PCI_VENDOR_ID_AMD   0x1022

/* Supports up to 1 Megabyte (Mbyte) optional
   Boot PROM and Flash for diskless node application. (p.2) */
#define MAX_ROM_SIZE    (1024 * 1024)

/* RAP offsets - 16-bit I/0 (DWIO = 0). (pp. 104-105) */
#define WIO_RAP         0x0012  /* Register Address Port */
#define WIO_RDP         0x0010  /* CSR Data Port */
#define WIO_BDP         0x0016  /* BCR Data Port  */

/* RAP offsets - 32-bit I/0 (DWIO = 1). (p. 106) */
#define DWIO_RAP        0x0014  /* Register Address Port */
#define DWIO_RDP        0x0010  /* CSR Data Port */
#define DWIO_BDP        0x001C  /* BCR Data Port */

/* Control and Status Registers - offsets and bit definitions. (p. 115) */
#define CSR0            0       /* Controller Status and Control Register */
#define CSR0_STOP       0x0004  /* Stop controller */
#define CSR0_START      0x0002  /* Start controller */
#define CSR0_INIT       0x0001  /* Initialize controller */

/* Bus Control Registers - offsets and bit definitions. (p.148) */
#define BCR18           18      /* Burst and Bus Control */
#define BCR28           28      /* Expansion Bus Address Lower (EBADDRL) */
#define BCR29           29      /* Expansion Bus Address Upper (EBADDRU) */
#define BCR30           30      /* Expansion Bus Data Port (EBD) */
#define BCR18_DWIO      0x0080  /* Bit 7 (32-bit I/O) */
#define BCR29_FLASH     0x8000  /* Bit 15 (flash cycle, not SRAM) */

static uint32_t io_base_addr;

const struct dev_entry nics_amd[] = {
    /* 79C971 */
    {PCI_VENDOR_ID_AMD, 0x2000, NT, "AMD", "PCNET32: Am79C971 PCI 10/100 Mbps"},
    {0},
};

static void nicamd_chip_writeb(const struct flashctx *flash, uint8_t val,
    chipaddr addr);
static uint8_t nicamd_chip_readb(const struct flashctx *flash,
    const chipaddr addr);
static const struct par_master par_master_nicamd = {
    .chip_readb     = nicamd_chip_readb,
    .chip_readw     = fallback_chip_readw,
    .chip_readl     = fallback_chip_readl,
    .chip_readn     = fallback_chip_readn,
    .chip_writeb    = nicamd_chip_writeb,
    .chip_writew    = fallback_chip_writew,
    .chip_writel    = fallback_chip_writel,
    .chip_writen    = fallback_chip_writen,
};

/* Functions preparing the controller for ROM access. */
static unsigned int nicamd_enable_32bit_io();
static unsigned int nicamd_enter_stop_mode();

/* Function loading ROM address before read/write. */
static void nicamd_load_rom_addr(uint32_t addr);

/* Functions for reading and writing CSR and BCR. */
static void nicamd_write_rap(uint32_t val);
static uint16_t nicamd_read_csr(uint32_t csr);
static void nicamd_write_csr(uint32_t csr, uint16_t val);
static uint16_t nicamd_read_bcr(uint32_t bcr);
static void nicamd_write_bcr(uint32_t bcr, uint16_t val);

static int nicamd_shutdown(void *data)
{
    /* Start the device again. */
    nicamd_write_csr(CSR0, nicamd_read_csr(CSR0) | CSR0_START);
    return 0;
}

void *nicamd_map(const char *descr, uintptr_t phys_addr, size_t len)
{
    /* In case fallback_map ever returns something other than NULL. */
    return NULL;
}

int nicamd_init(void)
{
    static struct pci_dev *dev = NULL;

    if (rget_io_perms())
    {
        return 1;
    }

    /* Get reference to device. */
    dev = pcidev_init(nics_amd, PCI_BASE_ADDRESS_0);

    if (!dev)
    {
        return 1;
    }

    /* Get I/O base address. */
    io_base_addr = pcidev_readbar(dev, PCI_BASE_ADDRESS_0);

    if (!io_base_addr)
    {    
        return 1;
    }

    if (!nicamd_enable_32bit_io())
    {
        msg_perr("[nicamd] Failed enabling 32-bit I/O.\n");
        return 1;
    }

    if (!nicamd_enter_stop_mode())
    {
        msg_perr("[nicamd] Failed entering STOP mode.\n");
        return 1;
    }

    if (register_shutdown(nicamd_shutdown, NULL))
    {
        return 1;
    }

    max_rom_decode.parallel = MAX_ROM_SIZE;
    register_par_master(&par_master_nicamd, BUS_PARALLEL);

    return 0;
}

static void nicamd_chip_writeb(const struct flashctx *flash, uint8_t val, 
    chipaddr addr)
{
    nicamd_load_rom_addr(addr);

    /* Write byte to Expansion bus Data Port. */
    nicamd_write_bcr(BCR30, val);
}

static uint8_t nicamd_chip_readb(const struct flashctx *flash, 
    const chipaddr addr)
{
    nicamd_load_rom_addr(addr);

    /* Read byte from Expansion bus Data Port. */
    return (uint8_t) (nicamd_read_bcr(BCR30) & 0xFF);
}

static void nicamd_load_rom_addr(uint32_t addr)
{
    /* Set FLASH bit and load BCR29[3:0] with upper 4 bits of address. */
    nicamd_write_bcr(BCR29, ((addr & 0x000F0000) >> 16) | BCR29_FLASH);

    /* Load BCR28[15:0] with lower 16 bits of address. */
    nicamd_write_bcr(BCR28, addr & 0xFFFF);
}

static unsigned int nicamd_enable_32bit_io()
{
    /* 
     * Switch to/verify 32-bit I/O mode.
     * Assume we're in 16-bit mode. Write to WIO_RAP, read back, and 
     * compare to original value. If they're equal, enable 32-bit mode.
     * If we read back garble, we're probably already in 32-bit mode.
     */

    msg_pdbg("[nicamd] Enabling/verifying 32-bit I/O.\n");
    OUTW(88, io_base_addr + WIO_RAP);

    if (INW(io_base_addr + WIO_RAP) == 88)
    {
        /* We're probably in 16-bit mode. Switch to 32-bit. */
        msg_pdbg("[nicamd] Probably in 16-bit mode. Switching to 32-bit.\n");

        /* Select CSR0. */
        OUTW(CSR0, io_base_addr + WIO_RAP);

        /* Switch to 32-bit I/O by writing a DWORD to RDP. */
        OUTL(CSR0_INIT, io_base_addr + WIO_RDP);
    }

    /* 
     * Either we were already in 32-bit mode, or we just entered 32-bit mode.
     * Verify by reading bit 7 (DWIO) from BCR18.
     */

    return (nicamd_read_bcr(BCR18) & BCR18_DWIO) == BCR18_DWIO;
}

static unsigned int nicamd_enter_stop_mode()
{
    /* Set STOP bit in CSR0, read back and verify. */
    msg_pdbg("[nicamd] Entering STOP mode.\n");
    nicamd_write_csr(CSR0, nicamd_read_csr(CSR0) | CSR0_STOP);
    return (nicamd_read_csr(CSR0) & CSR0_STOP) == CSR0_STOP;
}

static void nicamd_write_rap(uint32_t reg)
{
    /* Select a specified CSR or BCR. */
    OUTL(reg, io_base_addr + DWIO_RAP);
}

static uint16_t nicamd_read_csr(uint32_t csr)
{
    /* Select specified CSR. */
    nicamd_write_rap(csr);

    /* Read CSR data from RDP. */
    return INL(io_base_addr + DWIO_RDP) & 0xFFFF;
}

static void nicamd_write_csr(uint32_t csr, uint16_t val)
{
    /* Select specified CSR. */
    nicamd_write_rap(csr);

    /* Write to CSR through RDP. */
    OUTL(val & 0x0000FFFF, io_base_addr + DWIO_RDP);
}

static uint16_t nicamd_read_bcr(uint32_t bcr)
{
    /* Select specified BCR. */
    nicamd_write_rap(bcr);

    /* Read CSR data from BDP. */
    return INL(io_base_addr + DWIO_BDP) & 0xFFFF;
}

static void nicamd_write_bcr(uint32_t bcr, uint16_t val)
{
    /* Select specified CSR. */
    nicamd_write_rap(bcr);

    /* Write to CSR through BDP. */
    OUTL(val & 0x0000FFFF, io_base_addr + DWIO_BDP);
}

#else
#error PCI port I/O access is not supported on this architecture yet.
#endif
_______________________________________________
flashrom mailing list
[email protected]
https://mail.coreboot.org/mailman/listinfo/flashrom

Reply via email to