Skip to content

net: ps2: PlayStation 2 SMAP Ethernet driver - #95

Closed
hubix94 wants to merge 5 commits into
frno7:ps2-mainfrom
hubix94:ps2-smap
Closed

net: ps2: PlayStation 2 SMAP Ethernet driver#95
hubix94 wants to merge 5 commits into
frno7:ps2-mainfrom
hubix94:ps2-smap

Conversation

@hubix94

@hubix94 hubix94 commented Sep 7, 2026

Copy link
Copy Markdown

Follow-up to #94, as agreed there. The two DEV9 fixes come back as part of the
work they were written for, with the network driver on top.

What this is

An EE-side port of Sony's linux-2.4.17/drivers/ps2/smap.c (GPL v2, taken from
the BlackRhino kit, cross-checked against the 2.2.1 driver on the PlayStation 2
Linux DISC2) onto 5.4. Sony's are the register layout, the FIFO and
buffer-descriptor protocol, the EMAC3 default values and the EEPROM bit-bang
sequence. New for 5.4 is the structure: net_device_ops, NAPI, phylib with its
own MDIO bus instead of the original link-check thread, and
netdev_alloc_skb_ip_align().

Five commits:

Commit
MIPS: PS2: IOP: Fix iop_write{b,w,l}() issuing the LOADFILE read RPC the typo you called a valid fix in #94
MIPS: PS2: IOP: Actually initialise the DEV9 expansion bay removes the early return that left exp_dev_init() as dead code
net: ps2: Add PlayStation 2 SMAP Ethernet driver drivers/net/ethernet/ps2/, own Kconfig and Makefile
MIPS: PS2: Register the SMAP Ethernet device the platform device it binds to, CONFIG_PS2_SMAP=m in ps2_defconfig
net: ps2: Close the interface on machine shutdown .shutdown; without it, resetting the IOP panics the kernel in the driver's timer

The chip window is shared with the ATA registers, so the platform device
declares the two ranges SMAP actually uses rather than the whole window.
Claiming all of it collides with the ATA device registered a few lines earlier,
and the SMAP device then never registers at all. On hardware:

14000000-1400003f : speed
14000040-1400005f : PATA
14000100-14003fff : smap

Measured on hardware

SCPH-30004 (PAL, ROM 0150), Network Adaptor in the expansion bay, over TCP with
SSH on top, so the figures include encryption on the same CPU:

Transmit 2.32 MB/s (~18.5 Mbit/s), 30 MB in 13.5 s
Receive 1.41 MB/s (~11 Mbit/s), 30 MB in 22.3 s
Interrupts over those 60 MB 24817 TXEND, 16254 RXEND, 0 EMAC3, no stall, no fallback to polling
Interface counters after it rx_errors 0 tx_errors 0, and on close: 29115 rx, 26504 tx, 41402 interrupts, 30539 dnv cleared, ran on interrupts
Longest run 367 MB over 19 minutes, 0 interface errors
Module reload rmmod + insmod works without rebooting the console
modprobe pata_ps2 with the network up ata1: PATA max UDMA/66 irq 110, network untouched, 2.23 MB/s against 2.32 without it
Cold boot from the kernel image DHCP lease and sshd at 20 s from kernel start, no keyboard involved

The transmit, receive, reload and interrupt figures above were re-measured on
exactly the commits in this branch. The 367 MB run and the pata_ps2 test are
from a build a day older whose driver differs only in comment wrapping.

Two details from that testing that may be worth knowing regardless of this
driver's fate:

  • ata.irx finds the bay already powered by iop-dev9 (power is already on)
    and skips its own sequence, and the interrupts are disjoint - 110 for ATA
    against 114/115/116 for SMAP. That is why the two coexist.
  • poweroff -f still works with the bay powered on, at least on this model.
    reboot / halt / shutdown support #67 suggested it might not.

On your comments in #94

An IOP counterpart: agreed, and not written yet. This is the EE-only
prototype you called a good starting point, and everything it does over the SIF
is a candidate to move to the IOP later - DEV9 initialisation the way ata.c
does it, interrupt handling, and eventually DMA, which is what the throughput
figures above are really costing.

Retiring drivers/ps2/iop-dev9.c: yes, once the IOP module exists. The
driver needs the bay powered before it touches the register window; today
iop-dev9 is the only thing in the tree that does it at boot. A smap.irx with
its own dev9_init() would take that over and the file could go. Until then the
second commit here keeps it working rather than deepening the dependency - it
deletes a return 0;, nothing more.

The interrupt relay. Understood, and it matches what the driver ran into:
with no irq_mask/irq_unmask in iop-irq.c the driver cannot mask its own
source for the duration of a NAPI poll, so it acknowledges through
SMAP_INTR_CLR and relies on the IOP re-arming. It works, and one thing that
surprised me is worth recording: the IOP clears only STAT & MASK, so the
TXDNV/RXDNV bits - which are not in its mask - stay set and hold the line
high. Clearing them from the EE is what took this driver from dying under load
to surviving 367 MB. Over that run they were set 258323 times.

Known gaps

  • No DMA. Sony's driver moved data with SmapDmaWrite/SmapDmaRead on the
    IOP side; this one does 32-bit PIO from the EE through a bounce buffer,
    because skb data is only 2-byte aligned.
  • Only tested on SCPH-30004. Nothing is model-aware: the PHY is hardwired to
    MDIO address 1 and the register map is the adapter's. SCPH-700xx is untested.
    Probe does refuse cleanly on unexpected hardware - it bails out if SPEED
    rev1 reads 0000/ffff, or if rev3 lacks the SMAP capability bit.
  • No multicast filter. The first group switches the interface to
    all-multicast, as the original did.
  • rx_dropped is not zero, and it rises only where
    netdev_alloc_skb_ip_align() fails, so it is memory pressure on a 32 MB
    machine rather than a data-path fault. Under traffic it is small - 60 frames
    out of 28529 received, 0.2% - but it is much worse during boot, 101 out of the
    first 226 frames, when the machine is down to 9 MB free. A receive path that
    recycled buffers instead of allocating one per frame would not care.
  • A polling fallback is still in the driver, behind the poll module
    parameter. It was written when the relay had never delivered a SPEED interrupt
    to the EE and an interrupt-only driver would have said nothing about whether
    the data path worked. Since the TXDNV fix it has not been needed. Happy to
    remove it if you would rather not carry it.

checkpatch

0 errors and nothing about line length. What is left is 32 of "networking block
comments don't use an empty /* line" - the rest of this tree,
drivers/ps2/iop-dev9.c and drivers/ata/pata_ps2.c included, uses the
ordinary kernel comment style, so I matched the tree rather than the netdev
rule. Say which you would prefer and I will convert them. Sixteen lines still
run past 80 columns, all of them log messages that
Documentation/process/coding-style.rst says not to break.

Provenance

Sony's code is GPL v2 and the file records where each part came from, with the
2.4.17 line numbers for the parts that are ports rather than rewrites.
MODULE_AUTHOR credits Sony alongside me. As in #94, the commits carry
Co-Authored-By for the AI assistance I use, and the caveat from my first
message stands: treat the measurements as solid and anything I say about causes
as a suggestion that may be wrong.

🤖 Generated with Claude Code

Hubert Wyrzykiewicz and others added 5 commits September 5, 2026 17:06
iop_rpc_write() sends its { addr, type, data } argument with rpo_get_addr
(3) instead of rpo_set_addr (2). LOADFILE therefore performs a read at
addr, discards data, and returns the current value, which iop_rpc_write()
passes on as a non-negative "status". Every caller that tests for err < 0
sees success while nothing has been written.

Measured on an SCPH-30004 (ROM 0150) by replaying the exp_dev_init()
sequence from iop-dev9.c through iop_writel()/iop_writew() and reading the
registers back over the SIF: with rpo_get_addr all writes "succeed" and
the SSBUS registers 0x1418/0x141c/0x1420 keep their reset values
000000ff/001a1055/000510ff and DEV9 power stays 0000; with rpo_set_addr
the same sequence reads back e01a3043/ef1a3043/00051011 and DEV9 power
becomes 0005, the expansion bay powers up and the SPEED chip answers.

Nothing in the tree besides the dead code in iop_dev9_init() calls the
write helpers yet, which is why this went unnoticed.

Fixes: 2e3a838 ("FIXME: iop_read[bwl] and iop_write[bwl]")

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Hubert Wyrzykiewicz <h.wyrzyk@gmail.com>
iop_dev9_init() returns right after iop_module_request("dev9", ...),
leaving exp_dev_init() -- the SSBUS setup, bay power-up and reset -- as
dead code. The expansion bay therefore stays unpowered and the SPEED chip
(ATA and SMAP Ethernet) is unreachable: a read of its registers from the
EE at 0xb4000000 raises a data bus error.

Remove the early return so the module does what it was written to do.
Together with the iop_rpc_write() fix this brings the bay up at module
load. Measured on an SCPH-30004 (ROM 0150): DEV9 power register 0000 ->
0005, SPEED rev1 0011 rev3 0003 rev8 0002, EMAC3 soft reset completes,
PHY DP83846 rev 3 answers on MII address 1 and reports link up at 100 Mbit
full duplex with a cable plugged in and link down without, the MAC
address reads from the serial EEPROM with a matching checksum, and the
EE can read the SPEED registers directly once the bay is powered.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Hubert Wyrzykiewicz <h.wyrzyk@gmail.com>
The SMAP part of the SPEED chip in the PlayStation 2 expansion bay is an
IBM EMAC3 MAC with a National DP83846 PHY on MII address 1, a 4 KiB TX
FIFO, a 16 KiB RX FIFO and two rings of 64 buffer descriptors, all
memory-mapped at physical 0x14000000 on the EE side. The window answers
once the DEV9 expansion bay is powered; the driver checks that through
the IOP before it touches the window, because an unpowered bay turns
every EE access into a data bus error.

The register layout, the FIFO and buffer-descriptor protocol, the EMAC3
defaults and the EEPROM bit-bang sequence come from Sony's driver in the
PlayStation 2 Linux kit, linux-2.4.17/drivers/ps2/smap.c, GPL v2. The
driver structure is new for 5.4: net_device_ops, NAPI, and phylib with
its own MDIO bus instead of the original link-check thread.

Data moves by programmed I/O, 32-bit accesses through a bounce buffer
because skb data is only 2-byte aligned. The IOP-side DMA of the
original driver is not ported. Measured on an SCPH-30004 over TCP with
SSH on top: 2.4 MB/s transmit and 1.4 MB/s receive, sustained over
300 MB.

The three SPEED interrupts reach the EE through the IOP interrupt relay.
Because the SPEED interrupt mask register belongs to the IOP, the driver
never touches it and acknowledges its sources through SMAP_INTR_CLR, as
the original did. It also clears TXDNV and RXDNV, which the IOP side
does not acknowledge; left standing they keep the interrupt line
asserted and the interface stops receiving interrupts under load.

A poll parameter selects interrupt mode with a polling fallback
(default), interrupts only, or polling only. The fallback exists because
the interrupt relay for a bay device had never been exercised, and an
interrupt-only driver that stays silent says nothing about whether the
data path works.

Signed-off-by: Hubert Wyrzykiewicz <h.wyrzyk@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Add the platform device the SMAP driver binds to: the 16 KiB SPEED
register window at physical 0x14000000, which holds the registers, both
FIFO data ports and the two buffer descriptor rings, and the three
interrupts relayed from the IOP, named rx, tx and emac3 so the driver
does not depend on their order.

Enable the driver as a module in ps2_defconfig.

Signed-off-by: Hubert Wyrzykiewicz <h.wyrzyk@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Restarting the machine resets the I/O processor, and with it the DEV9
expansion bay: the SPEED register window stops answering and every access
from the EE raises a data bus error. The driver's watchdog timer walks
into that half a second later, in interrupt context:

  reboot: Restarting system
  Data bus error, epc == c0024020, ra == c0024398
  epc : readw+0x8/0x18 [ps2_smap]
  ra  : smap_timer_fn+0x44/0x1bc [ps2_smap]
  Kernel panic - not syncing: Fatal exception in interrupt

Add a shutdown callback that closes the interface while the hardware is
still there to be closed, the way ohci-ps2 has always done through
usb_hcd_platform_shutdown(). device_shutdown() runs it before the machine
is restarted, so the timer is stopped, the interrupts are released and the
MACs are disabled before anything touches the bay.

Signed-off-by: Hubert Wyrzykiewicz <h.wyrzyk@gmail.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@hubix94

hubix94 commented Sep 7, 2026

Copy link
Copy Markdown
Author

Closed in favour of #96. The commits needed their authorship metadata corrected, and that changes every commit ID, so opening a fresh pull request was cleaner than force-pushing under this one. Same code and same measurements - the only difference is a dropped copyright line, since the driver is a port of Sony's. Sorry for the noise.

@hubix94 hubix94 closed this Sep 7, 2026
@hubix94
hubix94 deleted the ps2-smap branch September 7, 2026 10:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant