Linux's own r8169 through LDLS, deep idle, and the GPU plan
The RTL8168 is now driven by Linux's r8169, unmodified, in a process. `make ldls-r8169` stages it from the running kernel together with libphy, mdio-bus, mdio_devres and the Realtek PHY driver, the kernel's BTF and the RTL PHY firmware, and etc/schinit/ldls-r8169.job starts it at boot with its log in /var/log/ldls-r8169.log. The native driver is out of the registry; its code, now with Linux's 8168H setup, stays as the fallback. LDLS grows what that needed: - ldlsd loads a comma-separated module set in dependency order, each linking against the ones before it, with BTF read before any init_module runs; - a driver core (bus match, driver probe), devres, kernel printf, real udelay/msleep/ktime and 1000 Hz jiffies, phylib's bitmaps and link_mode_params, firmware from /lib/firmware, and the symbols whose stub zero was a lie (list validity, try_module_get, dev_err_probe, eth_platform_get_mac_address); - pdev->bus is a real pci_bus, and cmovcc from per-CPU memory is rewritten like the other %gs forms. In QEMU all five modules load, link and initialise, and r8169 registers and finds no device. Probe and traffic are untested until real hardware. The kernel turns ASPM off on Realtek NIC links at boot, as r8169 does through pci_disable_link_state, which a process cannot. Idle CPUs enter ACPI _CST's C2 (CC6 on Zen) by port read or MWAIT when the APIC timer keeps running, instead of only ever halting at C1. docs/plan/gpu.md: amdgpu and nouveau through LDLS, with measurements. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S1giNvrHTrPMPw7V6x1kQ4
This commit is contained in:
parent
438e7af141
commit
4ad49ed5f2
18 changed files with 1945 additions and 81 deletions
|
|
@ -49,6 +49,14 @@
|
|||
#define R_RCR 0x44 // Rx config
|
||||
#define R_9346CR 0x50 // unlocks the config registers
|
||||
#define R_CONFIG1 0x52
|
||||
#define R_CONFIG2 0x53
|
||||
#define R_CONFIG3 0x54
|
||||
#define R_CONFIG5 0x56
|
||||
#define R_EEE_LED 0x1B
|
||||
#define R_EPHYAR 0x80 // the PCIe PHY's registers
|
||||
#define R_DLLPR 0xD0
|
||||
#define R_MISC1 0xF2
|
||||
#define R_INTRMIT 0xE2 // interrupt mitigation
|
||||
#define R_PHYAR 0x60 // the MII window
|
||||
#define R_PHYSTATUS 0x6C
|
||||
#define R_PMCH 0x6F // PLL power
|
||||
|
|
@ -107,6 +115,11 @@
|
|||
// MXDMA short. It would have worked and been slower, which is the worst kind
|
||||
// of wrong to leave in a register constant.
|
||||
#define RCR_UNLIMITED 0x0000E700
|
||||
// What Linux's rtl_init_rxcfg() writes on the 8168G and later instead:
|
||||
// 128-byte early interrupt, multiple descriptors per DMA, unlimited burst,
|
||||
// early receive off.
|
||||
#define RCR_GEN_G (0x8000 | 0x4000 | 0x0700 | 0x0800)
|
||||
#define TCR_AUTO_FIFO 0x00000080 // 8168E-VL and later: the chip sizes its FIFOs
|
||||
|
||||
#define TCR_IFG_STD 0x03000000
|
||||
#define TCR_MXDMA 0x00000700
|
||||
|
|
@ -282,7 +295,21 @@ mac_ocp_write(struct r8169 *r, uint16 reg, uint16 val)
|
|||
wr32(r, R_MAC_OCP, OCP_FLAG | ((uint32)(reg >> 1) << 16) | val);
|
||||
}
|
||||
|
||||
// The extended (ERI) registers, all four bytes at once.
|
||||
// The extended (ERI) registers. `mask` picks the bytes, bit 0 the lowest:
|
||||
// several of these registers share a word with ones that must not change.
|
||||
static void
|
||||
eri_write_mask(struct r8169 *r, int addr, int mask, uint32 val)
|
||||
{
|
||||
wr32(r, R_ERIDR, val);
|
||||
wr32(r, R_ERIAR, OCP_FLAG | ((uint32)mask << 12) | addr);
|
||||
for (int i = 0; i < 100; i++) {
|
||||
microdelay(10);
|
||||
if (!(rd32(r, R_ERIAR) & OCP_FLAG))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// All four bytes at once.
|
||||
static uint32
|
||||
eri_read(struct r8169 *r, int addr)
|
||||
{
|
||||
|
|
@ -315,7 +342,7 @@ r8169_gen_g(uint32 xid)
|
|||
switch (xid) {
|
||||
case 0x4C0: case 0x4C1: case 0x5C8: case 0x509:
|
||||
case 0x500: case 0x501: case 0x502:
|
||||
case 0x540: case 0x541:
|
||||
case 0x540: case 0x541: case 0x6C0:
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
|
|
@ -347,6 +374,202 @@ r8169_takeover_g(struct r8169 *r)
|
|||
wait_ll_ready(r);
|
||||
}
|
||||
|
||||
// --- the 8168H as Linux sets it up ------------------------------------------
|
||||
//
|
||||
// From Linux 6.18 drivers/net/ethernet/realtek/r8169_main.c
|
||||
// (rtl_hw_start_8168h_1, rtl_hw_aspm_clkreq_enable) and r8169_phy_config.c
|
||||
// (rtl8168h_2_hw_phy_config), GPL-2.0, carried over register for register.
|
||||
// Without them the chip runs on its power-on FIFO sizes and PHY tuning and
|
||||
// with ASPM as the firmware left it, and moves frames, slowly and with drops.
|
||||
// The one part left out is the PHY firmware patch (rtl_nic/rtl8168h-2.fw),
|
||||
// which needs the root filesystem this runs before.
|
||||
|
||||
static void
|
||||
ephy_write(struct r8169 *r, int reg, uint16 val)
|
||||
{
|
||||
wr32(r, R_EPHYAR, 0x80000000u | ((uint32)(reg & 0x1F) << 16) | val);
|
||||
for (int i = 0; i < 100; i++) {
|
||||
microdelay(10);
|
||||
if (!(rd32(r, R_EPHYAR) & 0x80000000u))
|
||||
break;
|
||||
}
|
||||
microdelay(10);
|
||||
}
|
||||
|
||||
static uint16
|
||||
ephy_read(struct r8169 *r, int reg)
|
||||
{
|
||||
wr32(r, R_EPHYAR, (uint32)(reg & 0x1F) << 16);
|
||||
for (int i = 0; i < 100; i++) {
|
||||
microdelay(10);
|
||||
uint32 v = rd32(r, R_EPHYAR);
|
||||
if (v & 0x80000000u)
|
||||
return (uint16)v;
|
||||
}
|
||||
return 0xFFFF;
|
||||
}
|
||||
|
||||
static void
|
||||
mac_ocp_modify(struct r8169 *r, uint16 reg, uint16 clear, uint16 set)
|
||||
{
|
||||
mac_ocp_write(r, reg, (mac_ocp_read(r, reg) & ~clear) | set);
|
||||
}
|
||||
|
||||
static int
|
||||
phy_read_paged(struct r8169 *r, int page, int reg)
|
||||
{
|
||||
mii_write(r, 0x1F, (uint16)page);
|
||||
int v = mii_read(r, reg);
|
||||
mii_write(r, 0x1F, 0);
|
||||
return v;
|
||||
}
|
||||
|
||||
static void
|
||||
phy_write_paged(struct r8169 *r, int page, int reg, uint16 val)
|
||||
{
|
||||
mii_write(r, 0x1F, (uint16)page);
|
||||
mii_write(r, reg, val);
|
||||
mii_write(r, 0x1F, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
phy_modify_paged(struct r8169 *r, int page, int reg, uint16 clear, uint16 set)
|
||||
{
|
||||
mii_write(r, 0x1F, (uint16)page);
|
||||
int v = mii_read(r, reg);
|
||||
if (v >= 0)
|
||||
mii_write(r, reg, (uint16)((v & ~clear) | set));
|
||||
mii_write(r, 0x1F, 0);
|
||||
}
|
||||
|
||||
static void
|
||||
phy_param(struct r8169 *r, uint16 parm, uint16 clear, uint16 set)
|
||||
{
|
||||
mii_write(r, 0x1F, 0x0A43);
|
||||
mii_write(r, 0x13, parm);
|
||||
int v = mii_read(r, 0x14);
|
||||
if (v >= 0)
|
||||
mii_write(r, 0x14, (uint16)((v & ~clear) | set));
|
||||
mii_write(r, 0x1F, 0);
|
||||
}
|
||||
|
||||
// PCIe Link Control, low two bits: L0s and L1. Linux clears L1 on this chip
|
||||
// and on the bridge above it unless the vendor has marked it safe, for "random
|
||||
// device stop working problems as well as full system hangs". Firmware
|
||||
// leaves it on. A link that drops into L1 between frames costs a wake-up per
|
||||
// burst and on some boards stalls outright, which is a download that crawls.
|
||||
static void
|
||||
aspm_off(struct pci_dev *d)
|
||||
{
|
||||
uint8 cap = pci_find_cap(d, 0x10);
|
||||
if (cap) {
|
||||
uint32 v = pci_read32(d->bus, d->slot, d->func, cap + 0x10);
|
||||
pci_write32(d->bus, d->slot, d->func, cap + 0x10, v & ~3u);
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
r8169_aspm_off(struct r8169 *r)
|
||||
{
|
||||
struct pci_dev *d = r->pdev;
|
||||
|
||||
// The vendor's flag that ASPM is safe on this board (rtl_aspm_is_safe).
|
||||
if (mac_ocp_read(r, 0xC0B2) & 0xF)
|
||||
return;
|
||||
aspm_off(d);
|
||||
for (int i = 0; i < pci_count(); i++) {
|
||||
struct pci_dev *b = pci_get(i);
|
||||
if (b && (b->header_type & 0x7F) == 1 &&
|
||||
pci_read8(b->bus, b->slot, b->func, 0x19) == d->bus)
|
||||
aspm_off(b);
|
||||
}
|
||||
// And in the chip: no L1.2 trigger, no clock request, ASPM off.
|
||||
mac_ocp_modify(r, 0xE092, 0x00FF, 0);
|
||||
wr8(r, R_CONFIG2, rd8(r, R_CONFIG2) & ~0x80);
|
||||
wr8(r, R_CONFIG5, rd8(r, R_CONFIG5) & ~0x01);
|
||||
}
|
||||
|
||||
// rtl_hw_start_8168h_1. Config registers must be unlocked.
|
||||
static void
|
||||
r8169_start_8168h(struct r8169 *r)
|
||||
{
|
||||
static const struct { uint8 reg; uint16 clear, set; } ephy[] = {
|
||||
{ 0x1E, 0x0800, 0x0001 },
|
||||
{ 0x1D, 0x0000, 0x0800 },
|
||||
{ 0x05, 0xFFFF, 0x2089 },
|
||||
{ 0x06, 0xFFFF, 0x5881 },
|
||||
{ 0x04, 0xFFFF, 0x854A },
|
||||
{ 0x01, 0xFFFF, 0x068B },
|
||||
};
|
||||
for (unsigned i = 0; i < sizeof ephy / sizeof ephy[0]; i++)
|
||||
ephy_write(r, ephy[i].reg,
|
||||
(ephy_read(r, ephy[i].reg) & ~ephy[i].clear) | ephy[i].set);
|
||||
|
||||
eri_write(r, 0xC8, (0x08u << 16) | 0x02); // FIFO sizes
|
||||
eri_write(r, 0xE8, (0x10u << 16) | 0x06);
|
||||
eri_write_mask(r, 0xCC, 0x1, 0x38); // pause thresholds
|
||||
eri_write_mask(r, 0xD0, 0x1, 0x48);
|
||||
|
||||
eri_write(r, 0xDC, eri_read(r, 0xDC) & ~1u); // reset the packet filter
|
||||
eri_write(r, 0xDC, eri_read(r, 0xDC) | 1u);
|
||||
eri_write(r, 0xDC, eri_read(r, 0xDC) | 0x1C);
|
||||
eri_write_mask(r, 0x5F0, 0x3, 0x4F87);
|
||||
wr32(r, R_MISC, rd32(r, R_MISC) & ~MISC_RXDV_GATE);
|
||||
eri_write_mask(r, 0xC0, 0x3, 0);
|
||||
eri_write_mask(r, 0xB8, 0x3, 0);
|
||||
|
||||
wr8(r, R_EEE_LED, rd8(r, R_EEE_LED) & ~0x07); // rtl8168_config_eee_mac
|
||||
eri_write(r, 0x1B0, eri_read(r, 0x1B0) | 0x3);
|
||||
|
||||
wr8(r, R_DLLPR, rd8(r, R_DLLPR) & ~0x40); // PFM off
|
||||
wr8(r, R_MISC1, rd8(r, R_MISC1) & ~0x40); // PFM in D3cold off
|
||||
wr8(r, R_DLLPR, rd8(r, R_DLLPR) & ~0x80); // 10M power saving off
|
||||
eri_write(r, 0x1B0, eri_read(r, 0x1B0) & ~(1u << 12));
|
||||
wr8(r, R_CONFIG3, rd8(r, R_CONFIG3) & ~0x02); // Rdy_to_L23 off
|
||||
|
||||
int saw = phy_read_paged(r, 0x0C42, 0x13);
|
||||
if (saw > 0 && (saw & 0x3FFF) > 0)
|
||||
mac_ocp_modify(r, 0xD412, 0x0FFF, (16000000 / (saw & 0x3FFF)) & 0x0FFF);
|
||||
|
||||
mac_ocp_modify(r, 0xE056, 0x00F0, 0x0000);
|
||||
mac_ocp_modify(r, 0xE052, 0x6000, 0x8008);
|
||||
mac_ocp_modify(r, 0xE0D6, 0x01FF, 0x017F);
|
||||
mac_ocp_modify(r, 0xD420, 0x0FFF, 0x047F);
|
||||
mac_ocp_write(r, 0xE63E, 0x0001);
|
||||
mac_ocp_write(r, 0xE63E, 0x0000);
|
||||
mac_ocp_write(r, 0xC094, 0x0000);
|
||||
mac_ocp_write(r, 0xC09E, 0x0000);
|
||||
}
|
||||
|
||||
// rtl8168h_2_hw_phy_config, less the firmware patch.
|
||||
static void
|
||||
r8169_phy_8168h(struct r8169 *r)
|
||||
{
|
||||
phy_param(r, 0x808A, 0x003F, 0x000A); // CHIN EST parameter
|
||||
phy_param(r, 0x0811, 0x0000, 0x0800); // R-tune and PGA-retune on
|
||||
phy_modify_paged(r, 0x0A42, 0x16, 0x0000, 0x0002);
|
||||
phy_modify_paged(r, 0x0A44, 0x11, 0, 1u << 11); // gphy 10M
|
||||
|
||||
// ADC bias offset, from the MAC's OCP space.
|
||||
mac_ocp_write(r, 0xDD02, 0x807D);
|
||||
uint16 d1 = mac_ocp_read(r, 0xDD02), d2 = mac_ocp_read(r, 0xDD00);
|
||||
uint16 ioffset = ((d2 >> 1) & 0x7FF8) | (d2 & 0x0007) | ((d1 & 0x80) ? 0x8000 : 0);
|
||||
if (ioffset != 0xFFFF)
|
||||
phy_write_paged(r, 0x0BCF, 0x16, ioffset);
|
||||
|
||||
// TX low-pass corner.
|
||||
int data = phy_read_paged(r, 0x0BCD, 0x16);
|
||||
if (data >= 0) {
|
||||
uint16 rlen = (data & 0xF) > 3 ? (data & 0xF) - 3 : 0;
|
||||
phy_write_paged(r, 0x0BCD, 0x17, rlen | rlen << 4 | rlen << 8 | rlen << 12);
|
||||
}
|
||||
|
||||
phy_modify_paged(r, 0x0A44, 0x11, 1u << 7, 0); // PHY PFM off
|
||||
phy_modify_paged(r, 0x0A43, 0x10, 1u << 0, 0); // 10M PLL off: off
|
||||
phy_modify_paged(r, 0x0A43, 0x10, 1u << 2, 0); // ALDPS off
|
||||
phy_modify_paged(r, 0x0A43, 0x11, 0, 1u << 4); // EEE
|
||||
}
|
||||
|
||||
// The PHY up and negotiating: page 0, out of power-down, autoneg on and
|
||||
// restarted. Power-down is what firmware leaves set after a shutdown, and
|
||||
// the old code ORed autoneg into BMCR and kept it.
|
||||
|
|
@ -620,6 +843,10 @@ r8169_probe(struct pci_dev *d)
|
|||
if (xid == 0x540 || xid == 0x541)
|
||||
eri_write(r, 0x1A8, eri_read(r, 0x1A8) | 0xFC000000);
|
||||
wr32(r, R_MISC, rd32(r, R_MISC) & ~MISC_RXDV_GATE);
|
||||
if (xid == 0x541 || xid == 0x6C0) {
|
||||
r8169_aspm_off(r);
|
||||
r8169_start_8168h(r);
|
||||
}
|
||||
}
|
||||
|
||||
// CPCR is deliberately left alone.
|
||||
|
|
@ -635,13 +862,16 @@ r8169_probe(struct pci_dev *d)
|
|||
// unlike the 8139 it has no other programming model to be in.
|
||||
|
||||
wr16(r, R_RMS, BUFSZ);
|
||||
wr8(r, R_MTPS, 0x3B);
|
||||
// EarlySize on the 8168E-VL and later, TxPacketMax before, as in Linux.
|
||||
wr8(r, R_MTPS, r->gen_g ? 0x27 : 0x3B);
|
||||
wr16(r, R_INTRMIT, 0); // no interrupt coalescing
|
||||
|
||||
wr64(r, R_RDSAR, r->rx_phys);
|
||||
wr64(r, R_TNPDS, r->tx_phys);
|
||||
|
||||
wr32(r, R_TCR, TCR_IFG_STD | TCR_MXDMA);
|
||||
wr32(r, R_RCR, RCR_APM | RCR_AM | RCR_AB | RCR_UNLIMITED);
|
||||
wr32(r, R_TCR, TCR_IFG_STD | TCR_MXDMA | (r->gen_g ? TCR_AUTO_FIFO : 0));
|
||||
wr32(r, R_RCR, RCR_APM | RCR_AM | RCR_AB |
|
||||
(r->gen_g ? RCR_GEN_G : RCR_UNLIMITED));
|
||||
|
||||
// Multicast: everything. The stack filters, and a hash filter that is wrong
|
||||
// drops IPv6 neighbour discovery in a way that looks like a routing problem.
|
||||
|
|
@ -651,6 +881,11 @@ r8169_probe(struct pci_dev *d)
|
|||
wr8(r, R_CR, CR_RE | CR_TE);
|
||||
wr8(r, R_9346CR, C9346_LOCK);
|
||||
|
||||
{
|
||||
uint32 xid = (rd32(r, R_TCR) >> 20) & 0xFCF;
|
||||
if (xid == 0x541 || xid == 0x6C0)
|
||||
r8169_phy_8168h(r);
|
||||
}
|
||||
r8169_phy_up(r);
|
||||
|
||||
wr16(r, R_ISR, 0xFFFF); // clear anything left over
|
||||
|
|
|
|||
|
|
@ -33,7 +33,6 @@ extern const struct pci_driver xhci_driver;
|
|||
extern const struct pci_driver virtio_gpu_driver;
|
||||
extern const struct pci_driver e1000_driver;
|
||||
extern const struct pci_driver ac97_driver;
|
||||
extern const struct pci_driver r8169_driver;
|
||||
extern const struct pci_driver rtl8139_driver;
|
||||
extern const struct pci_driver ahci_driver;
|
||||
extern const struct pci_driver nvme_driver;
|
||||
|
|
@ -47,7 +46,9 @@ static const struct pci_driver *drivers[] = {
|
|||
&virtio_gpu_driver,
|
||||
&vga_pci_driver,
|
||||
&e1000_driver,
|
||||
&r8169_driver,
|
||||
// &r8169_driver -- the RTL8168 is driven by Linux's own r8169 through
|
||||
// LDLS (etc/schinit/ldls-r8169.job), which a kernel driver here would
|
||||
// claim first. The native driver stays in the tree as the fallback.
|
||||
&rtl8139_driver,
|
||||
&xhci_driver,
|
||||
&ahci_driver,
|
||||
|
|
|
|||
Loading…
Reference in a new issue