Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: whitequark/glasgow
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: c285dfa943ab
Choose a base ref
...
head repository: whitequark/glasgow
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: d8f78b7b03dc
Choose a head ref
  • 2 commits
  • 2 files changed
  • 1 contributor

Commits on Mar 2, 2019

  1. applet.spi_flash_25c: identify: significantly improve robustness.

    Somef flashes return 0x00 for unimplemented commands, some return
    0xff instead. Any commands may be unimplemented, and some commands
    can return conflicting information, so show everything that looks
    valid at all.
    
    Also, show a warning if RES returns an invalid result, since that's
    most likely a device that isn't connected properly.
    whitequark committed Mar 2, 2019
    Copy the full SHA
    8ce3458 View commit details
  2. Copy the full SHA
    d8f78b7 View commit details
Showing with 334 additions and 12 deletions.
  1. +51 −12 software/glasgow/applet/spi_flash_25c/__init__.py
  2. +283 −0 software/glasgow/protocol/sfdp.py
63 changes: 51 additions & 12 deletions software/glasgow/applet/spi_flash_25c/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
# Note: flashes vary in their response to unimplemented commands. Some return 00, some return FF,
# some will tristate SO.

import re
import sys
import struct
@@ -7,6 +10,7 @@
from .. import *
from ..spi_master import SPIMasterApplet
from ...database.jedec import *
from ...protocol.sfdp import *


class SPIFlash25CError(GlasgowAppletError):
@@ -97,6 +101,10 @@ async def fast_read(self, address, length, chunk_size=None,
return await self._read_command(address, length, chunk_size, cmd=0x0B, dummy=1,
callback=callback)

async def read_sfdp(self, address, length):
self._log("read sfdp addr=%#08x len=%d", address, length)
return await self._read_command(address, length, chunk_size=0x100, cmd=0x5A, dummy=1)

async def read_status(self):
status, = await self._command(0x05, ret=1)
self._log("read status=%s", "{:#010b}".format(status))
@@ -194,6 +202,15 @@ async def erase_program(self, address, data, sector_size, page_size,
callback(done, total, None)


class SPIFlash25CSFDPParser(SFDPParser):
async def __init__(self, flash_iface):
self._flash_iface = flash_iface
await super().__init__()

async def read(self, offset, length):
return await self._flash_iface.read_sfdp(offset, length)


class SPIFlash25CApplet(SPIMasterApplet, name="spi-flash-25c"):
logger = logging.getLogger(__name__)
help = "read and write 25C-compatible Flash memories"
@@ -354,23 +371,45 @@ async def interact(self, device, args, flash_iface):
.format((status & MSK_PROT) >> 2))

if args.operation == "identify":
manufacturer_id, device_id = \
legacy_device_id, = \
await flash_iface.read_device_id()
short_manufacturer_id, short_device_id = \
await flash_iface.read_manufacturer_device_id()
long_manufacturer_id, long_device_id = \
await flash_iface.read_manufacturer_long_device_id()
# If a flash does not support command 0x90 (read manufacturer/8-bit device ID),
# fall back to command 0x9F (read manufacturer/16-bit device ID). An example of
# such flash is the flash macro of Lattice iCE40 FPGA series. In response to command
# 0x90, it returns manufacturer ID 0xff, which is not a valid JEDEC ID as it fails
# the parity check.
if manufacturer_id == 0xff or long_manufacturer_id == manufacturer_id:
if short_manufacturer_id not in (0x00, 0xff):
manufacturer_name = jedec_mfg_name_from_bytes([short_manufacturer_id]) or "unknown"
self.logger.info("JEDEC manufacturer %#04x (%s) device %#04x (8-bit ID)",
short_manufacturer_id, manufacturer_name, short_device_id)
if long_manufacturer_id not in (0x00, 0xff):
manufacturer_name = jedec_mfg_name_from_bytes([long_manufacturer_id]) or "unknown"
self.logger.info("JEDEC manufacturer %#04x (%s) device %#04x",
self.logger.info("JEDEC manufacturer %#04x (%s) device %#06x (16-bit ID)",
long_manufacturer_id, manufacturer_name, long_device_id)
else:
manufacturer_name = jedec_mfg_name_from_bytes([manufacturer_id]) or "unknown"
self.logger.info("JEDEC manufacturer %#04x (%s) device %#04x",
manufacturer_id, manufacturer_name, device_id)
if short_manufacturer_id in (0x00, 0xff) and long_manufacturer_id in (0x00, 0xff):
if legacy_device_id in (0x00, 0xff):
self.logger.warn("no electronic signature detected; device not present?")
else:
self.logger.info("device lacks JEDEC manufacturer/device ID")
self.logger.info("electronic signature %#04x",
legacy_device_id)

try:
sfdp = await SPIFlash25CSFDPParser(flash_iface)
self.logger.info("device has valid SFDP %d.%d (%s) descriptor",
*sfdp.version, sfdp.jedec_revision)
for index, table in enumerate(sfdp):
if table.vendor_id == 0x00: # JEDEC
self.logger.info(" SFDP table #%d: %s %d.%d (%s)",
index, table, *table.version, table.jedec_revision)
else:
self.logger.info(" SFDP table #%d: %s %d.%d",
index, table, *table.version)
if any(table):
key_width = max(len(k) for k, v in table) + 1
for key, value in table:
self.logger.info(" %-*s: %s", key_width, key, value)
except ValueError as e:
self.logger.info("device does not have valid SFDP data: %s", str(e))

if args.operation in ("read", "fast-read"):
if args.operation == "read":
283 changes: 283 additions & 0 deletions software/glasgow/protocol/sfdp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
# Ref: JEDEC JESD216/JESD216A/JESD216B
# Currently, only JESD216 (initial revision) is implemented.

from abc import ABCMeta, abstractmethod
import struct

from ..database.jedec import *
from ..support.aobject import *
from ..support.bits import *


__all__ = ["SFDPParser"]


class _JEDECRevisionMixin:
@property
def jedec_revision(self):
if self.version == (1, 0):
return "JESD216"
if self.version == (1, 5):
return "JESD216A"
if self.version == (1, 6):
return "JESD216B"
return "unknown JESD216 revision"


_JEDEC_Flash_Param_0 = Bitfield("JEDEC_Flash_Param_0", 4, [
("block_sector_erase_size", 2),
("write_granularity", 1),
("volatile_wren_required", 1),
("volatile_wren_opcode_sel", 1),
(None, 3),
("_4_kbyte_erase_opcode", 8),
("has_1_1_2_fast_read", 1),
("address_byte_count", 2),
("has_double_transfer_rate", 1),
("has_1_2_2_fast_read", 1),
("has_1_4_4_fast_read", 1),
("has_1_1_4_fast_read", 1),
(None, 9),
])

_JEDEC_Flash_Param_1 = Bitfield("JEDEC_Flash_Param_1", 4, [
("density_value", 31),
("density_over_2gbit", 1),
])

_JEDEC_Flash_Param_2 = Bitfield("JEDEC_Flash_Param_2", 4, [
("_fast_read_1_4_4_wait_states", 5),
("_fast_read_1_4_4_mode_bits", 3),
("_fast_read_1_4_4_opcode", 8),
("_fast_read_1_1_4_wait_states", 5),
("_fast_read_1_1_4_mode_bits", 3),
("_fast_read_1_1_4_opcode", 8),
])

_JEDEC_Flash_Param_3 = Bitfield("JEDEC_Flash_Param_3", 4, [
("_fast_read_1_1_2_wait_states", 5),
("_fast_read_1_1_2_mode_bits", 3),
("_fast_read_1_1_2_opcode", 8),
("_fast_read_1_2_2_wait_states", 5),
("_fast_read_1_2_2_mode_bits", 3),
("_fast_read_1_2_2_opcode", 8),
])

_JEDEC_Flash_Param_4 = Bitfield("JEDEC_Flash_Param_4", 4, [
("has_2_2_2_fast_read", 1),
(None, 3),
("has_4_4_4_fast_read", 1),
(None, 27),
])

_JEDEC_Flash_Param_5 = Bitfield("JEDEC_Flash_Param_5", 4, [
(None, 16),
("_fast_read_2_2_2_wait_states", 5),
("_fast_read_2_2_2_mode_bits", 3),
("_fast_read_2_2_2_opcode", 8),
])

_JEDEC_Flash_Param_6 = Bitfield("JEDEC_Flash_Param_6", 4, [
(None, 16),
("_fast_read_4_4_4_wait_states", 5),
("_fast_read_4_4_4_mode_bits", 3),
("_fast_read_4_4_4_opcode", 8),
])

_JEDEC_Flash_Param_7 = Bitfield("JEDEC_Flash_Param_7", 4, [
("sector_type_1_size", 8),
("sector_type_1_opcode", 8),
("sector_type_2_size", 8),
("sector_type_2_opcode", 8),
])

_JEDEC_Flash_Param_8 = Bitfield("JEDEC_Flash_Param_8", 4, [
("sector_type_3_size", 8),
("sector_type_3_opcode", 8),
("sector_type_4_size", 8),
("sector_type_4_opcode", 8),
])


class SFDPTable(_JEDECRevisionMixin):
def __new__(cls, vendor_id, table_id, revision, parameter):
if vendor_id == 0x00: # JEDEC
if table_id == 0xff: # Flash Parameters
cls = SFDPJEDECFlashParametersTable
return object.__new__(cls)

def __init__(self, vendor_id, table_id, revision, parameter):
self.vendor_id = vendor_id
self.table_id = table_id
self.version = revision
self.parameter = parameter

@property
def vendor_name(self):
if self.vendor_id == 0x00:
vendor_name = "JEDEC"
else:
vendor_name = jedec_mfg_name_from_bytes([self.vendor_id])
if vendor_name is None:
return "Unknown Vendor {:#04x}".format(self.vendor_id)
return vendor_name

@property
def table_name(self):
return "Unknown Table {:#04x}".format(self.table_id)

def __str__(self):
return "{}, {}".format(self.vendor_name, self.table_name)

def __iter__(self):
return iter(())


class SFDPJEDECFlashParametersTable(SFDPTable):
def __init__(self, vendor_id, table_id, revision, parameter):
super().__init__(vendor_id, table_id, revision, parameter)

try:
if len(parameter) < 9 * 4:
raise ValueError("table too small")

# First, parse SFDP table bitfields
words = struct.unpack("4s" * 9, parameter)
word0 = _JEDEC_Flash_Param_0.from_bytes(words[0])
word1 = _JEDEC_Flash_Param_1.from_bytes(words[1])
word2 = _JEDEC_Flash_Param_2.from_bytes(words[2])
word3 = _JEDEC_Flash_Param_3.from_bytes(words[3])
word4 = _JEDEC_Flash_Param_4.from_bytes(words[4])
word5 = _JEDEC_Flash_Param_5.from_bytes(words[5])
word6 = _JEDEC_Flash_Param_6.from_bytes(words[6])
word7 = _JEDEC_Flash_Param_7.from_bytes(words[7])
word8 = _JEDEC_Flash_Param_8.from_bytes(words[8])

# Second, populate our structured fields
if word1.density_over_2gbit:
self.density = 1 << word1.density_value
else:
self.density = word1.density_value + 1

if word0.address_byte_count == 0b00:
self.address_byte_count = {3}
elif word0.address_byte_count == 0b01:
self.address_byte_count = {3, 4}
elif word0.address_byte_count == 0b10:
self.address_byte_count = {4}
else:
raise ValueError("invalid address byte count {:#04b}"
.format(word0.address_byte_count))

if word0.write_granularity:
self.write_granularity = 64
else:
self.write_granularity = 1

self.sector_sizes = {}
if word7.sector_type_1_size > 0:
self.sector_sizes[1 << word7.sector_type_1_size] = word7.sector_type_1_opcode
if word7.sector_type_2_size > 0:
self.sector_sizes[1 << word7.sector_type_2_size] = word7.sector_type_2_opcode
if word8.sector_type_3_size > 0:
self.sector_sizes[1 << word8.sector_type_3_size] = word8.sector_type_3_opcode
if word8.sector_type_4_size > 0:
self.sector_sizes[1 << word8.sector_type_4_size] = word8.sector_type_4_opcode

self.has_double_transfer_rate = word0.has_double_transfer_rate

self.fast_read_modes = {}
if word0.has_1_1_2_fast_read:
self.fast_read_modes[(1,1,2)] = (
word3._fast_read_1_1_2_opcode,
word3._fast_read_1_1_2_wait_states,
word3._fast_read_1_1_2_mode_bits)
if word0.has_1_1_4_fast_read:
self.fast_read_modes[(1,1,4)] = (
word2._fast_read_1_1_4_opcode,
word2._fast_read_1_1_4_wait_states,
word2._fast_read_1_1_4_mode_bits)
if word0.has_1_2_2_fast_read:
self.fast_read_modes[(1,2,2)] = (
word3._fast_read_1_2_2_opcode,
word3._fast_read_1_2_2_wait_states,
word3._fast_read_1_2_2_mode_bits)
if word0.has_1_4_4_fast_read:
self.fast_read_modes[(1,4,4)] = (
word2._fast_read_1_4_4_opcode,
word2._fast_read_1_4_4_wait_states,
word2._fast_read_1_4_4_mode_bits)
if word4.has_2_2_2_fast_read:
self.fast_read_modes[(2,2,2)] = (
word5._fast_read_2_2_2_opcode,
word5._fast_read_2_2_2_wait_states,
word5._fast_read_2_2_2_mode_bits)
if word4.has_4_4_4_fast_read:
self.fast_read_modes[(4,4,4)] = (
word6._fast_read_4_4_4_opcode,
word6._fast_read_4_4_4_wait_states,
word6._fast_read_4_4_4_mode_bits)

except ValueError as e:
raise ValueError("cannot parse {}: {}".format(str(self), str(e))) from None

@property
def table_name(self):
return "Flash Parameter Table"

def __iter__(self):
properties = {}
properties["density (Mbits)"] = "{}".format(self.density >> 20)
properties["density (Mbytes)"] = "{}".format(self.density >> 23)
properties["address byte count"] = ", ".join(map(str, self.address_byte_count))
properties["write granularity"] = "{} byte(s)".format(self.write_granularity)

properties["sector sizes"] = ", ".join(map(str, self.sector_sizes))
for sector_size, opcode in self.sector_sizes.items():
properties["sector size {}".format(sector_size)] = \
"erase opcode {:#04x}".format(opcode)

properties["double transfer rate"] = "yes" if self.has_double_transfer_rate else "no"
properties["fast read modes"] = \
", ".join("({}-{}-{})".format(*mode) for mode in self.fast_read_modes.keys())
for mode, (opcode, wait_states, mode_bits) in self.fast_read_modes.items():
properties["fast read mode ({}-{}-{})".format(*mode)] = \
("opcode {:#04x}, {} wait states, {} mode bits"
.format(opcode, wait_states, mode_bits))

return iter(properties.items())


class SFDPParser(_JEDECRevisionMixin, aobject, metaclass=ABCMeta):
@abstractmethod
async def read(self, offset, length):
pass

async def __init__(self):
sfdp_header = await self.read(0, 8)
signature, ver_minor, ver_major, num_param_headers, _ = \
struct.unpack("4sBBBB", sfdp_header)
if signature != b"SFDP":
raise ValueError("SFDP signature not present")
self.version = (ver_major, ver_minor)

self.tables = []
for index in range(num_param_headers + 1):
param_header = await self.read(8 * (1 + index), 8)
vendor_id, rev_minor, rev_major, length_dwords, pointer, table_id = \
struct.unpack("BBBB3sB", param_header)
pointer = int.from_bytes(pointer, "little")

if index == 0 and vendor_id != 0x00:
raise ValueError("SFDP parameter header 0 has incorrect vendor ID {:#04x}"
.format(vendor_id))

parameter = await self.read(pointer, length_dwords * 4)
table = SFDPTable(vendor_id, table_id, (rev_major, rev_minor), parameter)
self.tables.append(table)

def __len__(self):
return len(self.tables)

def __iter__(self):
return iter(self.tables)