OpenBIOS
Threads by month
- ----- 2026 -----
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2025 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2024 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2023 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2022 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2021 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2020 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2019 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2018 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2017 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2016 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2015 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2014 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2013 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2012 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2011 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2010 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2009 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2008 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2007 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2006 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2005 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2004 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2003 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2002 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2001 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 2000 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1999 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1998 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1997 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1996 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1995 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1994 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1993 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1992 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1991 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1990 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1989 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1988 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1987 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1986 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1985 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1984 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1983 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1982 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1981 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1980 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1979 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1978 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1977 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1976 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1975 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1974 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1973 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1972 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1971 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1970 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1969 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1968 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1967 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1966 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1965 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1964 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1963 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1962 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1961 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1960 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1959 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1958 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1957 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1956 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1955 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1954 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1953 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1952 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1951 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1950 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1949 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1948 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1947 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1946 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1945 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1944 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1943 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1942 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1941 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1940 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1939 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1938 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1937 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1936 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1935 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1934 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1933 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1932 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1931 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1930 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1929 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1928 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1927 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1926 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1925 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1924 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1923 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1922 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1921 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1920 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1919 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1918 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1917 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1916 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1915 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1914 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1913 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1912 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1911 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1910 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1909 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1908 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1907 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1906 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1905 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
- ----- 1904 -----
- December
- November
- October
- September
- August
- July
- June
- May
- April
- March
- February
- January
September 2026
- 2 participants
- 13 discussions
sun4u-fire is QEMU's virtual UltraSPARC IIIi system, a Fire host bridge
with both leaves and no particular Sun board behind it. QEMU models it
as -M sun4u-fire in a companion series; this is the firmware side.
Leaf A carries the Fire PCI Express tree; leaf B roots the EBus rather
than finding it behind a PCI device. The console UART is on that bus, at
a physical address no I/O port number reaches, so the console driver
takes its address from the machine's hwdef. The clock and the NVRAM are
one M48T59 on the same bus, read through a mapped window, and each UART
takes its own INO so neither can drop the other's interrupt line. Reset
and power-off reach the platform through Fire's RESET_GEN register and
the EBus power chip select.
Memory past one DIMM pair comes in disjoint banks, one per pair. The
/memory node takes its "reg" from the bank list the machine publishes,
the holes between the banks are claimed, and the firmware takes its own
pages from the top of the bank at 0.
Tested on QEMU's -M sun4u-fire from the companion series: Solaris 10
1/13 installs and boots multiuser from the installed disk, OpenBSD 7.9
and NetBSD 10.1 boot from their install media, and the Debian sparc64
installer (Linux 6.1) reaches its language menu.
Solaris loads its forthdebug module through the client interface. That
module needs OBP's Forth-83 dictionary-walk words and OBP's ACTIONS
object subsystem, neither of which OpenBIOS implements, so Solaris
prints two Forth exceptions early in boot and its PROM-side kernel-VA
translation hook is not installed. It boots and runs regardless.
Applies on top of "sparc64: add the Sun Fire PCI Express host bridge"
and the sparc64 platform series below it; upstream base e5ac46dd
("config/scripts/switch-arch: fix build on aarch64 and riscv64 hosts").
Imre Kaloz (4):
sparc64: drive the console from a memory-mapped UART
sparc64: read the NVRAM through a memory-mapped window
sparc64: publish every memory bank the machine lists
sparc64: add the sun4u-fire machine detection and device tree
arch/sparc64/console.c | 12 +-
arch/sparc64/entry.S | 89 +++---
arch/sparc64/lib.c | 57 +++-
arch/sparc64/openbios.c | 525 ++++++++++++++++++++++++++++++++++-
arch/sparc64/openbios.h | 12 +
arch/sparc64/tree.fs | 34 ++-
drivers/pci.c | 11 +-
include/arch/common/fw_cfg.h | 3 +
include/arch/sparc64/smp.h | 7 +-
include/drivers/pci.h | 5 +
10 files changed, 681 insertions(+), 74 deletions(-)
base-commit: 1a13654b8018816ed4a799e01c48c660e1796039
--
2.47.3
1
4
Sept. 27, 2026
Fire is Sun's JBus-to-PCI Express bridge, two leaves per chip, and it is
the host bridge of every UltraSPARC IIIi workstation OpenBIOS can be
asked to boot. Each leaf gets a node built from the machine's
description, with an interrupt-map keyed on the INTx pin, the CSR window
the leaf's own registers answer in, the module revision and the two
error INOs an OS checks before it attaches the nexus.
A leaf refuses every DMA out of reset and the firmware sets up no
translations of its own, so the leaf is put in bypass and dma-map-in
tags the bus addresses it hands back.
Applies on top of "OpenBIOS: PCI bridge, PROM address and I/O allocation
fixes" and the sparc64 series below it; upstream base e5ac46dd
("config/scripts/switch-arch: fix build on aarch64 and riscv64 hosts").
Imre Kaloz (2):
sparc64: add the Sun Fire PCI Express host bridge
sparc64: take Fire's DMA through its bypass window
arch/sparc64/openbios.c | 10 ++-
drivers/pci.c | 175 ++++++++++++++++++++++++++++++++++++++
include/arch/sparc64/io.h | 20 +++++
include/drivers/pci.h | 4 +
4 files changed, 208 insertions(+), 1 deletion(-)
base-commit: 113bc67e6670c3830cdafbae42a072d44ebbd4cc
--
2.47.3
1
2
[PATCH 0/4] OpenBIOS: PCI bridge, PROM address and I/O allocation fixes
by Imre Kaloz Sept. 27, 2026
by Imre Kaloz Sept. 27, 2026
Sept. 27, 2026
Four fixes a second sparc64 platform needs, independent of each other
and of any particular machine.
A bridge whose children are not addressed in PCI's devfn and register
form binds its own "decode-unit" and "encode-unit" from its config
callback, and the generic bridge method set shadowed that pair; such a
bridge now gets a method set without it. A PCI-to-PCI bridge the tables
do not name got no cell counts at all, so path resolution failed on its
children -- the ports of a PCI Express switch, among others.
The boot PROM's physical address was a constant naming sabre's window;
it is taken from the PC at entry, which is inside the PROM. And I/O
space allocation started below the window a platform's own NVRAM answers
in, handing a card the addresses the firmware reads its configuration
through.
Imre Kaloz (4):
sparc64: find the boot PROM's physical address at entry
pci: let a bridge address its children in its own convention
pci: publish a node for an unnamed PCI-to-PCI bridge
pci: keep I/O-space allocation out of a platform's own NVRAM window
arch/sparc64/entry.S | 18 ++++++---
arch/sparc64/openbios.c | 5 +++
drivers/pci.c | 71 +++++++++++++++++++++++++++++++--
drivers/pci_database.c | 87 +++++++++++++++++++++++++----------------
drivers/pci_database.h | 3 ++
include/drivers/pci.h | 4 ++
6 files changed, 147 insertions(+), 41 deletions(-)
base-commit: 6153f3b7f1e1e07ea395ecda1c320d5bd241e7ce
--
2.47.3
1
4
OpenBIOS starts every CPU QEMU creates at the same reset vector and has
nothing that tells them apart, so on an SMP machine four CPUs race
through .data initialisation and fw_cfg's single read cursor.
Each CPU reads its module id out of the Fireplane/JBus config register
at entry. MID 0 boots; the others park on a per-MID mailbox until the
client interface's SUNW,start-cpu releases them at an address of the
OS's choosing. The trap and client-interface paths then need per-CPU
state rather than the single static block they shared, and the two
client-interface patches behind that are what a secondary taking an
interrupt inside firmware exposed: the window state a CIF call must
leave alone, and the register-file snapshot it must take with interrupts
off.
OpenBSD 7.9, NetBSD 10.1, Solaris 10 1/13 and Linux 6.1 bring up four
CPUs on an UltraSPARC IIIi machine with this.
Imre Kaloz (4):
sparc64: park secondary CPUs and publish SUNW,start-cpu services
sparc64: give the trap and client-interface paths per-CPU state
sparc64: leave the client's window state alone across a CIF call
sparc64: take the client's register-file snapshot with interrupts off
arch/sparc64/call-client.S | 121 +++++++++++++++++++++++++---------
arch/sparc64/context.c | 22 ++++++-
arch/sparc64/cpustate.h | 29 ++++++++-
arch/sparc64/entry.S | 130 ++++++++++++++++++++++++++++++++++---
arch/sparc64/ldscript | 5 --
arch/sparc64/openbios.c | 79 +++++++++++++++++++++-
arch/sparc64/switch.S | 1 +
arch/sparc64/vectors.S | 41 ++++++++----
include/arch/sparc64/io.h | 2 +-
include/arch/sparc64/smp.h | 75 +++++++++++++++++++++
10 files changed, 442 insertions(+), 63 deletions(-)
create mode 100644 include/arch/sparc64/smp.h
base-commit: d48258097c64252d04a89b27cda79e42e60c5aff
--
2.47.3
1
4
arch/sparc64 has one machine in hwdefs[] and reads sabre's values
wherever a platform-specific one is needed: the NVRAM I/O offset, the
system bus clock, the register the power switch is behind, the window
fw_cfg answers on, and the single host bridge a root node is built for.
None of that holds for an UltraSPARC IIIi system, whose fw_cfg is in a
JBus leaf's I/O space and whose device tree has a root node per leaf.
The first three patches publish what a cheetah-class CPU node owes
an OS: the counter rates from fw_cfg, the cache and TLB geometry,
and the port id. The next four move the platform's own values into
struct hwdef -- its identity, power, NVRAM and clock; the fw_cfg
window, probed in entry.S with the MMU still off, because a window the
running machine does not decode faults once it is on; an array of host
bridges instead of one; and the UltraSPARC IIIi memory controller node
Solaris loads mc-us3i from, one per processor where the machine lists
them. The last names a root child after the UPA port it answers on,
which is what path resolution compares against "reg".
-M sun4u keeps the values it has: its hwdef declares sabre's, and its
single bridge is the array's only entry.
Imre Kaloz (8):
sparc64: publish "stick-frequency" and take the counter rates from
fw_cfg
sparc64: populate cache and TLB geometry for UltraSPARC III/IIIi
sparc64: publish "portid" on the cpu node for cheetah-class CPUs
sparc64: describe the machine's identity, power and NVRAM in its hwdef
sparc64: probe for the fw_cfg window before the MMU comes on
sparc64: give a machine more than one host bridge
sparc64: publish the UltraSPARC IIIi memory controller
sparc64: name UPA root children by their port id
arch/sparc64/entry.S | 101 ++++++++++---
arch/sparc64/openbios.c | 266 ++++++++++++++++++++++++++++++++---
arch/sparc64/tree.fs | 14 +-
arch/sparc64/vectors.S | 11 +-
include/arch/common/fw_cfg.h | 5 +
5 files changed, 352 insertions(+), 45 deletions(-)
base-commit: 158545c847ba4e4a095b0e21487d874c5aa92c0a
--
2.47.3
1
8
Sept. 27, 2026
sun4u has no firmware path to boot from NVMe. Bring up one admin and
one I/O queue pair, find the namespaces with Identify, and read a page
per command through one PRP entry. Each namespace is a disk node
addressed by its namespace ID. On sun4u it relies on the dma-map-in and
dma-sync fixes and on the PCI bridge memory window fix that precede it.
qemu-system-sparc64 -M sun4u -m 1G -nographic \
-device nvme,serial=nv0,drive=nv0 \
-drive if=none,id=nv0,format=raw,file=nvme0.img
Signed-off-by: Imre Kaloz <kaloz(a)kernel.org>
---
Notes:
This applies on top of the sun4u-fire series ("sparc64: add the
sun4u-fire machine detection and device tree" and the series below it),
which it takes drivers/pci.c and drivers/pci_database.c context from.
config/examples/sparc64_config.xml | 2 +
drivers/build.xml | 1 +
drivers/nvme.c | 660 +++++++++++++++++++++++++++++
drivers/nvme.h | 84 ++++
drivers/pci.c | 16 +
drivers/pci_database.c | 30 ++
drivers/pci_database.h | 1 +
include/drivers/drivers.h | 4 +
include/drivers/pci.h | 5 +
9 files changed, 803 insertions(+)
create mode 100644 drivers/nvme.c
create mode 100644 drivers/nvme.h
diff --git a/config/examples/sparc64_config.xml b/config/examples/sparc64_config.xml
index d9c669fa..363d509b 100644
--- a/config/examples/sparc64_config.xml
+++ b/config/examples/sparc64_config.xml
@@ -74,4 +74,6 @@
<option name="CONFIG_DRIVER_VIRTIO_BLK" type="boolean" value="true"/>
<option name="CONFIG_DRIVER_MPTSAS" type="boolean" value="true"/>
<option name="CONFIG_DEBUG_MPTSAS" type="boolean" value="false"/>
+ <option name="CONFIG_DRIVER_NVME" type="boolean" value="true"/>
+ <option name="CONFIG_DEBUG_NVME" type="boolean" value="false"/>
<option name="CONFIG_FW_CFG_ADDR" type="integer" value="0x510"/>
diff --git a/drivers/build.xml b/drivers/build.xml
index 8b41037a..1f47d769 100644
--- a/drivers/build.xml
+++ b/drivers/build.xml
@@ -29,6 +29,7 @@
<object source="usbohci_rh.c" condition="DRIVER_USB"/>
<object source="lsi.c" condition="DRIVER_LSI_53C810"/>
<object source="mptsas.c" condition="DRIVER_MPTSAS"/>
+ <object source="nvme.c" condition="DRIVER_NVME"/>
<object source="virtio.c" condition="DRIVER_VIRTIO_BLK"/>
</library>
diff --git a/drivers/nvme.c b/drivers/nvme.c
new file mode 100644
index 00000000..47e76524
--- /dev/null
+++ b/drivers/nvme.c
@@ -0,0 +1,660 @@
+/*
+ * OpenBIOS NVM Express driver
+ *
+ * Copyright (C) 2026 Imre Kaloz <kaloz(a)kernel.org>
+ *
+ * Based upon drivers/mptsas.c and drivers/virtio.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2
+ *
+ */
+
+#include "config.h"
+#include "libc/byteorder.h"
+#include "libc/vsprintf.h"
+#include "libopenbios/bindings.h"
+#include "kernel/kernel.h"
+#include "drivers/drivers.h"
+#include "nvme.h"
+
+typedef struct nvme_ns nvme_ns_t;
+typedef struct nvme_private nvme_private_t;
+
+struct nvme_ns {
+ uint32_t nsid;
+ unsigned int lba_shift;
+ uint64_t nsze;
+ nvme_private_t *nvme;
+};
+
+/*
+ * One page for each of the four queues and one for data. A command moves
+ * at most one page, so PRP1 alone describes every transfer.
+ */
+#define NVME_PAGE_SIZE 0x1000
+#define NVME_QUEUE_DEPTH 8
+
+#define NVME_MAX_NS 16
+
+enum {
+ NVME_ASQ,
+ NVME_ACQ,
+ NVME_IOSQ,
+ NVME_IOCQ,
+ NVME_DATA,
+ NVME_NUM_PAGES
+};
+
+typedef struct nvme_queue {
+ nvme_sqe_t *sq;
+ volatile nvme_cqe_t *cq;
+ uint64_t sq_iova;
+ unsigned int sq_tail;
+ unsigned int cq_head;
+ uint32_t phase;
+ unsigned int qid;
+} nvme_queue_t;
+
+struct nvme_private {
+ uint64_t regs;
+ unsigned int db_stride;
+ uint8_t *page[NVME_NUM_PAGES];
+ uint64_t page_iova[NVME_NUM_PAGES];
+ nvme_queue_t admin;
+ nvme_queue_t io;
+ uint16_t cid;
+ unsigned int num_ns;
+ nvme_ns_t ns[NVME_MAX_NS];
+};
+
+/* Polled, one command outstanding: no interrupt ever reaches the CPU. */
+#define NVME_POLL_LIMIT 10000000
+
+#ifdef CONFIG_DEBUG_NVME
+#define DPRINTF(fmt, args...) \
+ do { printk(fmt , ##args); } while (0)
+#else
+#define DPRINTF(fmt, args...)
+#endif
+
+/* DECLARE data structures for the nodes. */
+DECLARE_UNNAMED_NODE(ob_nvme_ns, INSTALL_OPEN, sizeof(nvme_ns_t *));
+DECLARE_UNNAMED_NODE(ob_nvme, INSTALL_OPEN, sizeof(nvme_private_t **));
+
+static int nvme_count;
+
+static uint32_t
+nvme_reg_read(nvme_private_t *nvme, unsigned int reg)
+{
+ return in_le32((volatile unsigned *)(uintptr_t)(nvme->regs + reg));
+}
+
+static void
+nvme_reg_write(nvme_private_t *nvme, unsigned int reg, uint32_t val)
+{
+ out_le32((volatile unsigned *)(uintptr_t)(nvme->regs + reg), val);
+}
+
+/* The 64-bit registers are written as two dwords, low half first. */
+static void
+nvme_reg_write64(nvme_private_t *nvme, unsigned int reg, uint64_t val)
+{
+ nvme_reg_write(nvme, reg, (uint32_t)val);
+ nvme_reg_write(nvme, reg + 4, (uint32_t)(val >> 32));
+}
+
+static void
+nvme_ring_sq(nvme_private_t *nvme, nvme_queue_t *q)
+{
+ nvme_reg_write(nvme, NVME_REG_DBS + (2 * q->qid) * nvme->db_stride,
+ q->sq_tail);
+}
+
+static void
+nvme_ring_cq(nvme_private_t *nvme, nvme_queue_t *q)
+{
+ nvme_reg_write(nvme, NVME_REG_DBS + (2 * q->qid + 1) * nvme->db_stride,
+ q->cq_head);
+}
+
+static void
+nvme_queue_init(nvme_private_t *nvme, nvme_queue_t *q, unsigned int qid,
+ int sq_page, int cq_page)
+{
+ q->sq = (nvme_sqe_t *)nvme->page[sq_page];
+ q->cq = (volatile nvme_cqe_t *)nvme->page[cq_page];
+ q->sq_iova = nvme->page_iova[sq_page];
+ q->sq_tail = 0;
+ q->cq_head = 0;
+ q->phase = NVME_CQE_PHASE;
+ q->qid = qid;
+}
+
+/*
+ * The controller reads and writes a queue or the data page over DMA.
+ * Reads also run under a client's instance, where the "dma-sync" node
+ * method does not resolve, so call the platform's (dma-sync) directly.
+ */
+static void
+nvme_dma_sync(uint8_t *virt, uint64_t devaddr, unsigned int size)
+{
+ PUSH(pointer2cell(virt));
+ PUSH((ucell)devaddr);
+ PUSH(size);
+ fword("(dma-sync)");
+}
+
+/*
+ * Submit one command and wait for its completion entry. The entry is
+ * current once its phase tag matches the pass through the queue we are on.
+ */
+static int
+nvme_submit(nvme_private_t *nvme, nvme_queue_t *q, nvme_sqe_t *cmd)
+{
+ volatile nvme_cqe_t *cqe;
+ uint32_t status = 0;
+ unsigned int i;
+ uint16_t cid = nvme->cid++;
+
+ cmd->cdw0 |= __cpu_to_le32(cid << 16);
+ memcpy(&q->sq[q->sq_tail], cmd, sizeof(*cmd));
+ nvme_dma_sync((uint8_t *)&q->sq[q->sq_tail],
+ q->sq_iova + q->sq_tail * sizeof(*cmd), sizeof(*cmd));
+ q->sq_tail = (q->sq_tail + 1) % NVME_QUEUE_DEPTH;
+ nvme_ring_sq(nvme, q);
+
+ cqe = &q->cq[q->cq_head];
+ for (i = 0; i < NVME_POLL_LIMIT; i++) {
+ /* One load: __le32_to_cpu() reads its argument once per byte. */
+ status = cqe->status;
+ status = __le32_to_cpu(status);
+ if ((status & NVME_CQE_PHASE) == q->phase) {
+ break;
+ }
+ }
+ if (i == NVME_POLL_LIMIT) {
+ printk("nvme: command %02x timed out\n", __le32_to_cpu(cmd->cdw0) & 0xff);
+ return -1;
+ }
+
+ q->cq_head = (q->cq_head + 1) % NVME_QUEUE_DEPTH;
+ if (q->cq_head == 0) {
+ q->phase ^= NVME_CQE_PHASE;
+ }
+ nvme_ring_cq(nvme, q);
+
+ if ((status & 0xffff) != cid || NVME_CQE_STATUS(status)) {
+ printk("nvme: command %02x failed, status %04x\n",
+ __le32_to_cpu(cmd->cdw0) & 0xff, NVME_CQE_STATUS(status));
+ return -1;
+ }
+
+ return 0;
+}
+
+static void
+nvme_cmd_init(nvme_sqe_t *cmd, uint8_t opcode, uint32_t nsid, uint64_t prp1)
+{
+ memset(cmd, 0, sizeof(*cmd));
+ cmd->cdw0 = __cpu_to_le32(opcode);
+ cmd->nsid = __cpu_to_le32(nsid);
+ cmd->prp1[0] = __cpu_to_le32((uint32_t)prp1);
+ cmd->prp1[1] = __cpu_to_le32((uint32_t)(prp1 >> 32));
+}
+
+static int
+nvme_identify(nvme_private_t *nvme, uint32_t nsid, uint32_t cns)
+{
+ nvme_sqe_t cmd;
+ int ret;
+
+ nvme_cmd_init(&cmd, NVME_ADM_IDENTIFY, nsid, nvme->page_iova[NVME_DATA]);
+ cmd.cdw10 = __cpu_to_le32(cns);
+
+ ret = nvme_submit(nvme, &nvme->admin, &cmd);
+ if (!ret) {
+ nvme_dma_sync(nvme->page[NVME_DATA], nvme->page_iova[NVME_DATA],
+ NVME_PAGE_SIZE);
+ }
+ return ret;
+}
+
+static int
+nvme_wait_ready(nvme_private_t *nvme, uint32_t rdy)
+{
+ unsigned int i;
+ uint32_t csts;
+
+ for (i = 0; i < NVME_POLL_LIMIT; i++) {
+ csts = nvme_reg_read(nvme, NVME_REG_CSTS);
+ if (csts == 0xffffffff || (csts & NVME_CSTS_CFS)) {
+ break;
+ }
+ if ((csts & NVME_CSTS_RDY) == rdy) {
+ return 0;
+ }
+ }
+
+ printk("nvme: controller did not become %s\n", rdy ? "ready" : "idle");
+ return -1;
+}
+
+static int
+nvme_enable(nvme_private_t *nvme)
+{
+ uint32_t cap_hi = nvme_reg_read(nvme, NVME_REG_CAP + 4);
+
+ /* Queue base addresses and every PRP are in 4 KiB pages. */
+ if (NVME_CAP_HI_MPSMIN(cap_hi) != 0) {
+ printk("nvme: minimum page size %d unsupported\n",
+ 4096 << NVME_CAP_HI_MPSMIN(cap_hi));
+ return -1;
+ }
+ nvme->db_stride = 4 << NVME_CAP_HI_DSTRD(cap_hi);
+
+ if (nvme_reg_read(nvme, NVME_REG_CC) & NVME_CC_EN) {
+ nvme_reg_write(nvme, NVME_REG_CC, 0);
+ }
+ if (nvme_wait_ready(nvme, 0)) {
+ return -1;
+ }
+
+ nvme_queue_init(nvme, &nvme->admin, 0, NVME_ASQ, NVME_ACQ);
+ nvme_reg_write(nvme, NVME_REG_INTMS, 0xffffffff);
+ nvme_reg_write(nvme, NVME_REG_AQA, ((NVME_QUEUE_DEPTH - 1) << 16) |
+ (NVME_QUEUE_DEPTH - 1));
+ nvme_reg_write64(nvme, NVME_REG_ASQ, nvme->page_iova[NVME_ASQ]);
+ nvme_reg_write64(nvme, NVME_REG_ACQ, nvme->page_iova[NVME_ACQ]);
+ nvme_reg_write(nvme, NVME_REG_CC, NVME_CC_IOSQES | NVME_CC_IOCQES |
+ NVME_CC_EN);
+
+ return nvme_wait_ready(nvme, NVME_CSTS_RDY);
+}
+
+static int
+nvme_create_io_queues(nvme_private_t *nvme)
+{
+ uint32_t qsize = (NVME_QUEUE_DEPTH - 1) << 16;
+ nvme_sqe_t cmd;
+
+ nvme_queue_init(nvme, &nvme->io, 1, NVME_IOSQ, NVME_IOCQ);
+
+ /* Interrupts stay off: IEN is clear. */
+ nvme_cmd_init(&cmd, NVME_ADM_CREATE_CQ, 0, nvme->page_iova[NVME_IOCQ]);
+ cmd.cdw10 = __cpu_to_le32(qsize | nvme->io.qid);
+ cmd.cdw11 = __cpu_to_le32(NVME_QUEUE_PC);
+ if (nvme_submit(nvme, &nvme->admin, &cmd)) {
+ return -1;
+ }
+
+ nvme_cmd_init(&cmd, NVME_ADM_CREATE_SQ, 0, nvme->page_iova[NVME_IOSQ]);
+ cmd.cdw10 = __cpu_to_le32(qsize | nvme->io.qid);
+ cmd.cdw11 = __cpu_to_le32((nvme->io.qid << 16) | NVME_QUEUE_PC);
+
+ return nvme_submit(nvme, &nvme->admin, &cmd);
+}
+
+static uint32_t
+nvme_get_le32(const uint8_t *p)
+{
+ return p[0] | (p[1] << 8) | (p[2] << 16) | ((uint32_t)p[3] << 24);
+}
+
+/* Returns 1 if the namespace is usable, 0 if not. */
+static int
+nvme_identify_ns(nvme_private_t *nvme, nvme_ns_t *ns, uint32_t nsid)
+{
+ uint8_t *id = nvme->page[NVME_DATA];
+ unsigned int lbaf;
+
+ if (nvme_identify(nvme, nsid, NVME_ID_CNS_NS)) {
+ return 0;
+ }
+
+ ns->nsid = nsid;
+ ns->nsze = nvme_get_le32(id + NVME_ID_NS_NSZE) |
+ ((uint64_t)nvme_get_le32(id + NVME_ID_NS_NSZE + 4) << 32);
+ lbaf = id[NVME_ID_NS_FLBAS] & 0xf;
+ ns->lba_shift = id[NVME_ID_NS_LBAF + lbaf * 4 + 2];
+
+ /* Each block has to fit the data page, and splits into 512-byte ones. */
+ if (ns->lba_shift < 9 || ns->lba_shift > 12) {
+ printk("nvme: namespace %d block size %d unsupported\n", nsid,
+ 1 << ns->lba_shift);
+ return 0;
+ }
+
+ DPRINTF("nvme: namespace %d, %lld blocks of %d bytes\n", nsid,
+ (unsigned long long)ns->nsze, 1 << ns->lba_shift);
+
+ return ns->nsze != 0;
+}
+
+static void
+nvme_scan_namespaces(nvme_private_t *nvme)
+{
+ uint32_t nsids[NVME_MAX_NS];
+ unsigned int i, n;
+ uint8_t *id = nvme->page[NVME_DATA];
+
+ if (nvme_identify(nvme, 0, NVME_ID_CNS_CTRL)) {
+ return;
+ }
+ n = nvme_get_le32(id + NVME_ID_CTRL_NN);
+ DPRINTF("nvme: %d namespaces\n", n);
+
+ /* A controller without the active namespace list gets NN probed. */
+ if (nvme_identify(nvme, 0, NVME_ID_CNS_NS_ACTIVE) == 0) {
+ for (n = 0; n < NVME_MAX_NS; n++) {
+ nsids[n] = nvme_get_le32(id + n * 4);
+ if (nsids[n] == 0) {
+ break;
+ }
+ }
+ } else {
+ for (i = 0; i < n && i < NVME_MAX_NS; i++) {
+ nsids[i] = i + 1;
+ }
+ n = i;
+ }
+
+ for (i = 0; i < n; i++) {
+ if (nvme_identify_ns(nvme, &nvme->ns[nvme->num_ns], nsids[i])) {
+ nvme->ns[nvme->num_ns].nvme = nvme;
+ nvme->num_ns++;
+ }
+ }
+}
+
+/* Read up to a page of blocks into the data page. */
+static int
+nvme_read(nvme_ns_t *ns, uint64_t lba, unsigned int count)
+{
+ nvme_private_t *nvme = ns->nvme;
+ nvme_sqe_t cmd;
+ int ret;
+
+ nvme_cmd_init(&cmd, NVME_CMD_READ, ns->nsid, nvme->page_iova[NVME_DATA]);
+ cmd.cdw10 = __cpu_to_le32((uint32_t)lba);
+ cmd.cdw11 = __cpu_to_le32((uint32_t)(lba >> 32));
+ cmd.cdw12 = __cpu_to_le32(count - 1);
+
+ ret = nvme_submit(nvme, &nvme->io, &cmd);
+ if (!ret) {
+ nvme_dma_sync(nvme->page[NVME_DATA], nvme->page_iova[NVME_DATA],
+ NVME_PAGE_SIZE);
+ }
+ return ret;
+}
+
+static void
+ob_nvme_dma_alloc(__attribute__((unused)) nvme_private_t **nvme)
+{
+ call_parent_method("dma-alloc");
+}
+
+static void
+ob_nvme_dma_free(__attribute__((unused)) nvme_private_t **nvme)
+{
+ call_parent_method("dma-free");
+}
+
+static void
+ob_nvme_dma_map_in(__attribute__((unused)) nvme_private_t **nvme)
+{
+ call_parent_method("dma-map-in");
+}
+
+static void
+ob_nvme_dma_map_out(__attribute__((unused)) nvme_private_t **nvme)
+{
+ call_parent_method("dma-map-out");
+}
+
+static void
+ob_nvme_dma_sync(__attribute__((unused)) nvme_private_t **nvme)
+{
+ call_parent_method("dma-sync");
+}
+
+/* ( addr blk n -- actual ), in 512-byte blocks */
+static void
+ob_nvme_ns_read_blocks(nvme_ns_t **ns)
+{
+ cell n = POP(), cnt = n;
+ ucell blk = POP();
+ char *dest = (char*)POP();
+ unsigned int spb = 1 << ((*ns)->lba_shift - 9);
+ unsigned int pos, count, len;
+ uint64_t lba;
+
+ DPRINTF("ob_nvme_ns_read_blocks ns %d %lx block=%lld n=%lld\n",
+ (*ns)->nsid, (unsigned long)dest, (long long)blk, (long long)n);
+
+ while (n) {
+ lba = blk / spb;
+ pos = (blk % spb) * 512;
+ count = (pos + n * 512 + (1 << (*ns)->lba_shift) - 1) >>
+ (*ns)->lba_shift;
+ if (count > NVME_PAGE_SIZE >> (*ns)->lba_shift) {
+ count = NVME_PAGE_SIZE >> (*ns)->lba_shift;
+ }
+ if (lba + count > (*ns)->nsze) {
+ break;
+ }
+
+ if (nvme_read(*ns, lba, count)) {
+ printk("nvme: namespace %d block %lld not read\n", (*ns)->nsid,
+ (unsigned long long)lba);
+ break;
+ }
+
+ len = (count << (*ns)->lba_shift) - pos;
+ if (len > n * 512) {
+ len = n * 512;
+ }
+ memcpy(dest, (*ns)->nvme->page[NVME_DATA] + pos, len);
+ dest += len;
+ blk += len / 512;
+ n -= len / 512;
+ }
+ PUSH(cnt - n);
+}
+
+static void
+ob_nvme_ns_block_size(__attribute__((unused))nvme_ns_t **ns)
+{
+ PUSH(512);
+}
+
+static void
+ob_nvme_ns_nblocks(nvme_ns_t **ns)
+{
+ PUSH((*ns)->nsze << ((*ns)->lba_shift - 9));
+}
+
+static void
+ob_nvme_ns_open(nvme_ns_t **ns)
+{
+ phandle_t ph;
+
+ PUSH(find_ih_method("nvme-ns-private", my_self()));
+ fword("execute");
+ *ns = cell2pointer(POP());
+
+ selfword("open-deblocker");
+
+ /* interpose disk-label */
+ ph = find_dev("/packages/disk-label");
+ fword("my-args");
+ PUSH_ph( ph );
+ fword("interpose");
+
+ RET ( -1 );
+}
+
+static void
+ob_nvme_ns_close(__attribute__((unused)) nvme_ns_t **ns)
+{
+ selfword("close-deblocker");
+}
+
+NODE_METHODS(ob_nvme_ns) = {
+ { "open", ob_nvme_ns_open },
+ { "close", ob_nvme_ns_close },
+ { "read-blocks", ob_nvme_ns_read_blocks },
+ { "block-size", ob_nvme_ns_block_size },
+ { "#blocks", ob_nvme_ns_nblocks },
+};
+
+static void
+ob_nvme_decodeunit(__attribute__((unused)) nvme_private_t **nvme)
+{
+ /* ( str len -- nsid ) */
+ fword("parse-hex");
+}
+
+static void
+ob_nvme_encodeunit(__attribute__((unused)) nvme_private_t **nvme)
+{
+ /* ( nsid -- str len ) */
+ fword("pocket");
+ fword("tohexstr");
+}
+
+static void
+ob_nvme_open(__attribute__((unused)) nvme_private_t **nvme)
+{
+ PUSH(-1);
+}
+
+static void
+ob_nvme_close(__attribute__((unused)) nvme_private_t **nvme)
+{
+ return;
+}
+
+NODE_METHODS(ob_nvme) = {
+ { "open" , ob_nvme_open },
+ { "close" , ob_nvme_close },
+ { "decode-unit", ob_nvme_decodeunit },
+ { "encode-unit", ob_nvme_encodeunit },
+ { "dma-alloc", ob_nvme_dma_alloc },
+ { "dma-free", ob_nvme_dma_free },
+ { "dma-map-in", ob_nvme_dma_map_in },
+ { "dma-map-out", ob_nvme_dma_map_out },
+ { "dma-sync", ob_nvme_dma_sync },
+};
+
+static void
+add_alias(const char *device, const char *alias)
+{
+ phandle_t aliases;
+
+ DPRINTF("add_alias dev \"%s\" = alias \"%s\"\n", device, alias);
+
+ aliases = find_dev("/aliases");
+ set_property(aliases, alias, device, strlen(device) + 1);
+}
+
+static void
+nvme_free_pages(nvme_private_t *nvme, unsigned int npages)
+{
+ unsigned int i;
+
+ for (i = 0; i < npages; i++) {
+ PUSH(pointer2cell(nvme->page[i]));
+ PUSH(NVME_PAGE_SIZE);
+ feval("dma-free");
+ }
+}
+
+int
+ob_nvme_init(uint64_t regs)
+{
+ char nodebuff[256], aliasbuff[16];
+ phandle_t ph = get_cur_dev();
+ nvme_private_t *nvme;
+ unsigned int i;
+ ucell addr;
+
+ BIND_NODE_METHODS(ph, ob_nvme);
+
+ nvme = malloc(sizeof(nvme_private_t));
+ if (!nvme) {
+ DPRINTF("Can't allocate NVMe private structure\n");
+ return -1;
+ }
+ memset(nvme, 0, sizeof(*nvme));
+ nvme->regs = regs;
+
+ for (i = 0; i < NVME_NUM_PAGES; i++) {
+ PUSH(NVME_PAGE_SIZE);
+ feval("dma-alloc");
+ addr = POP();
+ if (!addr) {
+ DPRINTF("Can't allocate NVMe DMA page %d\n", i);
+ nvme_free_pages(nvme, i);
+ free(nvme);
+ return -1;
+ }
+ nvme->page[i] = cell2pointer(addr);
+ memset(nvme->page[i], 0, NVME_PAGE_SIZE);
+
+ PUSH(addr);
+ PUSH(NVME_PAGE_SIZE);
+ PUSH(0);
+ feval("dma-map-in");
+ nvme->page_iova[i] = POP();
+ if (nvme->page_iova[i] == (uint64_t)-1) {
+ DPRINTF("Can't map NVMe DMA page %d\n", i);
+ nvme_free_pages(nvme, i + 1);
+ free(nvme);
+ return -1;
+ }
+ }
+
+ set_int_property(ph, "#address-cells", 1);
+ set_int_property(ph, "#size-cells", 0);
+
+ if (nvme_enable(nvme) || nvme_create_io_queues(nvme)) {
+ nvme_free_pages(nvme, NVME_NUM_PAGES);
+ free(nvme);
+ return -1;
+ }
+ nvme_scan_namespaces(nvme);
+
+ for (i = 0; i < nvme->num_ns; i++) {
+ fword("new-device");
+ push_str("disk");
+ fword("device-name");
+ push_str("block");
+ fword("device-type");
+ fword("is-deblocker");
+ PUSH(nvme->ns[i].nsid);
+ fword("encode-int");
+ push_str("reg");
+ fword("property");
+
+ PUSH(pointer2cell(&nvme->ns[i]));
+ feval("value nvme-ns-private");
+
+ BIND_NODE_METHODS(get_cur_dev(), ob_nvme_ns);
+ fword("finish-device");
+
+ if (i == 0) {
+ snprintf(nodebuff, sizeof(nodebuff), "%s/disk@%x",
+ get_path_from_ph(ph), nvme->ns[i].nsid);
+ if (nvme_count == 0) {
+ add_alias(nodebuff, "nvme");
+ }
+ snprintf(aliasbuff, sizeof(aliasbuff), "nvme%d", nvme_count);
+ add_alias(nodebuff, aliasbuff);
+ }
+ }
+ nvme_count++;
+
+ return 0;
+}
diff --git a/drivers/nvme.h b/drivers/nvme.h
new file mode 100644
index 00000000..e9f6f61d
--- /dev/null
+++ b/drivers/nvme.h
@@ -0,0 +1,84 @@
+/*
+ * NVM Express definitions
+ *
+ * Copyright (C) 2026 Imre Kaloz <kaloz(a)kernel.org>
+ *
+ * The controller registers, queue entries and admin and I/O commands a
+ * polled, read-only boot driver uses.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2
+ *
+ */
+
+#ifndef NVME_H
+#define NVME_H
+
+/* Controller registers, offsets into BAR 0. */
+#define NVME_REG_CAP 0x00
+#define NVME_REG_INTMS 0x0c
+#define NVME_REG_CC 0x14
+#define NVME_REG_CSTS 0x1c
+#define NVME_REG_AQA 0x24
+#define NVME_REG_ASQ 0x28
+#define NVME_REG_ACQ 0x30
+#define NVME_REG_DBS 0x1000
+
+/* Fields of the upper half of CAP. */
+#define NVME_CAP_HI_DSTRD(x) ((x) & 0xf)
+#define NVME_CAP_HI_MPSMIN(x) (((x) >> 16) & 0xf)
+
+#define NVME_CC_EN 0x00000001
+#define NVME_CC_IOSQES (6 << 16)
+#define NVME_CC_IOCQES (4 << 20)
+
+#define NVME_CSTS_RDY 0x00000001
+#define NVME_CSTS_CFS 0x00000002
+
+#define NVME_ADM_CREATE_SQ 0x01
+#define NVME_ADM_CREATE_CQ 0x05
+#define NVME_ADM_IDENTIFY 0x06
+
+#define NVME_CMD_READ 0x02
+
+#define NVME_ID_CNS_NS 0x00
+#define NVME_ID_CNS_CTRL 0x01
+#define NVME_ID_CNS_NS_ACTIVE 0x02
+
+/* Identify Controller and Identify Namespace byte offsets. */
+#define NVME_ID_CTRL_NN 516
+#define NVME_ID_NS_NSZE 0
+#define NVME_ID_NS_FLBAS 26
+#define NVME_ID_NS_LBAF 128
+
+#define NVME_QUEUE_PC 0x0001
+
+/* Queue entries are little-endian, and are built and read a dword at a time. */
+
+typedef struct nvme_sqe {
+ uint32_t cdw0; /* opcode, and the command identifier above it */
+ uint32_t nsid;
+ uint32_t rsvd[2];
+ uint32_t mptr[2];
+ uint32_t prp1[2];
+ uint32_t prp2[2];
+ uint32_t cdw10;
+ uint32_t cdw11;
+ uint32_t cdw12;
+ uint32_t cdw13;
+ uint32_t cdw14;
+ uint32_t cdw15;
+} nvme_sqe_t;
+
+typedef struct nvme_cqe {
+ uint32_t result;
+ uint32_t rsvd;
+ uint32_t sq; /* SQ head pointer, and the SQ identifier above it */
+ uint32_t status; /* command identifier, phase tag, status field */
+} nvme_cqe_t;
+
+#define NVME_CQE_PHASE 0x00010000
+#define NVME_CQE_STATUS(x) (((x) >> 17) & 0x7fff)
+
+#endif /* NVME_H */
diff --git a/drivers/pci.c b/drivers/pci.c
index 15e4ec97..1275f864 100644
--- a/drivers/pci.c
+++ b/drivers/pci.c
@@ -1341,6 +1341,22 @@ int mptsas_config_cb(const pci_config_t *config)
return 0;
}
+int nvme_config_cb(const pci_config_t *config)
+{
+#ifdef CONFIG_DRIVER_NVME
+ /* Enable PCI bus mastering */
+ ob_pci_enable_bus_master(config);
+
+ /* BAR 0 holds the controller registers and doorbells.
+ nvme_reg_read/write reach them through out_le32/in_le32, which
+ store via ASI_PHYS_BYPASS and so need the raw bus address, not
+ the ofmem-mapped pointer ob_pci_map() hands back for byte-array
+ MMIO like lsi53c810's. */
+ ob_nvme_init(arch->host_pci_base + (config->assigned[0] & ~0x0000000F));
+#endif
+ return 0;
+}
+
void ob_pci_enable_bus_master(const pci_config_t *config)
{
/* Enable bus mastering for the PCI device */
diff --git a/drivers/pci_database.c b/drivers/pci_database.c
index 1955e067..8532f416 100644
--- a/drivers/pci_database.c
+++ b/drivers/pci_database.c
@@ -102,6 +102,31 @@ static const pci_dev_t ide_devices[] = {
},
};
+static const pci_dev_t nvme_devices[] = {
+ {
+ /* QEMU NVMe controller */
+ PCI_VENDOR_ID_REDHAT, PCI_DEVICE_ID_REDHAT_NVME,
+ NULL, "nvme", NULL,
+ "pci1b36,10\0pciclass,010802\0",
+ 0, 0, 0,
+ nvme_config_cb, NULL,
+ },
+ {
+ /* QEMU NVMe controller, use-intel-id=on */
+ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_NVME,
+ NULL, "nvme", NULL,
+ "pci8086,5845\0pciclass,010802\0",
+ 0, 0, 0,
+ nvme_config_cb, NULL,
+ },
+ {
+ 0xFFFF, 0xFFFF,
+ NULL, NULL, NULL, NULL,
+ -1, -1, -1,
+ NULL, NULL,
+ },
+};
+
static const pci_subclass_t mass_subclass[] = {
{
PCI_SUBCLASS_STORAGE_SCSI, "SCSI bus controller",
@@ -133,6 +158,11 @@ static const pci_subclass_t mass_subclass[] = {
"ata", NULL, NULL,
NULL, NULL,
},
+ {
+ PCI_SUBCLASS_STORAGE_NVM, "NVM controller",
+ NULL, nvme_devices, NULL,
+ NULL, NULL,
+ },
{
PCI_SUBCLASS_STORAGE_OTHER, "misc mass-storage controller",
NULL, NULL, NULL,
diff --git a/drivers/pci_database.h b/drivers/pci_database.h
index 5970e93e..ae8a499f 100644
--- a/drivers/pci_database.h
+++ b/drivers/pci_database.h
@@ -49,6 +49,7 @@ extern int sungem_config_cb (const pci_config_t *config);
extern int sunhme_config_cb(const pci_config_t *config);
extern int lsi53c810_config_cb(const pci_config_t *config);
extern int mptsas_config_cb(const pci_config_t *config);
+extern int nvme_config_cb(const pci_config_t *config);
static inline int pci_compat_len(const pci_dev_t *dev)
{
diff --git a/include/drivers/drivers.h b/include/drivers/drivers.h
index bf34c505..13df825a 100644
--- a/include/drivers/drivers.h
+++ b/include/drivers/drivers.h
@@ -80,6 +80,10 @@ int ob_lsi_init(const char *path, uint64_t mmio, uint64_t ram);
/* drivers/mptsas.c */
int ob_mptsas_init(uint64_t regs);
#endif
+#ifdef CONFIG_DRIVER_NVME
+/* drivers/nvme.c */
+int ob_nvme_init(uint64_t regs);
+#endif
#ifdef CONFIG_DRIVER_OBIO
/* drivers/obio.c */
int ob_obio_init(uint64_t slavio_base, unsigned long fd_offset,
diff --git a/include/drivers/pci.h b/include/drivers/pci.h
index 037948c8..2bdf7597 100644
--- a/include/drivers/pci.h
+++ b/include/drivers/pci.h
@@ -81,6 +81,7 @@ extern void ob_isa_encode_unit(void);
#define PCI_SUBCLASS_STORAGE_RAID 0x04
#define PCI_SUBCLASS_STORAGE_ATA 0x05
#define PCI_SUBCLASS_STORAGE_SAS 0x07
+#define PCI_SUBCLASS_STORAGE_NVM 0x08
#define PCI_SUBCLASS_STORAGE_OTHER 0x80
#define PCI_BASE_CLASS_NETWORK 0x02
@@ -257,9 +258,13 @@ extern void ob_isa_encode_unit(void);
#define PCI_DEVICE_ID_VIRTIO_NET 0x1000
#define PCI_DEVICE_ID_VIRTIO_BLOCK 0x1001
+#define PCI_VENDOR_ID_REDHAT 0x1b36
+#define PCI_DEVICE_ID_REDHAT_NVME 0x0010
+
#define PCI_VENDOR_ID_INTEL 0x8086
#define PCI_DEVICE_ID_INTEL_82378 0x0484
#define PCI_DEVICE_ID_INTEL_E1000 0x100e
#define PCI_DEVICE_ID_INTEL_82441 0x1237
+#define PCI_DEVICE_ID_INTEL_NVME 0x5845
#endif /* _H_PCI */
base-commit: 942a8d02e2f0b6426057b2629222dae888861b32
--
2.47.3
1
0
sun4u has no firmware path to boot from an LSI SAS1068. Bring the IOC
up over the doorbell handshake, find the targets by INQUIRY, and read
one block at a time through a single scatter/gather element. Read-only,
polled. On sun4u it relies on the dma-map-in and dma-sync fixes and on
the PCI bridge memory window fix that precede it.
Based on drivers/lsi.c and drivers/virtio.c, and on QEMU's
hw/scsi/mptsas.c for the message interface.
qemu-system-sparc64 -M sun4u -m 512 -nographic -boot c \
-device mptsas1068,id=scsi0 \
-drive if=none,id=hd,format=qcow2,file=disk.qcow2 \
-device scsi-hd,drive=hd,bus=scsi0.0,scsi-id=0
Signed-off-by: Imre Kaloz <kaloz(a)kernel.org>
---
Notes:
On sun4u the driver needs, at run time, three patches posted ahead of
it: "sparc64: hand dma-map-in's caller an address the device can use",
"sparc64: stop demapping the buffer in dma-sync" and "pci: hold a
bridge's memory window open across its secondary bus scan". It applies
to e5ac46dd on its own, but does not run there without them.
config/examples/sparc64_config.xml | 2 +
drivers/build.xml | 1 +
drivers/mptsas.c | 746 +++++++++++++++++++++++++++++
drivers/mptsas.h | 140 ++++++
drivers/pci.c | 17 +
drivers/pci_database.c | 8 +
drivers/pci_database.h | 1 +
include/drivers/drivers.h | 4 +
include/drivers/pci.h | 1 +
9 files changed, 920 insertions(+)
create mode 100644 drivers/mptsas.c
create mode 100644 drivers/mptsas.h
diff --git a/config/examples/sparc64_config.xml b/config/examples/sparc64_config.xml
index a4172dfb..d9c669fa 100644
--- a/config/examples/sparc64_config.xml
+++ b/config/examples/sparc64_config.xml
@@ -72,4 +72,6 @@
<option name="CONFIG_DRIVER_PC_SERIAL" type="boolean" value="true"/>
<option name="CONFIG_DRIVER_FW_CFG" type="boolean" value="true"/>
<option name="CONFIG_DRIVER_VIRTIO_BLK" type="boolean" value="true"/>
+ <option name="CONFIG_DRIVER_MPTSAS" type="boolean" value="true"/>
+ <option name="CONFIG_DEBUG_MPTSAS" type="boolean" value="false"/>
<option name="CONFIG_FW_CFG_ADDR" type="integer" value="0x510"/>
diff --git a/drivers/build.xml b/drivers/build.xml
index 5a28bc2a..8b41037a 100644
--- a/drivers/build.xml
+++ b/drivers/build.xml
@@ -28,6 +28,7 @@
<object source="usbohci.c" condition="DRIVER_USB"/>
<object source="usbohci_rh.c" condition="DRIVER_USB"/>
<object source="lsi.c" condition="DRIVER_LSI_53C810"/>
+ <object source="mptsas.c" condition="DRIVER_MPTSAS"/>
<object source="virtio.c" condition="DRIVER_VIRTIO_BLK"/>
</library>
diff --git a/drivers/mptsas.c b/drivers/mptsas.c
new file mode 100644
index 00000000..f1e9c398
--- /dev/null
+++ b/drivers/mptsas.c
@@ -0,0 +1,746 @@
+/*
+ * OpenBIOS Fusion-MPT SAS driver
+ *
+ * Copyright (C) 2026 Imre Kaloz <kaloz(a)kernel.org>
+ *
+ * Based upon drivers/lsi.c and drivers/virtio.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2
+ *
+ */
+
+#include "config.h"
+#include "libc/byteorder.h"
+#include "libc/vsprintf.h"
+#include "libopenbios/bindings.h"
+#include "kernel/kernel.h"
+#include "drivers/drivers.h"
+#include "mptsas.h"
+#include "scsi.h"
+
+typedef struct sd_private sd_private_t;
+typedef struct mptsas_private mptsas_private_t;
+
+struct sd_private {
+ unsigned int bs;
+ const char *media_str[2];
+ uint32_t sectors;
+ uint8_t media;
+ uint8_t id;
+ uint8_t present;
+ char model[40];
+ mptsas_private_t *mpt;
+};
+
+#define MPTSAS_MAX_TARGETS 8
+
+struct mptsas_private {
+ uint64_t regs;
+ uint8_t *buffer;
+ uint64_t buffer_iova;
+ sd_private_t sd[MPTSAS_MAX_TARGETS];
+};
+
+/* One page of message frames and one of data, out of a single mapping. */
+#define MPTSAS_DMA_SIZE 0x2000
+#define MPTSAS_REQ_OFFSET 0x0000
+#define MPTSAS_REPLY_OFFSET 0x0100
+#define MPTSAS_REPLY_SIZE 0x0080
+#define MPTSAS_NUM_REPLIES 2
+#define MPTSAS_SENSE_OFFSET 0x0200
+#define MPTSAS_SENSE_SIZE 0x0020
+#define MPTSAS_DATA_OFFSET 0x1000
+#define MPTSAS_DATA_SIZE 0x1000
+
+/* Polled, one command outstanding: no interrupt ever reaches the CPU. */
+#define MPTSAS_POLL_LIMIT 10000000
+
+#ifdef CONFIG_DEBUG_MPTSAS
+#define DPRINTF(fmt, args...) \
+ do { printk(fmt , ##args); } while (0)
+#else
+#define DPRINTF(fmt, args...)
+#endif
+
+/* DECLARE data structures for the nodes. */
+DECLARE_UNNAMED_NODE(ob_sd, INSTALL_OPEN, sizeof(sd_private_t *));
+DECLARE_UNNAMED_NODE(ob_mptsas, INSTALL_OPEN, sizeof(mptsas_private_t **));
+
+static uint32_t
+mptsas_reg_read(mptsas_private_t *mpt, unsigned int reg)
+{
+ return in_le32((volatile unsigned *)(uintptr_t)(mpt->regs + reg));
+}
+
+static void
+mptsas_reg_write(mptsas_private_t *mpt, unsigned int reg, uint32_t val)
+{
+ out_le32((volatile unsigned *)(uintptr_t)(mpt->regs + reg), val);
+}
+
+static int
+mptsas_wait_doorbell_int(mptsas_private_t *mpt)
+{
+ unsigned int i;
+
+ for (i = 0; i < MPTSAS_POLL_LIMIT; i++) {
+ if (mptsas_reg_read(mpt, MPI_HOST_INTERRUPT_STATUS) &
+ MPI_HIS_DOORBELL_INTERRUPT) {
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+/* The IOC owns the doorbell while it digests a word of the request. */
+static int
+mptsas_wait_doorbell_ack(mptsas_private_t *mpt)
+{
+ unsigned int i;
+
+ for (i = 0; i < MPTSAS_POLL_LIMIT; i++) {
+ if (!(mptsas_reg_read(mpt, MPI_HOST_INTERRUPT_STATUS) &
+ MPI_HIS_IOP_DOORBELL_STATUS)) {
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+/*
+ * Hand a message to the IOC a 32-bit word at a time and collect its reply
+ * 16 bits at a time, which is the only way to talk to an IOC that has no
+ * reply frames posted yet. Both directions carry the frame's little-endian
+ * image, so a word is the native value of the image read as little-endian.
+ */
+static int
+mptsas_handshake(mptsas_private_t *mpt, void *msg, unsigned int len,
+ uint16_t *reply, unsigned int reply_words)
+{
+ uint32_t *req = (uint32_t *)msg;
+ unsigned int dwords = len / sizeof(uint32_t);
+ unsigned int i, words;
+
+ mptsas_reg_write(mpt, MPI_HOST_INTERRUPT_STATUS, 0);
+ mptsas_reg_write(mpt, MPI_DOORBELL,
+ (MPI_FUNCTION_HANDSHAKE << MPI_DOORBELL_FUNCTION_SHIFT) |
+ (dwords << MPI_DOORBELL_ADD_DWORDS_SHIFT));
+
+ if (mptsas_wait_doorbell_int(mpt)) {
+ return -1;
+ }
+ if (!(mptsas_reg_read(mpt, MPI_DOORBELL) & MPI_DOORBELL_ACTIVE)) {
+ return -1;
+ }
+ mptsas_reg_write(mpt, MPI_HOST_INTERRUPT_STATUS, 0);
+
+ for (i = 0; i < dwords; i++) {
+ if (mptsas_wait_doorbell_ack(mpt)) {
+ return -1;
+ }
+ mptsas_reg_write(mpt, MPI_DOORBELL, __le32_to_cpu(req[i]));
+ }
+
+ /* The second word of every reply header carries its length in dwords. */
+ for (i = 0; i < 2; i++) {
+ if (mptsas_wait_doorbell_int(mpt)) {
+ return -1;
+ }
+ reply[i] = mptsas_reg_read(mpt, MPI_DOORBELL) & MPI_DOORBELL_DATA_MASK;
+ mptsas_reg_write(mpt, MPI_HOST_INTERRUPT_STATUS, 0);
+ }
+
+ words = (reply[1] & 0xff) * 2;
+ for (i = 2; i < words; i++) {
+ if (mptsas_wait_doorbell_int(mpt)) {
+ return -1;
+ }
+ if (i < reply_words) {
+ reply[i] = mptsas_reg_read(mpt, MPI_DOORBELL) &
+ MPI_DOORBELL_DATA_MASK;
+ } else {
+ mptsas_reg_read(mpt, MPI_DOORBELL);
+ }
+ mptsas_reg_write(mpt, MPI_HOST_INTERRUPT_STATUS, 0);
+ }
+
+ mptsas_reg_write(mpt, MPI_HOST_INTERRUPT_STATUS, 0);
+
+ /* IOCStatus is the eighth 16-bit word of every reply. */
+ if (words > 7 && (reply[7] & MPI_IOCSTATUS_MASK) != MPI_IOCSTATUS_SUCCESS) {
+ printk("mptsas: message %02x failed, IOCStatus %04x\n",
+ reply[1] >> 8, reply[7]);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int
+mptsas_ioc_reset(mptsas_private_t *mpt)
+{
+ unsigned int i;
+
+ mptsas_reg_write(mpt, MPI_DOORBELL, MPI_FUNCTION_IOC_MESSAGE_UNIT_RESET <<
+ MPI_DOORBELL_FUNCTION_SHIFT);
+
+ for (i = 0; i < MPTSAS_POLL_LIMIT; i++) {
+ if ((mptsas_reg_read(mpt, MPI_DOORBELL) & MPI_IOC_STATE_MASK) ==
+ MPI_IOC_STATE_READY) {
+ mptsas_reg_write(mpt, MPI_HOST_INTERRUPT_STATUS, 0);
+ return 0;
+ }
+ }
+
+ printk("mptsas: IOC did not become ready\n");
+ return -1;
+}
+
+static int
+mptsas_ioc_init(mptsas_private_t *mpt)
+{
+ mpi_ioc_init_t msg;
+ uint16_t reply[16];
+
+ memset(&msg, 0, sizeof(msg));
+ msg.WhoInit = MPI_WHOINIT_ROM_BIOS;
+ msg.Function = MPI_FUNCTION_IOC_INIT;
+ msg.MaxBuses = 1;
+ msg.ReplyFrameSize = __cpu_to_le16(MPTSAS_REPLY_SIZE);
+ /*
+ * Reply and sense frames are addressed by their low 32 bits plus these
+ * two registers, so a mapping above 4 GB - a bypass-tagged one included
+ * - needs its high half here rather than in every frame.
+ */
+ msg.HostMfaHighAddr = __cpu_to_le32(mpt->buffer_iova >> 32);
+ msg.SenseBufferHighAddr = __cpu_to_le32(mpt->buffer_iova >> 32);
+ msg.MsgVersion = __cpu_to_le16(0x0105);
+
+ return mptsas_handshake(mpt, &msg, sizeof(msg), reply, 16);
+}
+
+static int
+mptsas_port_enable(mptsas_private_t *mpt)
+{
+ mpi_port_enable_t msg;
+ uint16_t reply[16];
+
+ memset(&msg, 0, sizeof(msg));
+ msg.Function = MPI_FUNCTION_PORT_ENABLE;
+
+ return mptsas_handshake(mpt, &msg, sizeof(msg), reply, 16);
+}
+
+/*
+ * Give the IOC somewhere to put an address reply. With none posted it
+ * faults instead of reporting the error that produced the reply, so the
+ * frames go up before the first command and each one goes back as soon as
+ * it has been read.
+ */
+static void
+mptsas_post_reply_frames(mptsas_private_t *mpt)
+{
+ unsigned int i;
+
+ for (i = 0; i < MPTSAS_NUM_REPLIES; i++) {
+ mptsas_reg_write(mpt, MPI_REPLY_FREE_FIFO,
+ (uint32_t)mpt->buffer_iova + MPTSAS_REPLY_OFFSET +
+ i * MPTSAS_REPLY_SIZE);
+ }
+}
+
+/* The IOC reads and writes mpt->buffer over DMA. Commands also run under
+ a client's instance, where the "dma-sync" node method does not resolve,
+ so call the platform's (dma-sync) directly. */
+static void
+mptsas_dma_sync(mptsas_private_t *mpt, unsigned int offset, unsigned int len)
+{
+ PUSH(pointer2cell(mpt->buffer + offset));
+ PUSH((ucell)(mpt->buffer_iova + offset));
+ PUSH(len);
+ fword("(dma-sync)");
+}
+
+/* Run one command to completion. Returns the SCSI status, or -1. */
+static int
+mptsas_command(mptsas_private_t *mpt, sd_private_t *sd, const uint8_t *cdb,
+ unsigned int cdb_len, unsigned int data_len)
+{
+ mpi_scsi_io_t *msg = (mpi_scsi_io_t *)(mpt->buffer + MPTSAS_REQ_OFFSET);
+ uint32_t flags, reply_addr, val = 0xffffffff;
+ mpi_reply_t *reply;
+ unsigned int i;
+ int status;
+
+ memset(msg, 0, sizeof(*msg));
+ msg->TargetID = sd->id;
+ msg->Function = MPI_FUNCTION_SCSI_IO_REQUEST;
+ msg->CDBLength = cdb_len;
+ msg->SenseBufferLength = MPTSAS_SENSE_SIZE;
+ msg->MsgContext = __cpu_to_le32(sd->id);
+ msg->Control = __cpu_to_le32(data_len ? MPI_SCSIIO_CONTROL_READ :
+ MPI_SCSIIO_CONTROL_NODATATRANSFER);
+ memcpy(msg->CDB, cdb, cdb_len);
+ msg->DataLength = __cpu_to_le32(data_len);
+ msg->SenseBufferLowAddr = __cpu_to_le32((uint32_t)mpt->buffer_iova +
+ MPTSAS_SENSE_OFFSET);
+
+ flags = MPI_SGE_FLAGS_SIMPLE_ELEMENT | MPI_SGE_FLAGS_64_BIT_ADDRESSING |
+ MPI_SGE_FLAGS_LAST_ELEMENT | MPI_SGE_FLAGS_END_OF_BUFFER |
+ MPI_SGE_FLAGS_END_OF_LIST | data_len;
+ msg->SGEFlagsLength = __cpu_to_le32(flags);
+ msg->SGEAddress = __cpu_to_le64(mpt->buffer_iova + MPTSAS_DATA_OFFSET);
+
+ mptsas_dma_sync(mpt, MPTSAS_REQ_OFFSET, sizeof(*msg));
+ mptsas_reg_write(mpt, MPI_REQUEST_POST_FIFO,
+ (uint32_t)mpt->buffer_iova + MPTSAS_REQ_OFFSET);
+
+ for (i = 0; i < MPTSAS_POLL_LIMIT; i++) {
+ val = mptsas_reg_read(mpt, MPI_REPLY_POST_FIFO);
+ if (val != 0xffffffff) {
+ break;
+ }
+ }
+ if (val == 0xffffffff) {
+ printk("mptsas: target %d did not answer command %02x\n",
+ sd->id, cdb[0]);
+ return -1;
+ }
+
+ /* A context reply is the message context alone and means success. */
+ if (!(val & MPI_ADDRESS_REPLY_A_BIT)) {
+ if (data_len) {
+ mptsas_dma_sync(mpt, MPTSAS_DATA_OFFSET, data_len);
+ }
+ return 0;
+ }
+
+ reply_addr = (val & ~MPI_ADDRESS_REPLY_A_BIT) << 1;
+ mptsas_dma_sync(mpt, reply_addr - (uint32_t)mpt->buffer_iova,
+ MPTSAS_REPLY_SIZE);
+ reply = (mpi_reply_t *)(mpt->buffer + (reply_addr -
+ (uint32_t)mpt->buffer_iova));
+ status = __le16_to_cpu(reply->IOCStatus) & MPI_IOCSTATUS_MASK;
+
+ if (status == MPI_IOCSTATUS_SUCCESS ||
+ status == MPI_IOCSTATUS_SCSI_DATA_UNDERRUN) {
+ status = reply->SCSIStatus;
+ if (data_len) {
+ mptsas_dma_sync(mpt, MPTSAS_DATA_OFFSET, data_len);
+ }
+ } else {
+ /* An empty target answers every command this way, and the bus scan
+ has nothing to say about the seven that are not there. */
+ if (status != MPI_IOCSTATUS_SCSI_DEVICE_NOT_THERE) {
+ printk("mptsas: target %d command %02x failed, IOCStatus %04x\n",
+ sd->id, cdb[0], status);
+ }
+ status = -1;
+ }
+
+ mptsas_reg_write(mpt, MPI_REPLY_FREE_FIFO, reply_addr);
+
+ return status;
+}
+
+static unsigned int
+inquiry(mptsas_private_t *mpt, sd_private_t *sd)
+{
+ const char *media[2] = { "UNKNOWN", "UNKNOWN"};
+ uint8_t cdb[6], *buffer;
+
+ memset(cdb, 0, sizeof(cdb));
+ cdb[0] = INQUIRY;
+ cdb[4] = 36;
+
+ if (mptsas_command(mpt, sd, cdb, sizeof(cdb), 36)) {
+ sd->present = 0;
+ sd->media = -1;
+ return 0;
+ }
+
+ buffer = mpt->buffer + MPTSAS_DATA_OFFSET;
+ sd->present = 1;
+ sd->media = buffer[0];
+
+ switch (sd->media) {
+ case TYPE_DISK:
+ media[0] = "disk";
+ media[1] = "hd";
+ break;
+ case TYPE_ROM:
+ media[0] = "cdrom";
+ media[1] = "cd";
+ break;
+ }
+ sd->media_str[0] = media[0];
+ sd->media_str[1] = media[1];
+ memcpy(sd->model, &buffer[16], 16);
+ sd->model[16] = '\0';
+
+ return 1;
+}
+
+static unsigned int
+read_capacity(mptsas_private_t *mpt, sd_private_t *sd)
+{
+ uint8_t cdb[10], *buffer;
+
+ memset(cdb, 0, sizeof(cdb));
+ cdb[0] = READ_CAPACITY;
+
+ if (mptsas_command(mpt, sd, cdb, sizeof(cdb), 8)) {
+ sd->sectors = 0;
+ sd->bs = 0;
+ DPRINTF("read_capacity id %d failed\n", sd->id);
+ return 0;
+ }
+
+ buffer = mpt->buffer + MPTSAS_DATA_OFFSET;
+ sd->bs = (buffer[4] << 24) | (buffer[5] << 16) | (buffer[6] << 8) | buffer[7];
+ if (sd->bs == 0 || sd->bs > MPTSAS_DATA_SIZE) {
+ printk("mptsas: target %d block size %d unsupported\n", sd->id, sd->bs);
+ sd->bs = 0;
+ return 0;
+ }
+ sd->sectors = (((buffer[0] << 24) | (buffer[1] << 16) | (buffer[2] << 8) |
+ buffer[3]) + 1) * (sd->bs / 512);
+
+ DPRINTF("read_capacity id %d bs %d sectors %d\n", sd->id, sd->bs,
+ sd->sectors);
+ return 1;
+}
+
+static unsigned int
+test_unit_ready(mptsas_private_t *mpt, sd_private_t *sd)
+{
+ uint8_t cdb[6];
+
+ memset(cdb, 0, sizeof(cdb));
+ cdb[0] = TEST_UNIT_READY;
+
+ if (mptsas_command(mpt, sd, cdb, sizeof(cdb), 0)) {
+ DPRINTF("test_unit_ready id %d failed\n", sd->id);
+ return 0;
+ }
+
+ DPRINTF("test_unit_ready id %d success\n", sd->id);
+ return 1;
+}
+
+static void
+ob_mptsas_dma_alloc(__attribute__((unused)) mptsas_private_t **mpt)
+{
+ call_parent_method("dma-alloc");
+}
+
+static void
+ob_mptsas_dma_free(__attribute__((unused)) mptsas_private_t **mpt)
+{
+ call_parent_method("dma-free");
+}
+
+static void
+ob_mptsas_dma_map_in(__attribute__((unused)) mptsas_private_t **mpt)
+{
+ call_parent_method("dma-map-in");
+}
+
+static void
+ob_mptsas_dma_map_out(__attribute__((unused)) mptsas_private_t **mpt)
+{
+ call_parent_method("dma-map-out");
+}
+
+static void
+ob_mptsas_dma_sync(__attribute__((unused)) mptsas_private_t **mpt)
+{
+ call_parent_method("dma-sync");
+}
+
+/* offset is in sectors */
+static int
+ob_sd_read_sector(mptsas_private_t *mpt, sd_private_t *sd, int offset)
+{
+ uint8_t cdb[10];
+
+ DPRINTF("ob_sd_read_sector id %d sector=%d\n", sd->id, offset);
+
+ memset(cdb, 0, sizeof(cdb));
+ cdb[0] = READ_10;
+ cdb[2] = (offset >> 24) & 0xff;
+ cdb[3] = (offset >> 16) & 0xff;
+ cdb[4] = (offset >> 8) & 0xff;
+ cdb[5] = offset & 0xff;
+ cdb[8] = 1;
+
+ if (mptsas_command(mpt, sd, cdb, sizeof(cdb), sd->bs)) {
+ printk("mptsas: target %d sector %d not read\n", sd->id, offset);
+ return 1;
+ }
+
+ return 0;
+}
+
+static void
+ob_sd_read_blocks(sd_private_t **sd)
+{
+ cell n = POP(), cnt = n;
+ ucell blk = POP();
+ char *dest = (char*)POP();
+ int pos, spb, sect_offset;
+ mptsas_private_t *mpt = (*sd)->mpt;
+
+ DPRINTF("ob_sd_read_blocks id %d %lx block=%d n=%d\n", (*sd)->id,
+ (unsigned long)dest, blk, n);
+
+ if ((*sd)->bs == 0) {
+ PUSH(0);
+ return;
+ }
+ spb = (*sd)->bs / 512;
+ while (n) {
+ sect_offset = blk / spb;
+ pos = (blk - sect_offset * spb) * 512;
+
+ if (ob_sd_read_sector(mpt, *sd, sect_offset)) {
+ DPRINTF("ob_sd_read_blocks: error\n");
+ RET(0);
+ }
+ while (n && pos < spb * 512) {
+ memcpy(dest, mpt->buffer + MPTSAS_DATA_OFFSET + pos, 512);
+ pos += 512;
+ dest += 512;
+ n--;
+ blk++;
+ }
+ }
+ PUSH(cnt);
+}
+
+static void
+ob_sd_block_size(__attribute__((unused))sd_private_t **sd)
+{
+ PUSH(512);
+}
+
+static void
+ob_sd_open(__attribute__((unused))sd_private_t **sd)
+{
+ int ret = 1;
+ phandle_t ph;
+
+ PUSH(find_ih_method("sd-private", my_self()));
+ fword("execute");
+ *sd = cell2pointer(POP());
+
+ selfword("open-deblocker");
+
+ /* interpose disk-label */
+ ph = find_dev("/packages/disk-label");
+ fword("my-args");
+ PUSH_ph( ph );
+ fword("interpose");
+
+ RET ( -ret );
+}
+
+static void
+ob_sd_close(__attribute__((unused)) sd_private_t **sd)
+{
+ selfword("close-deblocker");
+}
+
+NODE_METHODS(ob_sd) = {
+ { "open", ob_sd_open },
+ { "close", ob_sd_close },
+ { "read-blocks", ob_sd_read_blocks },
+ { "block-size", ob_sd_block_size },
+};
+
+static void
+ob_mptsas_decodeunit(__attribute__((unused)) mptsas_private_t **mpt)
+{
+ /* ( str len -- id ) */
+ fword("parse-hex");
+}
+
+static void
+ob_mptsas_encodeunit(__attribute__((unused)) mptsas_private_t **mpt)
+{
+ /* ( id -- str len ) */
+ fword("pocket");
+ fword("tohexstr");
+}
+
+static void
+ob_mptsas_open(__attribute__((unused)) mptsas_private_t **mpt)
+{
+ PUSH(-1);
+}
+
+static void
+ob_mptsas_close(__attribute__((unused)) mptsas_private_t **mpt)
+{
+ return;
+}
+
+NODE_METHODS(ob_mptsas) = {
+ { "open" , ob_mptsas_open },
+ { "close" , ob_mptsas_close },
+ { "decode-unit", ob_mptsas_decodeunit },
+ { "encode-unit", ob_mptsas_encodeunit },
+ { "dma-alloc", ob_mptsas_dma_alloc },
+ { "dma-free", ob_mptsas_dma_free },
+ { "dma-map-in", ob_mptsas_dma_map_in },
+ { "dma-map-out", ob_mptsas_dma_map_out },
+ { "dma-sync", ob_mptsas_dma_sync },
+};
+
+static void
+add_alias(const char *device, const char *alias)
+{
+ phandle_t aliases;
+
+ DPRINTF("add_alias dev \"%s\" = alias \"%s\"\n", device, alias);
+
+ aliases = find_dev("/aliases");
+ set_property(aliases, alias, device, strlen(device) + 1);
+}
+
+int
+ob_mptsas_init(uint64_t regs)
+{
+ int id, diskcount = 0, cdcount = 0, *counter_ptr;
+ char nodebuff[256], aliasbuff[256];
+ phandle_t ph = get_cur_dev();
+ mptsas_private_t *mpt;
+ ucell addr;
+ int i;
+
+ BIND_NODE_METHODS(ph, ob_mptsas);
+
+ mpt = malloc(sizeof(mptsas_private_t));
+ if (!mpt) {
+ DPRINTF("Can't allocate MPT private structure\n");
+ return -1;
+ }
+ memset(mpt, 0, sizeof(*mpt));
+
+ PUSH(MPTSAS_DMA_SIZE);
+ feval("dma-alloc");
+ addr = POP();
+ if (!addr) {
+ DPRINTF("Can't allocate MPT DMA buffer\n");
+ free(mpt);
+ return -1;
+ }
+ mpt->buffer = cell2pointer(addr);
+
+ PUSH(addr);
+ PUSH(MPTSAS_DMA_SIZE);
+ PUSH(0);
+ feval("dma-map-in");
+ mpt->buffer_iova = POP();
+ if (mpt->buffer_iova == (uint64_t)-1) {
+ DPRINTF("Can't map MPT DMA buffer\n");
+ PUSH(addr);
+ PUSH(MPTSAS_DMA_SIZE);
+ feval("dma-free");
+ free(mpt);
+ return -1;
+ }
+
+ set_int_property(ph, "#address-cells", 1);
+ set_int_property(ph, "#size-cells", 0);
+
+ mpt->regs = regs;
+
+ /* Leave every interrupt masked: replies are read out of the FIFO. */
+ mptsas_reg_write(mpt, MPI_HOST_INTERRUPT_MASK, MPI_HIM_RIM | MPI_HIM_DIM);
+
+ if (mptsas_ioc_reset(mpt) || mptsas_ioc_init(mpt) ||
+ mptsas_port_enable(mpt)) {
+ PUSH(addr);
+ PUSH(MPTSAS_DMA_SIZE);
+ feval("dma-free");
+ free(mpt);
+ return -1;
+ }
+ mptsas_post_reply_frames(mpt);
+
+ /* Scan the SCSI bus */
+ for (id = 0; id < MPTSAS_MAX_TARGETS; id++) {
+ mpt->sd[id].id = id;
+ if (!inquiry(mpt, &mpt->sd[id])) {
+ DPRINTF("Unit %d not present\n", id);
+ continue;
+ }
+
+ /* Clear Unit Attention condition from reset */
+ for (i = 0; i < 5; i++) {
+ if (test_unit_ready(mpt, &mpt->sd[id])) {
+ break;
+ }
+ }
+ if (i == 5) {
+ DPRINTF("Unit %d present but won't become ready\n", id);
+ continue;
+ }
+ DPRINTF("Unit %d present\n", id);
+ read_capacity(mpt, &mpt->sd[id]);
+ }
+
+ for (id = 0; id < MPTSAS_MAX_TARGETS; id++) {
+ if (!mpt->sd[id].present)
+ continue;
+
+ mpt->sd[id].mpt = mpt;
+
+ fword("new-device");
+ push_str("sd");
+ fword("device-name");
+ push_str("block");
+ fword("device-type");
+ fword("is-deblocker");
+ PUSH(id);
+ fword("encode-int");
+ PUSH(0);
+ fword("encode-int");
+ fword("encode+");
+ push_str("reg");
+ fword("property");
+
+ PUSH(pointer2cell(&mpt->sd[id]));
+ feval("value sd-private");
+
+ BIND_NODE_METHODS(get_cur_dev(), ob_sd);
+ fword("finish-device");
+
+ snprintf(nodebuff, sizeof(nodebuff), "%s/sd@%d",
+ get_path_from_ph(ph), id);
+
+ if (mpt->sd[id].media == TYPE_ROM) {
+ counter_ptr = &cdcount;
+ } else {
+ counter_ptr = &diskcount;
+ }
+ if (*counter_ptr == 0) {
+ add_alias(nodebuff, mpt->sd[id].media_str[0]);
+ add_alias(nodebuff, mpt->sd[id].media_str[1]);
+ }
+ snprintf(aliasbuff, sizeof(aliasbuff), "%s%d",
+ mpt->sd[id].media_str[0], *counter_ptr);
+ add_alias(nodebuff, aliasbuff);
+ snprintf(aliasbuff, sizeof(aliasbuff), "%s%d",
+ mpt->sd[id].media_str[1], *counter_ptr);
+ add_alias(nodebuff, aliasbuff);
+ (*counter_ptr)++;
+ }
+
+ return 0;
+}
diff --git a/drivers/mptsas.h b/drivers/mptsas.h
new file mode 100644
index 00000000..aedff86a
--- /dev/null
+++ b/drivers/mptsas.h
@@ -0,0 +1,140 @@
+/*
+ * Fusion-MPT message definitions
+ *
+ * Copyright (C) 2026 Imre Kaloz <kaloz(a)kernel.org>
+ *
+ * The subset of the MPI 1.5 interface a boot-time initiator uses: the
+ * system interface registers, the doorbell handshake, and the three
+ * message frames needed to bring the IOC up and read from a target.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * version 2
+ *
+ */
+
+#ifndef MPTSAS_H
+#define MPTSAS_H
+
+/* System interface registers, offsets into the IOC's memory BAR. */
+#define MPI_DOORBELL 0x00
+#define MPI_WRITE_SEQUENCE 0x04
+#define MPI_DIAGNOSTIC 0x08
+#define MPI_HOST_INTERRUPT_STATUS 0x30
+#define MPI_HOST_INTERRUPT_MASK 0x34
+#define MPI_REQUEST_POST_FIFO 0x40
+/* Reply post FIFO read, reply free FIFO written, at the same offset. */
+#define MPI_REPLY_POST_FIFO 0x44
+#define MPI_REPLY_FREE_FIFO 0x44
+
+/* Doorbell: IOC state and fault code when read, a command when written. */
+#define MPI_DOORBELL_ACTIVE 0x08000000
+#define MPI_DOORBELL_DATA_MASK 0x0000ffff
+#define MPI_DOORBELL_FUNCTION_SHIFT 24
+#define MPI_DOORBELL_ADD_DWORDS_SHIFT 16
+
+#define MPI_IOC_STATE_MASK 0xf0000000
+#define MPI_IOC_STATE_READY 0x10000000
+#define MPI_IOC_STATE_OPERATIONAL 0x20000000
+
+#define MPI_HIS_IOP_DOORBELL_STATUS 0x80000000
+#define MPI_HIS_REPLY_MESSAGE_INTERRUPT 0x00000008
+#define MPI_HIS_DOORBELL_INTERRUPT 0x00000001
+
+#define MPI_HIM_RIM 0x00000008
+#define MPI_HIM_DIM 0x00000001
+
+/* A reply posted as a frame address rather than as a message context. */
+#define MPI_ADDRESS_REPLY_A_BIT 0x80000000
+
+#define MPI_FUNCTION_SCSI_IO_REQUEST 0x00
+#define MPI_FUNCTION_IOC_INIT 0x02
+#define MPI_FUNCTION_PORT_ENABLE 0x06
+#define MPI_FUNCTION_IOC_MESSAGE_UNIT_RESET 0x40
+#define MPI_FUNCTION_HANDSHAKE 0x42
+
+#define MPI_WHOINIT_ROM_BIOS 0x02
+
+#define MPI_IOCSTATUS_MASK 0x7fff
+#define MPI_IOCSTATUS_SUCCESS 0x0000
+#define MPI_IOCSTATUS_SCSI_DEVICE_NOT_THERE 0x0043
+#define MPI_IOCSTATUS_SCSI_DATA_UNDERRUN 0x0045
+
+/* Scatter/gather element flags, in the high byte of FlagsLength. */
+#define MPI_SGE_FLAGS_LAST_ELEMENT 0x80000000
+#define MPI_SGE_FLAGS_END_OF_BUFFER 0x40000000
+#define MPI_SGE_FLAGS_SIMPLE_ELEMENT 0x10000000
+#define MPI_SGE_FLAGS_64_BIT_ADDRESSING 0x02000000
+#define MPI_SGE_FLAGS_END_OF_LIST 0x01000000
+
+#define MPI_SCSIIO_CONTROL_NODATATRANSFER 0x00000000
+#define MPI_SCSIIO_CONTROL_READ 0x02000000
+
+/* Every message frame and reply is little-endian on the wire. */
+
+typedef struct mpi_ioc_init {
+ uint8_t WhoInit;
+ uint8_t Reserved;
+ uint8_t ChainOffset;
+ uint8_t Function;
+ uint8_t Flags;
+ uint8_t MaxDevices;
+ uint8_t MaxBuses;
+ uint8_t MsgFlags;
+ uint32_t MsgContext;
+ uint16_t ReplyFrameSize;
+ uint8_t Reserved1[2];
+ uint32_t HostMfaHighAddr;
+ uint32_t SenseBufferHighAddr;
+ uint32_t ReplyFifoHostSignalingAddr;
+ uint32_t HostPageBufferSGEFlagsLength;
+ uint64_t HostPageBufferSGEAddress;
+ uint16_t MsgVersion;
+ uint16_t HeaderVersion;
+} __attribute__((packed, aligned(4))) mpi_ioc_init_t;
+
+typedef struct mpi_port_enable {
+ uint8_t Reserved[2];
+ uint8_t ChainOffset;
+ uint8_t Function;
+ uint8_t Reserved1[2];
+ uint8_t PortNumber;
+ uint8_t MsgFlags;
+ uint32_t MsgContext;
+} __attribute__((packed, aligned(4))) mpi_port_enable_t;
+
+typedef struct mpi_scsi_io {
+ uint8_t TargetID;
+ uint8_t Bus;
+ uint8_t ChainOffset;
+ uint8_t Function;
+ uint8_t CDBLength;
+ uint8_t SenseBufferLength;
+ uint8_t Reserved;
+ uint8_t MsgFlags;
+ uint32_t MsgContext;
+ uint8_t LUN[8];
+ uint32_t Control;
+ uint8_t CDB[16];
+ uint32_t DataLength;
+ uint32_t SenseBufferLowAddr;
+ /* One simple 64-bit element, the only scatter/gather list we build. */
+ uint32_t SGEFlagsLength;
+ uint64_t SGEAddress;
+} __attribute__((packed, aligned(4))) mpi_scsi_io_t;
+
+/* The header every reply shares, and the SCSI I/O reply's status fields. */
+typedef struct mpi_reply {
+ uint8_t Reserved;
+ uint8_t Reserved1;
+ uint8_t MsgLength;
+ uint8_t Function;
+ uint8_t Reserved2[4];
+ uint32_t MsgContext;
+ uint8_t SCSIStatus;
+ uint8_t SCSIState;
+ uint16_t IOCStatus;
+ uint32_t IOCLogInfo;
+} __attribute__((packed, aligned(4))) mpi_reply_t;
+
+#endif /* MPTSAS_H */
diff --git a/drivers/pci.c b/drivers/pci.c
index 286d7324..c11f8087 100644
--- a/drivers/pci.c
+++ b/drivers/pci.c
@@ -1270,6 +1270,23 @@ int lsi53c810_config_cb(const pci_config_t *config)
return 0;
}
+int mptsas_config_cb(const pci_config_t *config)
+{
+#ifdef CONFIG_DRIVER_MPTSAS
+ /* Enable PCI bus mastering */
+ ob_pci_enable_bus_master(config);
+
+ /* BAR 1 holds the MPI system interface registers. mptsas_reg_read/
+ write reach them through out_le32/in_le32, which store via
+ ASI_PHYS_BYPASS and so need the raw bus address, not the
+ ofmem-mapped pointer ob_pci_map() hands back for byte-array MMIO
+ like lsi53c810's. */
+ ob_mptsas_init(arch->host_pci_base +
+ (config->assigned[1] & ~0x0000000F));
+#endif
+ return 0;
+}
+
void ob_pci_enable_bus_master(const pci_config_t *config)
{
/* Enable bus mastering for the PCI device */
diff --git a/drivers/pci_database.c b/drivers/pci_database.c
index 64d8d34f..a5040e31 100644
--- a/drivers/pci_database.c
+++ b/drivers/pci_database.c
@@ -70,6 +70,14 @@ static const pci_dev_t scsi_devices[] = {
0, 0, 0,
lsi53c810_config_cb, NULL,
},
+ {
+ /* LSI SAS1068 controller */
+ PCI_VENDOR_ID_LSI_LOGIC, PCI_DEVICE_ID_LSI_SAS1068,
+ NULL, "scsi", NULL,
+ "pci1000,54\0",
+ 0, 0, 0,
+ mptsas_config_cb, NULL,
+ },
{
0xFFFF, 0xFFFF,
NULL, NULL, NULL, NULL,
diff --git a/drivers/pci_database.h b/drivers/pci_database.h
index e39ebfbc..050f6c11 100644
--- a/drivers/pci_database.h
+++ b/drivers/pci_database.h
@@ -45,6 +45,7 @@ extern int rtl8139_config_cb(const pci_config_t *config);
extern int sungem_config_cb (const pci_config_t *config);
extern int sunhme_config_cb(const pci_config_t *config);
extern int lsi53c810_config_cb(const pci_config_t *config);
+extern int mptsas_config_cb(const pci_config_t *config);
static inline int pci_compat_len(const pci_dev_t *dev)
{
diff --git a/include/drivers/drivers.h b/include/drivers/drivers.h
index df1bcfa2..bf34c505 100644
--- a/include/drivers/drivers.h
+++ b/include/drivers/drivers.h
@@ -76,6 +76,10 @@ int ob_esp_init(unsigned int slot, uint64_t base, unsigned long espoffset,
/* drivers/lsi.c */
int ob_lsi_init(const char *path, uint64_t mmio, uint64_t ram);
#endif
+#ifdef CONFIG_DRIVER_MPTSAS
+/* drivers/mptsas.c */
+int ob_mptsas_init(uint64_t regs);
+#endif
#ifdef CONFIG_DRIVER_OBIO
/* drivers/obio.c */
int ob_obio_init(uint64_t slavio_base, unsigned long fd_offset,
diff --git a/include/drivers/pci.h b/include/drivers/pci.h
index ed5bc888..3bf8a9f4 100644
--- a/include/drivers/pci.h
+++ b/include/drivers/pci.h
@@ -193,6 +193,7 @@ extern const pci_arch_t *arch;
#define PCI_VENDOR_ID_LSI_LOGIC 0x1000
#define PCI_DEVICE_ID_LSI_53C810 0x0001
+#define PCI_DEVICE_ID_LSI_SAS1068 0x0054
#define PCI_VENDOR_ID_ATI 0x1002
#define PCI_DEVICE_ID_ATI_RAGE128_PF 0x5046
base-commit: 4edb87ec19434720d0a756d98b5bdd45aec8c3bc
--
2.47.3
1
0
Four defects in the sparc64 client interface. None is specific to a
machine or a CPU, and all four sit in code that predates the boards
whose bring-up found them.
An unimplemented service leaves the caller's return cells untouched, so
a client that reads them back rather than the call's return value gets
its own uninitialised argument block. handle_calls() stores the
call-method catch result even when the caller declared nret == 0,
running one cell past the end of that block: OpenBSD's prom_map_phys()
builds exactly such a block, and the overrunning store lands on the
function's RETGUARD cookie and fires a stack-smash panic inside
pmap_bootstrap(). That is why no installed OpenBSD/sparc64 kernel boots
on this firmware today, on any machine and any CPU; only the install
RAMDISK kernel does.
The boot service printed "BOOT" and returned. IEEE 1275-1994 6.3.2.5 has
it restart the machine without returning, and OpenBSD, NetBSD and Linux
each panic when it returns; it now restarts through reset-all.
getproplen gains the NULL-phandle guard getprop already has, which is
what OpenBSD's pci_make_tag() reaches when it walks OF_child() past an
empty bridge's last populated slot.
Tested on QEMU's -M sun4u with the three companion series applied:
installed OpenBSD 7.9 and Solaris 10 1/13 kernels reach a login prompt,
and a reboot brings the firmware banner back.
Based on e5ac46dd ("config/scripts/switch-arch: fix build on aarch64 and
riscv64 hosts"); depends on nothing else.
Imre Kaloz (4):
ciface.fs: return -1 from getproplen on a NULL phandle
ciface.fs: implement the boot service
client.c: do not store the call-method catch result when nret is zero
client.c: return -1 in the return cells of an unimplemented service
forth/system/ciface.fs | 9 ++++++++-
libopenbios/client.c | 15 ++++++++++++---
2 files changed, 20 insertions(+), 4 deletions(-)
base-commit: 15c6d8b41f980ef4950dfa472ecf8e7ac7791a5d
--
2.47.3
1
4
Sept. 27, 2026
Five ofmem defects that surface once a machine has more than 4 GiB of
RAM and the firmware's own arena sits at the top of it. An UltraSPARC
IIIi system is the platform here that reaches them; real sun4u-class
machines never carried that much memory.
align_ptr() built its alignment mask in size_t and ANDed it against the
wider phys_addr_t, clearing every address bit above bit 31, so the first
aligned allocation truncated its own return address and the firmware
aborted with "out of malloc memory (28)!".
An any-address claim picked a physical address bounded only by RAM size
and then reused it as the virtual address, landing in the firmware's own
fixed translations; it is now bounded by the top of the client's virtual
space and retries lower when the virtual claim still collides. A
physical claim searches below 4 GiB first, which is the order a real
board's disjoint memory banks give OBP's own allocator.
Releasing a claim gave the whole range back even where a translation
still covered part of it, so a live-mapped page reappeared in /memory's
"available" property; the released range is rebuilt around any sub-range
a translation still covers. And the virtual "available" list stopped at
the top of the range client allocations come from, so mappings above it
left no hole, which is what Solaris sizes its nucleus hblk8 pool from.
Based on e5ac46dd ("config/scripts/switch-arch: fix build on aarch64 and
riscv64 hosts").
Imre Kaloz (5):
ofmem: give the virtual "available" property its own upper bound
ofmem: keep the full physical address when aligning
ofmem: keep any-address claims inside the virtual space
ofmem: keep any-address physical claims under 4 GiB first
ofmem: keep a mapped page claimed when its claim is released
arch/ppc/ofmem.c | 5 +
arch/ppc/qemu/ofmem.c | 5 +
arch/sparc32/ofmem_sparc32.c | 5 +
arch/sparc64/ofmem_sparc64.c | 10 ++
include/arch/sparc64/ofmem_sparc64.h | 4 +
include/libopenbios/ofmem.h | 1 +
libopenbios/ofmem_common.c | 146 ++++++++++++++++++++++++---
7 files changed, 162 insertions(+), 14 deletions(-)
base-commit: bbb45c18f0f3f910e58bcad1695274690f6c1d9b
--
2.47.3
1
5
Sept. 27, 2026
Six independent PCI defects, all in generic code.
Two are bridge windows closed during the scan below them. With 16-bit
I/O decoding a bridge's scan limit past 0xffff loses its carry and lands
below the base; the memory limit register comes out of reset below its
base and stays there while the secondary bus is scanned, so a driver
reaching its own registers from a configuration callback finds the
window shut.
A host bridge that gives each function 4 kB of configuration space moves
the bus, device and function fields four bits left, which the
configuration-address shift now comes from the bridge rather than from a
constant.
"latency-timer" joins min-grant and max-latency on every PCI node, which
is the only way a client can read it. And the e1000 entry published
"pci1086,100e" as its compatible property, a transposition of Intel's
0x8086 that matches no OS's 1275 binding.
A PCI-to-PCI bridge published "bus-range" and no "ranges". Since Linux
commit 92427ab4378f the kernel no longer claims an OF-described
bridge's I/O window without one, and the devices behind it get no I/O
BARs, so such a bridge now publishes its windows identity-mapped, as
Sun's OBP does.
Based on e5ac46dd ("config/scripts/switch-arch: fix build on aarch64 and
riscv64 hosts").
Imre Kaloz (6):
pci: publish "latency-timer" on PCI nodes
pci: take the configuration-address shift from the host bridge
pci: keep a bridge's I/O window open across its secondary bus scan
pci: hold a bridge's memory window open across its secondary bus scan
e1000: fix the vendor id in the compatible property
pci: publish "ranges" on PCI-PCI bridges
drivers/pci.c | 53 ++++++++++++++++++++++++++++++++++++--
drivers/pci.h | 1 +
drivers/pci_database.c | 2 +-
include/arch/sparc64/pci.h | 7 ++---
include/drivers/pci.h | 3 +++
5 files changed, 60 insertions(+), 6 deletions(-)
base-commit: 9c11be7f8eb089f19ef333946535fe5d45aacf9f
--
2.47.3
1
6