ladybird/Kernel/Net/RTL8168NetworkAdapter.cpp
Liav A 25ea7461a0 Kernel/PCI: Simplify the entire subsystem
A couple of things were changed:
1. Semantic changes - PCI segments are now called PCI domains, to better
match what they are really. It's also the name that Linux gave, and it
seems that Wikipedia also uses this name.
We also remove PCI::ChangeableAddress, because it was used in the past
but now it's no longer being used.
2. There are no WindowedMMIOAccess or MMIOAccess classes anymore, as
they made a bunch of unnecessary complexity. Instead, Windowed access is
removed entirely (this was tested, but never was benchmarked), so we are
left with IO access and memory access options. The memory access option
is essentially mapping the PCI bus (from the chosen PCI domain), to
virtual memory as-is. This means that unless needed, at any time, there
is only one PCI bus being mapped, and this is changed if access to
another PCI bus in the same PCI domain is needed. For now, we don't
support mapping of different PCI buses from different PCI domains at the
same time, because basically it's still a non-issue for most machines
out there.
2. OOM-safety is increased, especially when constructing the Access
object. It means that we pre-allocating any needed resources, and we try
to find PCI domains (if requested to initialize memory access) after we
attempt to construct the Access object, so it's possible to fail at this
point "gracefully".
3. All PCI API functions are now separated into a different header file,
which means only "clients" of the PCI subsystem API will need to include
that header file.
4. Functional changes - we only allow now to enumerate the bus after
a hardware scan. This means that the old method "enumerate_hardware"
is removed, so, when initializing an Access object, the initializing
function must call rescan on it to force it to find devices. This makes
it possible to fail rescan, and also to defer it after construction from
both OOM-safety terms and hotplug capabilities.
2021-09-07 13:47:37 +02:00

1663 lines
50 KiB
C++

/*
* Copyright (c) 2021, Idan Horowitz <idan.horowitz@serenityos.org>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <AK/MACAddress.h>
#include <Kernel/Bus/PCI/API.h>
#include <Kernel/Bus/PCI/IDs.h>
#include <Kernel/Debug.h>
#include <Kernel/Net/RTL8168NetworkAdapter.h>
#include <Kernel/Sections.h>
namespace Kernel {
#define REG_MAC 0x00
#define REG_MAR4 0x0B
#define REG_MAR0 0x0F
#define REG_EEE_LED 0x1B
#define REG_TXADDR 0x20
#define REG_COMMAND 0x37
#define REG_TXSTART 0x38
#define REG_IMR 0x3C
#define REG_ISR 0x3E
#define REG_TXCFG 0x40
#define REG_RXCFG 0x44
#define REG_MPC 0x4C
#define REG_CFG9346 0x50
#define REG_CONFIG1 0x52
#define REG_CONFIG2 0x53
#define REG_CONFIG3 0x54
#define REG_CONFIG4 0x55
#define REG_CONFIG5 0x56
#define REG_MULTIINTR 0x5C
#define REG_PHYACCESS 0x60
#define REG_CSI_DATA 0x64
#define REG_CSI_ADDR 0x68
#define REG_PHYSTATUS 0x6C
#define REG_PMCH 0x6F
#define REG_ERI_DATA 0x70
#define REG_ERI_ADDR 0x74
#define REG_EPHYACCESS 0x80
#define REG_OCP_DATA 0xB0
#define REG_OCP_ADDR 0xB4
#define REG_GPHY_OCP 0xB8
#define REG_DLLPR 0xD0
#define REG_MCU 0xD3
#define REG_RMS 0xDA
#define REG_CPLUS_COMMAND 0xE0
#define REG_INT_MOD 0xE2
#define REG_RXADDR 0xE4
#define REG_MTPS 0xEC
#define REG_MISC 0xF0
#define REG_MISC2 0xF2
#define REG_IBCR0 0xF8
#define REG_IBCR2 0xF9
#define REG_IBISR0 0xFB
#define COMMAND_TX_ENABLE 0x4
#define COMMAND_RX_ENABLE 0x8
#define COMMAND_RESET 0x10
#define CPLUS_COMMAND_VERIFY_CHECKSUM 0x20
#define CPLUS_COMMAND_VLAN_STRIP 0x40
#define CPLUS_COMMAND_MAC_DBGO_SEL 0x1C
#define CPLUS_COMMAND_PACKET_CONTROL_DISABLE 0x80
#define CPLUS_COMMAND_ASF 0x100
#define CPLUS_COMMAND_CXPL_DBG_SEL 0x200
#define CPLUS_COMMAND_FORCE_TXFLOW_ENABLE 0x400
#define CPLUS_COMMAND_FORCE_RXFLOW_ENABLE 0x800
#define CPLUS_COMMAND_FORCE_HALF_DUP 0x1000
#define CPLUS_COMMAND_MAC_DBGO_OE 0x4000
#define CPLUS_COMMAND_ENABLE_BIST 0x8000
#define INT_RXOK 0x1
#define INT_RXERR 0x2
#define INT_TXOK 0x4
#define INT_TXERR 0x8
#define INT_RX_OVERFLOW 0x10
#define INT_LINK_CHANGE 0x20
#define INT_RX_FIFO_OVERFLOW 0x40
#define INT_SYS_ERR 0x8000
#define CFG9346_NONE 0x00
#define CFG9346_EEM0 0x40
#define CFG9346_EEM1 0x80
#define CFG9346_UNLOCK (CFG9346_EEM0 | CFG9346_EEM1)
#define TXCFG_AUTO_FIFO 0x80
#define TXCFG_MAX_DMA_UNLIMITED 0x700
#define TXCFG_EMPTY 0x800
#define TXCFG_IFG011 0x3000000
#define RXCFG_READ_MASK 0x3F
#define RXCFG_APM 0x2
#define RXCFG_AM 0x4
#define RXCFG_AB 0x8
#define RXCFG_MAX_DMA_UNLIMITED 0x700
#define RXCFG_EARLY_OFF_V2 0x800
#define RXCFG_FTH_NONE 0xE000
#define RXCFG_MULTI_ENABLE 0x4000
#define RXCFG_128INT_ENABLE 0x8000
#define CFG2_CLOCK_REQUEST_ENABLE 0x80
#define CFG3_BEACON_ENABLE 0x1
#define CFG3_READY_TO_L23 0x2
#define CFG5_ASPM_ENABLE 0x1
#define CFG5_SPI_ENABLE 0x8
#define PHY_LINK_STATUS 0x2
#define PHY_FLAG 0x80000000
#define PHY_REG_BMCR 0x00
#define PHY_REG_ANAR 0x4
#define PHY_REG_GBCR 0x9
#define CSI_FLAG 0x80000000
#define CSI_BYTE_ENABLE 0xF000
#define CSI_FUNC_NIC 0x20000
#define CSI_FUNC_NIC2 0x10000
#define CSI_ACCESS_1 0x17000000
#define CSI_ACCESS_2 0x27000000
#define EPHY_FLAG 0x80000000
#define ERI_FLAG 0x80000000
#define ERI_MASK_0001 0x1000
#define ERI_MASK_0011 0x3000
#define ERI_MASK_0100 0x4000
#define ERI_MASK_0101 0x5000
#define ERI_MASK_1111 0xF000
#define ERI_EXGMAC 0x0
#define OCP_FLAG 0x80000000
#define OCP_STANDARD_PHY_BASE 0xa400
#define TXSTART_START 0x40
#define BMCR_RESET 0x8000
#define BMCR_SPEED_0 0x2000
#define BMCR_AUTO_NEGOTIATE 0x1000
#define BMCR_RESTART_AUTO_NEGOTIATE 0x200
#define BMCR_DUPLEX 0x100
#define BMCR_SPEED_1 0x40
#define ADVERTISE_10_HALF 0x20
#define ADVERTISE_10_FULL 0x40
#define ADVERTISE_100_HALF 0x80
#define ADVERTISE_100_FULL 0x100
#define ADVERTISE_PAUSE_CAP 0x400
#define ADVERTISE_PAUSE_ASYM 0x800
#define ADVERTISE_1000_HALF 0x100
#define ADVERTISE_1000_FULL 0x200
#define DLLPR_PFM_ENABLE 0x40
#define DLLPR_TX_10M_PS_ENABLE 0x80
#define MCU_LINK_LIST_READY 0x2
#define MCU_RX_EMPTY 0x10
#define MCU_TX_EMPTY 0x20
#define MCU_NOW_IS_OOB 0x80
#define MTPS_JUMBO 0x3F
#define MISC_RXDV_GATE_ENABLE 0x80000
#define MISC_PWM_ENABLE 0x400000
#define MISC2_PFM_D3COLD_ENABLE 0x40
#define PHYSTATUS_FULLDUP 0x01
#define PHYSTATUS_1000MF 0x10
#define PHYSTATUS_100M 0x08
#define PHYSTATUS_10M 0x04
#define TX_BUFFER_SIZE 0x1FF8
#define RX_BUFFER_SIZE 0x1FF8 // FIXME: this should be increased (0x3FFF)
UNMAP_AFTER_INIT RefPtr<RTL8168NetworkAdapter> RTL8168NetworkAdapter::try_to_initialize(PCI::Address address)
{
auto id = PCI::get_id(address);
if (id.vendor_id != PCI::VendorID::Realtek)
return {};
if (id.device_id != 0x8168)
return {};
u8 irq = PCI::get_interrupt_line(address);
return adopt_ref_if_nonnull(new (nothrow) RTL8168NetworkAdapter(address, irq));
}
UNMAP_AFTER_INIT RTL8168NetworkAdapter::RTL8168NetworkAdapter(PCI::Address address, u8 irq)
: PCI::Device(address)
, IRQHandler(irq)
, m_io_base(PCI::get_BAR0(pci_address()) & ~1)
, m_rx_descriptors_region(MM.allocate_contiguous_kernel_region(Memory::page_round_up(sizeof(TXDescriptor) * (number_of_rx_descriptors + 1)), "RTL8168 RX", Memory::Region::Access::ReadWrite).release_value())
, m_tx_descriptors_region(MM.allocate_contiguous_kernel_region(Memory::page_round_up(sizeof(RXDescriptor) * (number_of_tx_descriptors + 1)), "RTL8168 TX", Memory::Region::Access::ReadWrite).release_value())
{
set_interface_name(address);
dmesgln("RTL8168: Found @ {}", pci_address());
dmesgln("RTL8168: I/O port base: {}", m_io_base);
identify_chip_version();
dmesgln("RTL8168: Version detected - {} ({}{})", possible_device_name(), (u8)m_version, m_version_uncertain ? "?" : "");
if (m_version == ChipVersion::Unknown || (m_version >= ChipVersion::Version4 && m_version <= ChipVersion::Version16) || m_version >= ChipVersion::Version18) {
dmesgln("RTL8168: Aborting initialization! Support for your chip version ({}) is not implemented yet, please open a GH issue and include this message.", (u8)m_version);
return; // Each ChipVersion requires a specific implementation of configure_phy and hardware_quirks
}
initialize();
startup();
}
void RTL8168NetworkAdapter::initialize()
{
// set initial REG_RXCFG
auto rx_config = RXCFG_MAX_DMA_UNLIMITED;
if (m_version <= ChipVersion::Version3) {
rx_config |= RXCFG_FTH_NONE;
} else if (m_version <= ChipVersion::Version8 || (m_version >= ChipVersion::Version16 && m_version <= ChipVersion::Version19)) {
rx_config |= RXCFG_128INT_ENABLE | RXCFG_MULTI_ENABLE;
} else if (m_version >= ChipVersion::Version21) {
rx_config |= RXCFG_128INT_ENABLE | RXCFG_MULTI_ENABLE | RXCFG_EARLY_OFF_V2;
} else {
rx_config |= RXCFG_128INT_ENABLE;
}
out32(REG_RXCFG, rx_config);
// disable interrupts
out16(REG_IMR, 0);
// initialize hardware
if (m_version == ChipVersion::Version23 || m_version == ChipVersion::Version27 || m_version == ChipVersion::Version28) {
// disable CMAC
out8(REG_IBCR2, in8(REG_IBCR2) & ~1);
while ((in32(REG_IBISR0) & 0x2) != 0)
;
out8(REG_IBISR0, in8(REG_IBISR0) | 0x20);
out8(REG_IBCR0, in8(REG_IBCR0) & ~1);
}
if (m_version >= ChipVersion::Version21) {
m_ocp_base_address = OCP_STANDARD_PHY_BASE;
// enable RXDV gate
out32(REG_MISC, in32(REG_MISC) | MISC_RXDV_GATE_ENABLE);
while ((in32(REG_TXCFG) & TXCFG_EMPTY) == 0)
;
while ((in32(REG_MCU) & (MCU_RX_EMPTY | MCU_TX_EMPTY)) == 0)
;
out8(REG_COMMAND, in8(REG_COMMAND) & ~(COMMAND_RX_ENABLE | COMMAND_TX_ENABLE));
out8(REG_MCU, in8(REG_MCU) & ~MCU_NOW_IS_OOB);
// vendor magic values ???
auto data = ocp_in(0xe8de);
data &= ~(1 << 14);
ocp_out(0xe8de, data);
while ((in32(REG_MCU) & MCU_LINK_LIST_READY) == 0)
;
// vendor magic values ???
data = ocp_in(0xe8de);
data |= (1 << 15);
ocp_out(0xe8de, data);
while ((in32(REG_MCU) & MCU_LINK_LIST_READY) == 0)
;
}
// software reset
reset();
// clear interrupts
out16(REG_ISR, 0xffff);
enable_bus_mastering(pci_address());
read_mac_address();
dmesgln("RTL8168: MAC address: {}", mac_address().to_string());
// notify about driver start
if (m_version >= ChipVersion::Version11 && m_version <= ChipVersion::Version13) {
// if check_dash
// notify
TODO();
} else if (m_version == ChipVersion::Version23 || m_version == ChipVersion::Version27 || m_version == ChipVersion::Version28) {
// if check_dash
// notify
TODO();
}
}
void RTL8168NetworkAdapter::startup()
{
// initialize descriptors
initialize_rx_descriptors();
initialize_tx_descriptors();
// register irq
enable_irq();
// version specific phy configuration
configure_phy();
// software reset phy
phy_out(PHY_REG_BMCR, phy_in(PHY_REG_BMCR) | BMCR_RESET);
while ((phy_in(PHY_REG_BMCR) & BMCR_RESET) != 0)
;
set_phy_speed();
// set C+ command
auto cplus_command = in16(REG_CPLUS_COMMAND) | CPLUS_COMMAND_VERIFY_CHECKSUM | CPLUS_COMMAND_VLAN_STRIP;
out16(REG_CPLUS_COMMAND, cplus_command);
in16(REG_CPLUS_COMMAND); // C+ Command barrier
// power up phy
if (m_version >= ChipVersion::Version9 && m_version <= ChipVersion::Version15) {
out8(REG_PMCH, in8(REG_PMCH) | 0x80);
} else if (m_version >= ChipVersion::Version26) {
out8(REG_PMCH, in8(REG_PMCH) | 0xC0);
} else if (m_version >= ChipVersion::Version21 && m_version <= ChipVersion::Version23) {
out8(REG_PMCH, in8(REG_PMCH) | 0xC0);
// vendor magic values ???
eri_update(0x1a8, ERI_MASK_1111, 0xfc000000, 0, ERI_EXGMAC);
}
// wakeup phy (more vendor magic values)
phy_out(0x1F, 0);
if (m_version <= ChipVersion::Version13) {
phy_out(0x0E, 0);
}
phy_out(PHY_REG_BMCR, BMCR_AUTO_NEGOTIATE); // send known good phy write (acts as a phy barrier)
start_hardware();
// re-enable interrupts
auto enabled_interrupts = INT_RXOK | INT_RXERR | INT_TXOK | INT_TXERR | INT_RX_OVERFLOW | INT_LINK_CHANGE | INT_SYS_ERR;
if (m_version == ChipVersion::Version1) {
enabled_interrupts |= INT_RX_FIFO_OVERFLOW;
enabled_interrupts &= ~INT_RX_OVERFLOW;
}
out16(REG_IMR, enabled_interrupts);
// update link status
m_link_up = (in8(REG_PHYSTATUS) & PHY_LINK_STATUS) != 0;
}
void RTL8168NetworkAdapter::configure_phy()
{
// this method sets a bunch of magic vendor values to the phy configuration registers based on the hardware version
switch (m_version) {
case ChipVersion::Version1: {
configure_phy_b_1();
return;
}
case ChipVersion::Version2:
case ChipVersion::Version3: {
configure_phy_b_2();
return;
}
case ChipVersion::Version4:
TODO();
case ChipVersion::Version5:
TODO();
case ChipVersion::Version6:
TODO();
case ChipVersion::Version7:
TODO();
case ChipVersion::Version8:
TODO();
case ChipVersion::Version9:
TODO();
case ChipVersion::Version10:
TODO();
case ChipVersion::Version11:
TODO();
case ChipVersion::Version12:
TODO();
case ChipVersion::Version13:
TODO();
case ChipVersion::Version14:
TODO();
case ChipVersion::Version15:
TODO();
case ChipVersion::Version16:
TODO();
case ChipVersion::Version17: {
configure_phy_e_2();
return;
}
case ChipVersion::Version18:
TODO();
case ChipVersion::Version19:
TODO();
case ChipVersion::Version20:
TODO();
case ChipVersion::Version21:
TODO();
case ChipVersion::Version22:
TODO();
case ChipVersion::Version23:
TODO();
case ChipVersion::Version24:
TODO();
case ChipVersion::Version25:
TODO();
case ChipVersion::Version26:
TODO();
case ChipVersion::Version27:
TODO();
case ChipVersion::Version28:
TODO();
case ChipVersion::Version29: {
configure_phy_h_1();
return;
}
case ChipVersion::Version30: {
configure_phy_h_2();
return;
}
default:
VERIFY_NOT_REACHED();
}
}
void RTL8168NetworkAdapter::configure_phy_b_1()
{
constexpr PhyRegister phy_registers[] = {
{ 0x10, 0xf41b },
{ 0x1f, 0 }
};
phy_out(0x1f, 0x1);
phy_out(0x16, 1 << 0);
phy_out_batch(phy_registers, 2);
}
void RTL8168NetworkAdapter::configure_phy_b_2()
{
constexpr PhyRegister phy_registers[] = {
{ 0x1f, 0x1 },
{ 0x10, 0xf41b },
{ 0x1f, 0 }
};
phy_out_batch(phy_registers, 3);
}
void RTL8168NetworkAdapter::configure_phy_e_2()
{
// FIXME: linux's driver writes a firmware blob to the device at this point, is this needed?
constexpr PhyRegister phy_registers[] = {
// Enable delay cap
{ 0x1f, 0x4 },
{ 0x1f, 0x7 },
{ 0x1e, 0xac },
{ 0x18, 0x6 },
{ 0x1f, 0x2 },
{ 0x1f, 0 },
{ 0x1f, 0 },
// Channel estimation fine tune
{ 0x1f, 0x3 },
{ 0x9, 0xa20f },
{ 0x1f, 0 },
{ 0x1f, 0 },
// Green Setting
{ 0x1f, 0x5 },
{ 0x5, 0x8b5b },
{ 0x6, 0x9222 },
{ 0x5, 0x8b6d },
{ 0x6, 0x8000 },
{ 0x5, 0x8b76 },
{ 0x6, 0x8000 },
{ 0x1f, 0 },
};
phy_out_batch(phy_registers, 19);
// 4 corner performance improvement
phy_out(0x1f, 0x5);
phy_out(0x5, 0x8b80);
phy_update(0x17, 0x6, 0);
phy_out(0x1f, 0);
// PHY auto speed down
phy_out(0x1f, 0x4);
phy_out(0x1f, 0x7);
phy_out(0x1e, 0x2d);
phy_update(0x18, 0x10, 0);
phy_out(0x1f, 0x2);
phy_out(0x1f, 0);
phy_update(0x14, 0x8000, 0);
// Improve 10M EEE waveform
phy_out(0x1f, 0x5);
phy_out(0x5, 0x8b86);
phy_update(0x6, 0x1, 0);
phy_out(0x1f, 0);
// Improve 2-pair detection performance
phy_out(0x1f, 0x5);
phy_out(0x5, 0x8b85);
phy_update(0x6, 0x4000, 0);
phy_out(0x1f, 0);
// EEE Setting
eri_update(0x1b0, ERI_MASK_1111, 0, 0x3, ERI_EXGMAC);
phy_out(0x1f, 0x5);
phy_out(0x5, 0x8b85);
phy_update(0x6, 0, 0x2000);
phy_out(0x1f, 0x4);
phy_out(0x1f, 0x7);
phy_out(0x1e, 0x20);
phy_update(0x15, 0, 0x100);
phy_out(0x1f, 0x2);
phy_out(0x1f, 0);
phy_out(0xd, 0x7);
phy_out(0xe, 0x3c);
phy_out(0xd, 0x4007);
phy_out(0xe, 0);
phy_out(0xd, 0);
// Green feature
phy_out(0x1f, 0x3);
phy_update(0x19, 0, 0x1);
phy_update(0x10, 0, 0x400);
phy_out(0x1f, 0);
// Broken BIOS workaround: feed GigaMAC registers with MAC address.
rar_exgmac_set();
}
void RTL8168NetworkAdapter::configure_phy_h_1()
{
// FIXME: linux's driver writes a firmware blob to the device at this point, is this needed?
// CHN EST parameters adjust - giga master
phy_out(0x1f, 0x0a43);
phy_out(0x13, 0x809b);
phy_update(0x14, 0x8000, 0xf800);
phy_out(0x13, 0x80a2);
phy_update(0x14, 0x8000, 0xff00);
phy_out(0x13, 0x80a4);
phy_update(0x14, 0x8500, 0xff00);
phy_out(0x13, 0x809c);
phy_update(0x14, 0xbd00, 0xff00);
phy_out(0x1f, 0);
// CHN EST parameters adjust - giga slave
phy_out(0x1f, 0x0a43);
phy_out(0x13, 0x80ad);
phy_update(0x14, 0x7000, 0xf800);
phy_out(0x13, 0x80b4);
phy_update(0x14, 0x5000, 0xff00);
phy_out(0x13, 0x80ac);
phy_update(0x14, 0x4000, 0xff00);
phy_out(0x1f, 0);
// CHN EST parameters adjust - fnet
phy_out(0x1f, 0x0a43);
phy_out(0x13, 0x808e);
phy_update(0x14, 0x1200, 0xff00);
phy_out(0x13, 0x8090);
phy_update(0x14, 0xe500, 0xff00);
phy_out(0x13, 0x8092);
phy_update(0x14, 0x9f00, 0xff00);
phy_out(0x1f, 0);
// enable R-tune & PGA-retune function
u16 dout_tapbin = 0;
phy_out(0x1f, 0x0a46);
auto data = phy_in(0x13);
data &= 3;
data <<= 2;
dout_tapbin |= data;
data = phy_in(0x12);
data &= 0xc000;
data >>= 14;
dout_tapbin |= data;
dout_tapbin = ~(dout_tapbin ^ 0x8);
dout_tapbin <<= 12;
dout_tapbin &= 0xf000;
phy_out(0x1f, 0x0a43);
phy_out(0x13, 0x827a);
phy_update(0x14, dout_tapbin, 0xf000);
phy_out(0x13, 0x827b);
phy_update(0x14, dout_tapbin, 0xf000);
phy_out(0x13, 0x827c);
phy_update(0x14, dout_tapbin, 0xf000);
phy_out(0x13, 0x827d);
phy_update(0x14, dout_tapbin, 0xf000);
phy_out(0x1f, 0x0a43);
phy_out(0x13, 0x811);
phy_update(0x14, 0x800, 0);
phy_out(0x1f, 0x0a42);
phy_update(0x16, 0x2, 0);
phy_out(0x1f, 0);
// enable GPHY 10M
phy_out(0x1f, 0x0a44);
phy_update(0x11, 0x800, 0);
phy_out(0x1f, 0);
// SAR ADC performance
phy_out(0x1f, 0x0bca);
phy_update(0x17, 0x4000, 0x3000);
phy_out(0x1f, 0);
phy_out(0x1f, 0x0a43);
phy_out(0x13, 0x803f);
phy_update(0x14, 0, 0x3000);
phy_out(0x13, 0x8047);
phy_update(0x14, 0, 0x3000);
phy_out(0x13, 0x804f);
phy_update(0x14, 0, 0x3000);
phy_out(0x13, 0x8057);
phy_update(0x14, 0, 0x3000);
phy_out(0x13, 0x805f);
phy_update(0x14, 0, 0x3000);
phy_out(0x13, 0x8067);
phy_update(0x14, 0, 0x3000);
phy_out(0x13, 0x806f);
phy_update(0x14, 0, 0x3000);
phy_out(0x1f, 0);
// disable phy pfm mode
phy_out(0x1f, 0x0a44);
phy_update(0x11, 0, 0x80);
phy_out(0x1f, 0);
// Check ALDPS bit, disable it if enabled
phy_out(0x1f, 0x0a43);
if (phy_in(0x10) & 0x4)
phy_update(0x10, 0, 0x4);
phy_out(0x1f, 0);
}
void RTL8168NetworkAdapter::configure_phy_h_2()
{
// FIXME: linux's driver writes a firmware blob to the device at this point, is this needed?
// CHIN EST parameter update
phy_out(0x1f, 0x0a43);
phy_out(0x13, 0x808a);
phy_update(0x14, 0x000a, 0x3f);
phy_out(0x1f, 0);
// enable R-tune & PGA-retune function
phy_out(0x1f, 0x0a43);
phy_out(0x13, 0x811);
phy_update(0x14, 0x800, 0);
phy_out(0x1f, 0x0a42);
phy_update(0x16, 0x2, 0);
phy_out(0x1f, 0);
// enable GPHY 10M
phy_out(0x1f, 0x0a44);
phy_update(0x11, 0x800, 0);
phy_out(0x1f, 0);
ocp_out(0xdd02, 0x807d);
auto data = ocp_in(0xdd02);
u16 ioffset_p3 = ((data & 0x80) >> 7);
ioffset_p3 <<= 3;
data = ocp_in(0xdd00);
ioffset_p3 |= ((data & (0xe000)) >> 13);
u16 ioffset_p2 = ((data & (0x1e00)) >> 9);
u16 ioffset_p1 = ((data & (0x1e0)) >> 5);
u16 ioffset_p0 = ((data & 0x10) >> 4);
ioffset_p0 <<= 3;
ioffset_p0 |= (data & (0x7));
data = (ioffset_p3 << 12) | (ioffset_p2 << 8) | (ioffset_p1 << 4) | (ioffset_p0);
if ((ioffset_p3 != 0x0f) || (ioffset_p2 != 0x0f) || (ioffset_p1 != 0x0f) || (ioffset_p0 != 0x0f)) {
phy_out(0x1f, 0x0bcf);
phy_out(0x16, data);
phy_out(0x1f, 0);
}
// Modify rlen (TX LPF corner frequency) level
phy_out(0x1f, 0x0bcd);
data = phy_in(0x16);
data &= 0x000f;
u16 rlen = 0;
if (data > 3)
rlen = data - 3;
data = rlen | (rlen << 4) | (rlen << 8) | (rlen << 12);
phy_out(0x17, data);
phy_out(0x1f, 0x0bcd);
phy_out(0x1f, 0);
// disable phy pfm mode
phy_out(0x1f, 0x0a44);
phy_update(0x11, 0, 0x80);
phy_out(0x1f, 0);
// Check ALDPS bit, disable it if enabled
phy_out(0x1f, 0x0a43);
if (phy_in(0x10) & 0x4)
phy_update(0x10, 0, 0x4);
phy_out(0x1f, 0);
}
void RTL8168NetworkAdapter::rar_exgmac_set()
{
auto mac = mac_address();
const u16 w[] = {
(u16)(mac[0] | (mac[1] << 8)),
(u16)(mac[2] | (mac[3] << 8)),
(u16)(mac[4] | (mac[5] << 8)),
};
const ExgMacRegister exg_mac_registers[] = {
{ 0xe0, ERI_MASK_1111, (u32)(w[0] | (w[1] << 16)) },
{ 0xe4, ERI_MASK_1111, (u32)w[2] },
{ 0xf0, ERI_MASK_1111, (u32)(w[0] << 16) },
{ 0xf4, ERI_MASK_1111, (u32)(w[1] | (w[2] << 16)) },
};
exgmac_out_batch(exg_mac_registers, 4);
}
void RTL8168NetworkAdapter::start_hardware()
{
// unlock config registers
out8(REG_CFG9346, CFG9346_UNLOCK);
// configure the maximum transmit packet size
out16(REG_MTPS, MTPS_JUMBO);
// configure the maximum receive packet size
out16(REG_RMS, RX_BUFFER_SIZE);
auto cplus_command = in16(REG_CPLUS_COMMAND);
cplus_command |= CPLUS_COMMAND_PACKET_CONTROL_DISABLE;
// undocumented magic value???
cplus_command |= 0x1;
out16(REG_CPLUS_COMMAND, cplus_command);
// setup interrupt moderation, magic from vendor (Linux Driver uses 0x5151, *BSD Driver uses 0x5100, RTL Driver use 0x5f51???)
out16(REG_INT_MOD, 0x5151);
// point to tx descriptors
out64(REG_TXADDR, m_tx_descriptors_region->physical_page(0)->paddr().get());
// point to rx descriptors
out64(REG_RXADDR, m_rx_descriptors_region->physical_page(0)->paddr().get());
// configure tx: use the maximum dma transfer size, default interframe gap time.
out32(REG_TXCFG, TXCFG_IFG011 | TXCFG_MAX_DMA_UNLIMITED);
// version specific quirks and tweaks
hardware_quirks();
in8(REG_IMR); // known good read (acts as a barrier)
// relock config registers
out8(REG_CFG9346, CFG9346_NONE);
// enable rx/tx
out8(REG_COMMAND, COMMAND_RX_ENABLE | COMMAND_TX_ENABLE);
// turn on all multicast
out32(REG_MAR0, 0xFFFFFFFF);
out32(REG_MAR4, 0xFFFFFFFF);
// configure rx mode: accept physical (MAC) match, multicast, and broadcast
out32(REG_RXCFG, (in32(REG_RXCFG) & ~RXCFG_READ_MASK) | RXCFG_APM | RXCFG_AM | RXCFG_AB);
// disable early-rx interrupts
out16(REG_MULTIINTR, in16(REG_MULTIINTR) & 0xF000);
}
void RTL8168NetworkAdapter::hardware_quirks()
{
switch (m_version) {
case ChipVersion::Version1:
hardware_quirks_b_1();
return;
case ChipVersion::Version2:
case ChipVersion::Version3:
hardware_quirks_b_2();
return;
case ChipVersion::Version4:
TODO();
case ChipVersion::Version5:
TODO();
case ChipVersion::Version6:
TODO();
case ChipVersion::Version7:
TODO();
case ChipVersion::Version8:
TODO();
case ChipVersion::Version9:
TODO();
case ChipVersion::Version10:
TODO();
case ChipVersion::Version11:
TODO();
case ChipVersion::Version12:
TODO();
case ChipVersion::Version13:
TODO();
case ChipVersion::Version14:
TODO();
case ChipVersion::Version15:
TODO();
case ChipVersion::Version16:
TODO();
case ChipVersion::Version17:
hardware_quirks_e_2();
return;
case ChipVersion::Version18:
TODO();
case ChipVersion::Version19:
TODO();
case ChipVersion::Version20:
TODO();
case ChipVersion::Version21:
TODO();
case ChipVersion::Version22:
TODO();
case ChipVersion::Version23:
TODO();
case ChipVersion::Version24:
TODO();
case ChipVersion::Version25:
TODO();
case ChipVersion::Version26:
TODO();
case ChipVersion::Version27:
TODO();
case ChipVersion::Version28:
TODO();
case ChipVersion::Version29:
case ChipVersion::Version30:
hardware_quirks_h();
return;
default:
VERIFY_NOT_REACHED();
}
}
void RTL8168NetworkAdapter::hardware_quirks_b_1()
{
// disable checked reserved bits
out8(REG_CONFIG3, in8(REG_CONFIG3) & ~CFG3_BEACON_ENABLE);
constexpr u16 version1_cplus_quirks = CPLUS_COMMAND_ENABLE_BIST | CPLUS_COMMAND_MAC_DBGO_OE | CPLUS_COMMAND_FORCE_HALF_DUP | CPLUS_COMMAND_FORCE_RXFLOW_ENABLE | CPLUS_COMMAND_FORCE_TXFLOW_ENABLE | CPLUS_COMMAND_CXPL_DBG_SEL | CPLUS_COMMAND_ASF | CPLUS_COMMAND_PACKET_CONTROL_DISABLE | CPLUS_COMMAND_MAC_DBGO_SEL;
out16(REG_CPLUS_COMMAND, in16(REG_CPLUS_COMMAND) & ~version1_cplus_quirks);
}
void RTL8168NetworkAdapter::hardware_quirks_b_2()
{
hardware_quirks_b_1();
// configure the maximum transmit packet size (again)
out16(REG_MTPS, MTPS_JUMBO);
// disable checked reserved bits
out8(REG_CONFIG4, in8(REG_CONFIG4) & ~1);
}
void RTL8168NetworkAdapter::hardware_quirks_e_2()
{
constexpr EPhyUpdate ephy_info[] = {
{ 0x9, 0, 0x80 },
{ 0x19, 0, 0x224 },
};
csi_enable(CSI_ACCESS_1);
extended_phy_initialize(ephy_info, 2);
// FIXME: MTU performance tweak
eri_out(0xc0, ERI_MASK_0011, 0, ERI_EXGMAC);
eri_out(0xb8, ERI_MASK_0011, 0, ERI_EXGMAC);
eri_out(0xc8, ERI_MASK_1111, 0x100002, ERI_EXGMAC);
eri_out(0xe8, ERI_MASK_1111, 0x100006, ERI_EXGMAC);
eri_out(0xcc, ERI_MASK_1111, 0x50, ERI_EXGMAC);
eri_out(0xd0, ERI_MASK_1111, 0x7ff0060, ERI_EXGMAC);
eri_update(0x1b0, ERI_MASK_0001, 0x10, 0, ERI_EXGMAC);
eri_update(0xd4, ERI_MASK_0011, 0xc00, 0xff00, ERI_EXGMAC);
// Set early TX
out8(REG_MTPS, 0x27);
// FIXME: Disable PCIe clock request
// enable tx auto fifo
out32(REG_TXCFG, in32(REG_TXCFG) | TXCFG_AUTO_FIFO);
out8(REG_MCU, in8(REG_MCU) & ~MCU_NOW_IS_OOB);
// Set EEE LED frequency
out8(REG_EEE_LED, in8(REG_EEE_LED) & ~0x7);
out8(REG_DLLPR, in8(REG_DLLPR) | DLLPR_PFM_ENABLE);
out32(REG_MISC, in32(REG_MISC) | MISC_PWM_ENABLE);
out8(REG_CONFIG5, in8(REG_CONFIG5) & ~CFG5_SPI_ENABLE);
}
void RTL8168NetworkAdapter::hardware_quirks_h()
{
// disable aspm and clock request before accessing extended phy
out8(REG_CONFIG2, in8(REG_CONFIG2) & ~CFG2_CLOCK_REQUEST_ENABLE);
out8(REG_CONFIG5, in8(REG_CONFIG5) & ~CFG5_ASPM_ENABLE);
// initialize extended phy
constexpr EPhyUpdate ephy_info[] = {
{ 0x1e, 0x800, 0x1 },
{ 0x1d, 0, 0x800 },
{ 0x5, 0xffff, 0x2089 },
{ 0x6, 0xffff, 0x5881 },
{ 0x4, 0xffff, 0x154a },
{ 0x1, 0xffff, 0x68b }
};
extended_phy_initialize(ephy_info, 6);
// enable tx auto fifo
out32(REG_TXCFG, in32(REG_TXCFG) | TXCFG_AUTO_FIFO);
// vendor magic values ???
eri_out(0xC8, ERI_MASK_0101, 0x80002, ERI_EXGMAC);
eri_out(0xCC, ERI_MASK_0001, 0x38, ERI_EXGMAC);
eri_out(0xD0, ERI_MASK_0001, 0x48, ERI_EXGMAC);
eri_out(0xE8, ERI_MASK_1111, 0x100006, ERI_EXGMAC);
csi_enable(CSI_ACCESS_1);
// vendor magic values ???
eri_update(0xDC, ERI_MASK_0001, 0x0, 0x1, ERI_EXGMAC);
eri_update(0xDC, ERI_MASK_0001, 0x1, 0x0, ERI_EXGMAC);
eri_update(0xDC, ERI_MASK_1111, 0x10, 0x0, ERI_EXGMAC);
eri_update(0xD4, ERI_MASK_1111, 0x1F00, 0x0, ERI_EXGMAC);
eri_out(0x5F0, ERI_MASK_0011, 0x4F87, ERI_EXGMAC);
// disable rxdv gate
out32(REG_MISC, in32(REG_MISC) & ~MISC_RXDV_GATE_ENABLE);
// set early TX
out8(REG_MTPS, 0x27);
// vendor magic values ???
eri_out(0xC0, ERI_MASK_0011, 0, ERI_EXGMAC);
eri_out(0xB8, ERI_MASK_0011, 0, ERI_EXGMAC);
// Set EEE LED frequency
out8(REG_EEE_LED, in8(REG_EEE_LED) & ~0x7);
out8(REG_DLLPR, in8(REG_DLLPR) & ~DLLPR_PFM_ENABLE);
out8(REG_MISC2, in8(REG_MISC2) & ~MISC2_PFM_D3COLD_ENABLE);
out8(REG_DLLPR, in8(REG_DLLPR) & ~DLLPR_TX_10M_PS_ENABLE);
// vendor magic values ???
eri_update(0x1B0, ERI_MASK_0011, 0, 0x1000, ERI_EXGMAC);
// disable l2l3 state
out8(REG_CONFIG3, in8(REG_CONFIG3) & ~CFG3_READY_TO_L23);
// blackmagic code taken from linux's r8169
phy_out(0x1F, 0x0C42);
auto rg_saw_count = (phy_in(0x13) & 0x3FFF);
phy_out(0x1F, 0);
if (rg_saw_count > 0) {
u16 sw_count_1ms_ini = 16000000 / rg_saw_count;
sw_count_1ms_ini &= 0x0fff;
u32 data = ocp_in(0xd412);
data &= ~0x0fff;
data |= sw_count_1ms_ini;
ocp_out(0xd412, data);
}
u32 data = ocp_in(0xe056);
data &= ~0xf0;
data |= 0x70;
ocp_out(0xe056, data);
data = ocp_in(0xe052);
data &= ~0x6000;
data |= 0x8008;
ocp_out(0xe052, data);
data = ocp_in(0xe0d6);
data &= ~0x1ff;
data |= 0x17f;
ocp_out(0xe0d6, data);
data = ocp_in(0xd420);
data &= ~0x0fff;
data |= 0x47f;
ocp_out(0xd420, data);
ocp_out(0xe63e, 0x1);
ocp_out(0xe63e, 0);
ocp_out(0xc094, 0);
ocp_out(0xc09e, 0);
}
void RTL8168NetworkAdapter::set_phy_speed()
{
// wakeup phy
phy_out(0x1F, 0);
// advertise all available features to get best connection possible
auto auto_negotiation_advertisement = phy_in(PHY_REG_ANAR);
auto_negotiation_advertisement |= ADVERTISE_10_HALF; // 10 mbit half duplex
auto_negotiation_advertisement |= ADVERTISE_10_FULL; // 10 mbit full duplex
auto_negotiation_advertisement |= ADVERTISE_100_HALF; // 100 mbit half duplex
auto_negotiation_advertisement |= ADVERTISE_100_FULL; // 100 mbit full duplex
auto_negotiation_advertisement |= ADVERTISE_PAUSE_CAP; // capable of pause flow control
auto_negotiation_advertisement |= ADVERTISE_PAUSE_ASYM; // capable of asymmetric pause flow control
phy_out(PHY_REG_ANAR, auto_negotiation_advertisement);
auto gigabyte_control = phy_in(PHY_REG_GBCR);
gigabyte_control |= ADVERTISE_1000_HALF; // 1000 mbit half dulpex
gigabyte_control |= ADVERTISE_1000_FULL; // 1000 mbit full duplex
phy_out(PHY_REG_GBCR, gigabyte_control);
// restart auto-negotation with set advertisements
phy_out(PHY_REG_BMCR, BMCR_AUTO_NEGOTIATE | BMCR_RESTART_AUTO_NEGOTIATE);
}
UNMAP_AFTER_INIT void RTL8168NetworkAdapter::initialize_rx_descriptors()
{
auto* rx_descriptors = (RXDescriptor*)m_rx_descriptors_region->vaddr().as_ptr();
for (size_t i = 0; i < number_of_rx_descriptors; ++i) {
auto& descriptor = rx_descriptors[i];
auto region = MM.allocate_contiguous_kernel_region(Memory::page_round_up(RX_BUFFER_SIZE), "RTL8168 RX buffer", Memory::Region::Access::ReadWrite).release_value();
memset(region->vaddr().as_ptr(), 0, region->size()); // MM already zeros out newly allocated pages, but we do it again in case that ever changes
m_rx_buffers_regions.append(move(region));
descriptor.buffer_size = RX_BUFFER_SIZE;
descriptor.flags = RXDescriptor::Ownership; // let the NIC know it can use this descriptor
auto physical_address = m_rx_buffers_regions[i].physical_page(0)->paddr().get();
descriptor.buffer_address_low = physical_address & 0xFFFFFFFF;
descriptor.buffer_address_high = (u64)physical_address >> 32; // cast to prevent shift count >= with of type warnings in 32 bit systems
}
rx_descriptors[number_of_rx_descriptors - 1].flags = rx_descriptors[number_of_rx_descriptors - 1].flags | RXDescriptor::EndOfRing;
}
UNMAP_AFTER_INIT void RTL8168NetworkAdapter::initialize_tx_descriptors()
{
auto* tx_descriptors = (TXDescriptor*)m_tx_descriptors_region->vaddr().as_ptr();
for (size_t i = 0; i < number_of_tx_descriptors; ++i) {
auto& descriptor = tx_descriptors[i];
auto region = MM.allocate_contiguous_kernel_region(Memory::page_round_up(TX_BUFFER_SIZE), "RTL8168 TX buffer", Memory::Region::Access::ReadWrite).release_value();
memset(region->vaddr().as_ptr(), 0, region->size()); // MM already zeros out newly allocated pages, but we do it again in case that ever changes
m_tx_buffers_regions.append(move(region));
descriptor.flags = TXDescriptor::FirstSegment | TXDescriptor::LastSegment;
auto physical_address = m_tx_buffers_regions[i].physical_page(0)->paddr().get();
descriptor.buffer_address_low = physical_address & 0xFFFFFFFF;
descriptor.buffer_address_high = (u64)physical_address >> 32;
}
tx_descriptors[number_of_tx_descriptors - 1].flags = tx_descriptors[number_of_tx_descriptors - 1].flags | TXDescriptor::EndOfRing;
}
UNMAP_AFTER_INIT RTL8168NetworkAdapter::~RTL8168NetworkAdapter()
{
}
bool RTL8168NetworkAdapter::handle_irq(const RegisterState&)
{
bool was_handled = false;
for (;;) {
int status = in16(REG_ISR);
out16(REG_ISR, status);
m_entropy_source.add_random_event(status);
dbgln_if(RTL8168_DEBUG, "RTL8168: handle_irq status={:#04x}", status);
if ((status & (INT_RXOK | INT_RXERR | INT_TXOK | INT_TXERR | INT_RX_OVERFLOW | INT_LINK_CHANGE | INT_RX_FIFO_OVERFLOW | INT_SYS_ERR)) == 0)
break;
was_handled = true;
if (status & INT_RXOK) {
dbgln_if(RTL8168_DEBUG, "RTL8168: RX ready");
receive();
}
if (status & INT_RXERR) {
dbgln_if(RTL8168_DEBUG, "RTL8168: RX error - invalid packet");
}
if (status & INT_TXOK) {
dbgln_if(RTL8168_DEBUG, "RTL8168: TX complete");
m_wait_queue.wake_one();
}
if (status & INT_TXERR) {
dbgln_if(RTL8168_DEBUG, "RTL8168: TX error - invalid packet");
}
if (status & INT_RX_OVERFLOW) {
dmesgln("RTL8168: RX descriptor unavailable (packet lost)");
receive();
}
if (status & INT_LINK_CHANGE) {
m_link_up = (in8(REG_PHYSTATUS) & PHY_LINK_STATUS) != 0;
dmesgln("RTL8168: Link status changed up={}", m_link_up);
}
if (status & INT_RX_FIFO_OVERFLOW) {
dmesgln("RTL8168: RX FIFO overflow");
receive();
}
if (status & INT_SYS_ERR) {
dmesgln("RTL8168: Fatal system error");
}
}
return was_handled;
}
void RTL8168NetworkAdapter::reset()
{
out8(REG_COMMAND, COMMAND_RESET);
while ((in8(REG_COMMAND) & COMMAND_RESET) != 0)
;
}
UNMAP_AFTER_INIT void RTL8168NetworkAdapter::read_mac_address()
{
MACAddress mac {};
for (int i = 0; i < 6; i++)
mac[i] = in8(REG_MAC + i);
set_mac_address(mac);
}
void RTL8168NetworkAdapter::send_raw(ReadonlyBytes payload)
{
dbgln_if(RTL8168_DEBUG, "RTL8168: send_raw length={}", payload.size());
if (payload.size() > TX_BUFFER_SIZE) {
dmesgln("RTL8168: Packet was too big; discarding");
return;
}
auto* tx_descriptors = (TXDescriptor*)m_tx_descriptors_region->vaddr().as_ptr();
auto& free_descriptor = tx_descriptors[m_tx_free_index];
if ((free_descriptor.flags & TXDescriptor::Ownership) != 0) {
dbgln_if(RTL8168_DEBUG, "RTL8168: No free TX buffers, sleeping until one is available");
m_wait_queue.wait_forever("RTL8168NetworkAdapter");
return send_raw(payload);
// if we woke up a TX descriptor is guaranteed to be available, so this should never recurse more than once
// but this can probably be done more cleanly
}
dbgln_if(RTL8168_DEBUG, "RTL8168: Chose descriptor {}", m_tx_free_index);
memcpy(m_tx_buffers_regions[m_tx_free_index].vaddr().as_ptr(), payload.data(), payload.size());
m_tx_free_index = (m_tx_free_index + 1) % number_of_tx_descriptors;
free_descriptor.frame_length = payload.size() & 0x3FFF;
free_descriptor.flags = free_descriptor.flags | TXDescriptor::Ownership;
out8(REG_TXSTART, TXSTART_START); // FIXME: this shouldnt be done so often, we should look into doing this using the watchdog timer
}
void RTL8168NetworkAdapter::receive()
{
auto* rx_descriptors = (RXDescriptor*)m_rx_descriptors_region->vaddr().as_ptr();
for (u16 i = 0; i < number_of_rx_descriptors; ++i) {
auto descriptor_index = (m_rx_free_index + i) % number_of_rx_descriptors;
auto& descriptor = rx_descriptors[descriptor_index];
if ((descriptor.flags & RXDescriptor::Ownership) != 0) {
m_rx_free_index = descriptor_index;
break;
}
u16 flags = descriptor.flags;
u16 length = descriptor.buffer_size & 0x3FFF;
dbgln_if(RTL8168_DEBUG, "RTL8168: receive, flags={:#04x}, length={}, descriptor={}", flags, length, descriptor_index);
if (length > RX_BUFFER_SIZE || (flags & RXDescriptor::ErrorSummary) != 0) {
dmesgln("RTL8168: receive got bad packet, flags={:#04x}, length={}", flags, length);
} else if ((flags & RXDescriptor::FirstSegment) != 0 && (flags & RXDescriptor::LastSegment) == 0) {
VERIFY_NOT_REACHED();
// Our maximum received packet size is smaller than the descriptor buffer size, so packets should never be segmented
// if this happens on a real NIC it might not respect that, and we will have to support packet segmentation
} else {
did_receive({ m_rx_buffers_regions[descriptor_index].vaddr().as_ptr(), length });
}
descriptor.buffer_size = RX_BUFFER_SIZE;
flags = RXDescriptor::Ownership;
if (descriptor_index == number_of_rx_descriptors - 1)
flags |= RXDescriptor::EndOfRing;
descriptor.flags = flags; // let the NIC know it can use this descriptor again
}
}
void RTL8168NetworkAdapter::out8(u16 address, u8 data)
{
m_io_base.offset(address).out(data);
}
void RTL8168NetworkAdapter::out16(u16 address, u16 data)
{
m_io_base.offset(address).out(data);
}
void RTL8168NetworkAdapter::out32(u16 address, u32 data)
{
m_io_base.offset(address).out(data);
}
void RTL8168NetworkAdapter::out64(u16 address, u64 data)
{
// ORDER MATTERS: Some NICs require the high part of the address to be written first
m_io_base.offset(address + 4).out((u32)(data >> 32));
m_io_base.offset(address).out((u32)(data & 0xFFFFFFFF));
}
u8 RTL8168NetworkAdapter::in8(u16 address)
{
return m_io_base.offset(address).in<u8>();
}
u16 RTL8168NetworkAdapter::in16(u16 address)
{
return m_io_base.offset(address).in<u16>();
}
u32 RTL8168NetworkAdapter::in32(u16 address)
{
return m_io_base.offset(address).in<u32>();
}
void RTL8168NetworkAdapter::phy_out(u8 address, u16 data)
{
if (m_version == ChipVersion::Version11) {
TODO();
} else if (m_version == ChipVersion::Version12 || m_version == ChipVersion::Version13) {
TODO();
} else if (m_version >= ChipVersion::Version21) {
if (address == 0x1F) {
m_ocp_base_address = data ? data << 4 : OCP_STANDARD_PHY_BASE;
return;
}
if (m_ocp_base_address != OCP_STANDARD_PHY_BASE)
address -= 0x10;
ocp_phy_out(m_ocp_base_address + address * 2, data);
} else {
VERIFY((address & 0xE0) == 0); // register address is only 5 bit
out32(REG_PHYACCESS, PHY_FLAG | (address & 0x1F) << 16 | (data & 0xFFFF));
while ((in32(REG_PHYACCESS) & PHY_FLAG) != 0)
;
}
}
u16 RTL8168NetworkAdapter::phy_in(u8 address)
{
if (m_version == ChipVersion::Version11) {
TODO();
} else if (m_version == ChipVersion::Version12 || m_version == ChipVersion::Version13) {
TODO();
} else if (m_version >= ChipVersion::Version21) {
if (m_ocp_base_address != OCP_STANDARD_PHY_BASE)
address -= 0x10;
return ocp_phy_in(m_ocp_base_address + address * 2);
} else {
VERIFY((address & 0xE0) == 0); // register address is only 5 bit
out32(REG_PHYACCESS, (address & 0x1F) << 16);
while ((in32(REG_PHYACCESS) & PHY_FLAG) == 0)
;
return in32(REG_PHYACCESS) & 0xFFFF;
}
}
void RTL8168NetworkAdapter::phy_update(u32 address, u32 set, u32 clear)
{
auto value = phy_in(address);
phy_out(address, (value & ~clear) | set);
}
void RTL8168NetworkAdapter::phy_out_batch(const PhyRegister phy_registers[], size_t length)
{
for (size_t i = 0; i < length; i++) {
phy_out(phy_registers[i].address, phy_registers[i].data);
}
}
void RTL8168NetworkAdapter::extended_phy_out(u8 address, u16 data)
{
VERIFY((address & 0xE0) == 0); // register address is only 5 bit
out32(REG_EPHYACCESS, EPHY_FLAG | (address & 0x1F) << 16 | (data & 0xFFFF));
while ((in32(REG_EPHYACCESS) & EPHY_FLAG) != 0)
;
}
u16 RTL8168NetworkAdapter::extended_phy_in(u8 address)
{
VERIFY((address & 0xE0) == 0); // register address is only 5 bit
out32(REG_EPHYACCESS, (address & 0x1F) << 16);
while ((in32(REG_EPHYACCESS) & EPHY_FLAG) == 0)
;
return in32(REG_EPHYACCESS) & 0xFFFF;
}
void RTL8168NetworkAdapter::extended_phy_initialize(const EPhyUpdate ephy_info[], size_t length)
{
for (size_t i = 0; i < length; i++) {
auto updated_value = (extended_phy_in(ephy_info[i].offset) & ~ephy_info[i].clear) | ephy_info[i].set;
extended_phy_out(ephy_info[i].offset, updated_value);
}
}
void RTL8168NetworkAdapter::eri_out(u32 address, u32 mask, u32 data, u32 type)
{
out32(REG_ERI_DATA, data);
out32(REG_ERI_ADDR, ERI_FLAG | type | mask | address);
while ((in32(REG_ERI_ADDR) & ERI_FLAG) != 0)
;
}
u32 RTL8168NetworkAdapter::eri_in(u32 address, u32 type)
{
out32(REG_ERI_ADDR, type | ERI_MASK_1111 | address);
while ((in32(REG_ERI_ADDR) & ERI_FLAG) == 0)
;
return in32(REG_ERI_DATA);
}
void RTL8168NetworkAdapter::eri_update(u32 address, u32 mask, u32 set, u32 clear, u32 type)
{
auto value = eri_in(address, type);
eri_out(address, mask, (value & ~clear) | set, type);
}
void RTL8168NetworkAdapter::exgmac_out_batch(const ExgMacRegister exgmac_registers[], size_t length)
{
for (size_t i = 0; i < length; i++) {
eri_out(exgmac_registers[i].address, exgmac_registers[i].mask, exgmac_registers[i].value, ERI_EXGMAC);
}
}
void RTL8168NetworkAdapter::csi_out(u32 address, u32 data)
{
VERIFY(m_version >= ChipVersion::Version4);
out32(REG_CSI_DATA, data);
auto modifier = CSI_BYTE_ENABLE;
if (m_version == ChipVersion::Version20) {
modifier |= CSI_FUNC_NIC;
} else if (m_version == ChipVersion::Version26) {
modifier |= CSI_FUNC_NIC2;
}
out32(REG_CSI_ADDR, CSI_FLAG | (address & 0xFFF) | modifier);
while ((in32(REG_CSI_ADDR) & CSI_FLAG) != 0)
;
}
u32 RTL8168NetworkAdapter::csi_in(u32 address)
{
VERIFY(m_version >= ChipVersion::Version4);
auto modifier = CSI_BYTE_ENABLE;
if (m_version == ChipVersion::Version20) {
modifier |= CSI_FUNC_NIC;
} else if (m_version == ChipVersion::Version26) {
modifier |= CSI_FUNC_NIC2;
}
out32(REG_CSI_ADDR, (address & 0xFFF) | modifier);
while ((in32(REG_CSI_ADDR) & CSI_FLAG) == 0)
;
return in32(REG_CSI_DATA) & 0xFFFF;
}
void RTL8168NetworkAdapter::csi_enable(u32 bits)
{
auto csi = csi_in(0x70c) & 0x00ffffff;
csi_out(0x70c, csi | bits);
}
void RTL8168NetworkAdapter::ocp_out(u32 address, u32 data)
{
VERIFY((address & 0xFFFF0001) == 0);
out32(REG_OCP_DATA, OCP_FLAG | address << 15 | data);
}
u32 RTL8168NetworkAdapter::ocp_in(u32 address)
{
VERIFY((address & 0xFFFF0001) == 0);
out32(REG_OCP_DATA, address << 15);
return in32(REG_OCP_DATA);
}
void RTL8168NetworkAdapter::ocp_phy_out(u32 address, u32 data)
{
VERIFY((address & 0xFFFF0001) == 0);
out32(REG_GPHY_OCP, OCP_FLAG | (address << 15) | data);
while ((in32(REG_GPHY_OCP) & OCP_FLAG) != 0)
;
}
u16 RTL8168NetworkAdapter::ocp_phy_in(u32 address)
{
VERIFY((address & 0xFFFF0001) == 0);
out32(REG_GPHY_OCP, address << 15);
while ((in32(REG_GPHY_OCP) & OCP_FLAG) == 0)
;
return in32(REG_GPHY_OCP) & 0xFFFF;
}
void RTL8168NetworkAdapter::identify_chip_version()
{
auto transmit_config = in32(REG_TXCFG);
auto registers = transmit_config & 0x7c800000;
auto hw_version_id = transmit_config & 0x700000;
m_version_uncertain = false;
switch (registers) {
case 0x30000000:
m_version = ChipVersion::Version1;
break;
case 0x38000000:
if (hw_version_id == 00000) {
m_version = ChipVersion::Version2;
} else if (hw_version_id == 0x500000) {
m_version = ChipVersion::Version3;
} else {
m_version = ChipVersion::Version3;
m_version_uncertain = true;
}
break;
case 0x3C000000:
if (hw_version_id == 00000) {
m_version = ChipVersion::Version4;
} else if (hw_version_id == 0x200000) {
m_version = ChipVersion::Version5;
} else if (hw_version_id == 0x400000) {
m_version = ChipVersion::Version6;
} else {
m_version = ChipVersion::Version6;
m_version_uncertain = true;
}
break;
case 0x3C800000:
if (hw_version_id == 0x100000) {
m_version = ChipVersion::Version7;
} else if (hw_version_id == 0x300000) {
m_version = ChipVersion::Version8;
} else {
m_version = ChipVersion::Version8;
m_version_uncertain = true;
}
break;
case 0x28000000:
if (hw_version_id == 0x100000) {
m_version = ChipVersion::Version9;
} else if (hw_version_id == 0x300000) {
m_version = ChipVersion::Version10;
} else {
m_version = ChipVersion::Version10;
m_version_uncertain = true;
}
break;
case 0x28800000:
if (hw_version_id == 00000) {
m_version = ChipVersion::Version11;
} else if (hw_version_id == 0x200000) {
m_version = ChipVersion::Version12;
} else if (hw_version_id == 0x300000) {
m_version = ChipVersion::Version13;
} else {
m_version = ChipVersion::Version13;
m_version_uncertain = true;
}
break;
case 0x2C000000:
if (hw_version_id == 0x100000) {
m_version = ChipVersion::Version14;
} else if (hw_version_id == 0x200000) {
m_version = ChipVersion::Version15;
} else {
m_version = ChipVersion::Version15;
m_version_uncertain = true;
}
break;
case 0x2C800000:
if (hw_version_id == 00000) {
m_version = ChipVersion::Version16;
} else if (hw_version_id == 0x100000) {
m_version = ChipVersion::Version17;
} else {
m_version = ChipVersion::Version17;
m_version_uncertain = true;
}
break;
case 0x48000000:
if (hw_version_id == 00000) {
m_version = ChipVersion::Version18;
} else if (hw_version_id == 0x100000) {
m_version = ChipVersion::Version19;
} else {
m_version = ChipVersion::Version19;
m_version_uncertain = true;
}
break;
case 0x48800000:
if (hw_version_id == 00000) {
m_version = ChipVersion::Version20;
} else {
m_version = ChipVersion::Version20;
m_version_uncertain = true;
}
break;
case 0x4C000000:
if (hw_version_id == 00000) {
m_version = ChipVersion::Version21;
} else if (hw_version_id == 0x100000) {
m_version = ChipVersion::Version22;
} else {
m_version = ChipVersion::Version22;
m_version_uncertain = true;
}
break;
case 0x50000000:
if (hw_version_id == 00000) {
m_version = ChipVersion::Version23;
} else if (hw_version_id == 0x100000) {
m_version = ChipVersion::Version27;
} else if (hw_version_id == 0x200000) {
m_version = ChipVersion::Version28;
} else {
m_version = ChipVersion::Version28;
m_version_uncertain = true;
}
break;
case 0x50800000:
if (hw_version_id == 00000) {
m_version = ChipVersion::Version24;
} else if (hw_version_id == 0x100000) {
m_version = ChipVersion::Version25;
} else {
m_version = ChipVersion::Version25;
m_version_uncertain = true;
}
break;
case 0x5C800000:
if (hw_version_id == 00000) {
m_version = ChipVersion::Version26;
} else {
m_version = ChipVersion::Version26;
m_version_uncertain = true;
}
break;
case 0x54000000:
if (hw_version_id == 00000) {
m_version = ChipVersion::Version29;
} else if (hw_version_id == 0x100000) {
m_version = ChipVersion::Version30;
} else {
m_version = ChipVersion::Version30;
m_version_uncertain = true;
}
break;
default:
dbgln_if(RTL8168_DEBUG, "Unable to determine device version: {:#04x}", registers);
m_version = ChipVersion::Unknown;
m_version_uncertain = true;
break;
}
}
String RTL8168NetworkAdapter::possible_device_name()
{
switch (m_version) { // We are following *BSD's versioning scheme, the comments note linux's versioning scheme, but they dont match up exactly
case ChipVersion::Version1:
case ChipVersion::Version2:
case ChipVersion::Version3:
return "RTL8168B/8111B"; // 11, 12, 17
case ChipVersion::Version4:
case ChipVersion::Version5:
case ChipVersion::Version6:
return "RTL8168C/8111C"; // 19, 20, 21, 22
case ChipVersion::Version7:
case ChipVersion::Version8:
return "RTL8168CP/8111CP"; // 18, 23, 24
case ChipVersion::Version9:
case ChipVersion::Version10:
return "RTL8168D/8111D"; // 25, 26
case ChipVersion::Version11:
case ChipVersion::Version12:
case ChipVersion::Version13:
return "RTL8168DP/8111DP"; // 27, 28, 31
case ChipVersion::Version14:
case ChipVersion::Version15:
return "RTL8168E/8111E"; // 32, 33
case ChipVersion::Version16:
case ChipVersion::Version17:
return "RTL8168E-VL/8111E-VL"; // 34
case ChipVersion::Version18:
case ChipVersion::Version19:
return "RTL8168F/8111F"; // 35, 36
case ChipVersion::Version20:
return "RTL8411"; // 38
case ChipVersion::Version21:
case ChipVersion::Version22:
return "RTL8168G/8111G"; // 40, 41, 42
case ChipVersion::Version23:
case ChipVersion::Version27:
case ChipVersion::Version28:
return "RTL8168EP/8111EP"; // 49, 50, 51
case ChipVersion::Version24:
case ChipVersion::Version25:
return "RTL8168GU/8111GU"; // ???
case ChipVersion::Version26:
return "RTL8411B"; // 44
case ChipVersion::Version29:
case ChipVersion::Version30:
return "RTL8168H/8111H"; // 45, 46
case ChipVersion::Unknown:
return "Unknown";
}
VERIFY_NOT_REACHED();
}
bool RTL8168NetworkAdapter::link_full_duplex()
{
u8 phystatus = in8(REG_PHYSTATUS);
return !!(phystatus & (PHYSTATUS_FULLDUP | PHYSTATUS_1000MF));
}
i32 RTL8168NetworkAdapter::link_speed()
{
if (!link_up())
return NetworkAdapter::LINKSPEED_INVALID;
u8 phystatus = in8(REG_PHYSTATUS);
if (phystatus & PHYSTATUS_1000MF)
return 1000;
if (phystatus & PHYSTATUS_100M)
return 100;
if (phystatus & PHYSTATUS_10M)
return 10;
return NetworkAdapter::LINKSPEED_INVALID;
}
}