Modular Computers the manual GitHub

EEPROM

The EEPROM is the chip a computer boots from. Its contents are the program the CPU runs at power-on, and a program can read or reflash them.

Facts

| | | |—|—| | Type | eeprom | | In /dev | eeprom (there is only ever one) | | Address | eeprom | | Interrupt | None | | Info | used: the size of its contents in bytes |

For the chip you craft and place, see EEPROM.

How it works

At power-on the CPU loads its program from the EEPROM’s fixed slot, as a reset vector, and runs it. A CPU that cannot (the chip is empty or its code does not load) panics. The EEPROM’s contents are plain text, one character a byte, and for the Lua CPU they are Lua code. The mod has no size limit for it yet.

The chip a computer comes with boots the first disk, in bus order, that holds /init.lua, handing it the disk’s address, and says boot: no bootable disk and halts when none does. The code on the chip runs before any OS, so it calls the board directly (board.invoke, board.list).

The chip keeps its contents on its item: reads and writes go straight there. A write takes effect at the next power-on.

In mcos

/dev/eeprom

/dev/eeprom is the chip’s image, as flashrom -r and -w read and write a board’s flash.

cp /dev/eeprom /home/eeprom.bak   # back it up
cp /home/new.lua /dev/eeprom      # flash a new image
reboot                            # run it

Its ioctls are its methods

The file’s ioctls are the methods below, by name, in any open mode: get gives the image and set flashes one at once, with no close to wait for. setLabel (the kernel’s, on every device file) puts a label on the chip’s item; a chip with no item to keep it on refuses with EPERM.

local image = sys.ioctl(sys.fileno(io.open("/dev/eeprom")), "get")

Methods

get() → string

The chip’s contents.

set(data) → nothing

Replaces the chip’s contents with data. It takes effect at the next power-on.

Events

None.

Examples

Back up the chip

-- Save the EEPROM's image to a file, and check the copy against the chip.
--
-- usage: lua eeprom-backup.lua [file]     (default /home/eeprom.bak)
--
-- Reading /dev/eeprom gives the chip's whole image, as flashrom -r reads a board's flash. Restore it
-- later with eeprom-flash.lua, or with cp file /dev/eeprom.
local FILE = ... or "/home/eeprom.bak"

local function slurp(path)
  local file = assert(io.open(path, "rb"))
  local data = file:read("a")
  file:close()
  return data
end

local image = slurp("/dev/eeprom")
local out = assert(io.open(FILE, "wb"))
assert(out:write(image))
assert(out:close()) -- (a disk's file is written whole at close)

if slurp(FILE) ~= image then
  io.stderr:write("eeprom-backup: ", FILE, " doesn't match the chip\n")
  os.exit(1)
end
print("saved " .. #image .. " bytes of the EEPROM to " .. FILE)

Download eeprom-backup.lua

Flash a new image, safely

Refuses a file that does not compile (it would leave the computer unable to boot), keeps the old image, and reads the chip back to check.

-- Flash a Lua file onto the EEPROM, safely: refuse one that doesn't compile, keep the old image, and
-- check the chip holds the new one.
--
-- usage: lua eeprom-flash.lua file [backup]     (backup: default /home/eeprom.old)
--
-- What's written to /dev/eeprom is kept until the file is closed, then replaces the whole image in
-- one go (all or nothing, as a firmware flash commits). The CPU runs the image as Lua at the next
-- power-on, so an image that doesn't compile would leave the computer unable to boot: this checks
-- first. Then it reads the chip back with the "get" ioctl (its method) to be sure.
local sys = require("sys")

local file, backup = ...
backup = backup or "/home/eeprom.old"
if not file then
  io.stderr:write("usage: eeprom-flash.lua file [backup]\n")
  os.exit(2)
end

local function slurp(path)
  local f = assert(io.open(path, "rb"))
  local data = f:read("a")
  f:close()
  return data
end

local image = slurp(file)
local compiled, why = load(image, "=" .. file)
if not compiled then
  io.stderr:write("eeprom-flash: not flashed: ", why, "\n")
  os.exit(1)
end

-- Keep what's there now.
local old = assert(io.open(backup, "wb"))
old:write(slurp("/dev/eeprom"))
assert(old:close())

-- Flash: written at close.
local chip = assert(io.open("/dev/eeprom", "wb"))
chip:write(image)
assert(chip:close())

-- Read it back from the chip itself.
local check = assert(io.open("/dev/eeprom"))
local now = assert(sys.ioctl(sys.fileno(check), "get"))
check:close()
if now ~= image then
  io.stderr:write("eeprom-flash: the chip doesn't hold what was written; the old image is in ", backup, "\n")
  os.exit(1)
end
print("flashed " .. #image .. " bytes; the old image is in " .. backup .. "; reboot to run it")

Download eeprom-flash.lua

lua eeprom-flash.lua /home/boot.lua
reboot

Boot one disk only

Writes a new boot image that boots one disk, by its id, wherever it sits on the bus. The image it writes is firmware-level code: a whole EEPROM program, calling the board directly.

-- Make the computer boot one disk only, whatever else is on the bus: write a new boot image for the
-- EEPROM that looks for /init.lua on that disk alone, by its id.
--
-- usage: lua eeprom-pin-boot.lua [disk]     (the disk's name in /dev; default storage0, the one now)
--
-- The EEPROM a computer comes with boots the first disk, in bus order, holding /init.lua; a disk put
-- in an earlier slot would then boot instead. A disk's id never changes when it moves, so an image
-- naming it boots that disk wherever it sits. The image is built from IMAGE below and flashed with
-- the "set" ioctl (the EEPROM's own method: the whole image, at once), after "get" saves the old one.
local sys = require("sys")

local BACKUP = "/home/eeprom.old" -- the image there before

-- The new image: firmware-level Lua (it runs before any OS, so it calls the board directly).
-- DISK is filled in with the disk's id.
local IMAGE = [=[
-- pinned boot: boots /init.lua from one disk, by its id, or halts saying why.
local DISK = %q
local function say(text)
  board.invoke("console", "write", text)
end
local opened, file = board.invoke(DISK, "open", "/init.lua")
if not opened then
  say("boot: " .. DISK .. ": " .. tostring(file) .. "\r\n")
  board.halt()
end
local parts = {}
while true do
  local ok, chunk = board.invoke(file, "read", 4096)
  if not ok or chunk == nil then
    break
  end
  parts[#parts + 1] = chunk
end
board.invoke(file, "close")
local init, why = load(table.concat(parts), "=init.lua")
if not init then
  say("boot: " .. DISK .. ": " .. why .. "\r\n")
  board.halt()
end
return init(DISK)
]=]

local diskname = ... or "storage0"
local disk
for _, device in ipairs(sys.devices()) do
  if device.name == diskname and device.type == "storage" then
    disk = device
  end
end
if not disk then
  io.stderr:write("eeprom-pin-boot: ", diskname, ": no such disk\n")
  os.exit(1)
end

local image = string.format(IMAGE, disk.id)
assert(load(image, "=pinned boot")) -- (it must compile: the CPU runs it at power-on)

local chip = assert(io.open("/dev/eeprom"))
local fd = sys.fileno(chip)
local old = assert(sys.ioctl(fd, "get"))
local saved = assert(io.open(BACKUP, "wb"))
saved:write(old)
assert(saved:close())

assert(sys.ioctl(fd, "set", image)) -- (true: done)
chip:close()
print("the EEPROM now boots " .. disk.id .. " only; the old image is in " .. BACKUP)

Download eeprom-pin-boot.lua

To undo it, flash the old image back: lua eeprom-flash.lua /home/eeprom.old.