Files
DrDoof 422dc3f24d port, dhcp: drop the called/done traces from the console and syslog
rtl837x_port.c prints "called" and "done" around mirroring, PVID, VLAN
and L2 setup, and dhcp.c around discover, request, start and stop. They
land on the console on every boot and config replay, and in the log
once syslog is running. The VLAN line keeps the VLAN, members and
tagged values, only without the function name in front.

On the KP_9000_6XHML_X2 this takes 359 bytes out of BANK1, 88 out of
BANK2 and 24 out of BANK3. BANK1 goes from 431 to 790 bytes free.
2026-09-25 07:45:03 +02:00

991 lines
26 KiB
C

/*
* This is a driver implementation for the Port features for the RTL827x platform
* This code is in the Public Domain
*/
// #define REGDBG
// #define DEBUG
#include <stdint.h>
#include "rtl837x_common.h"
#include "rtl837x_sfr.h"
#include "rtl837x_regs.h"
#include "rtl837x_port.h"
#include "rtl837x_phy.h"
#include "phy.h"
#include "machine.h"
#pragma codeseg BANK1
#pragma constseg BANK1
extern __code const uint8_t * __code const hex;
extern __code const uint16_t bit_mask[16];
extern __code const struct machine machine;
extern __xdata uint8_t sfr_data[4];
extern __xdata uint16_t vlan_ptr;
extern __xdata uint8_t vlan_names[VLAN_NAMES_SIZE];
extern __xdata struct machine_runtime machine_detected;
__xdata uint32_t l2_head;
__xdata struct vlan_settings vlan_settings;
// Wait for the TABLE ready
static void wait_table_ready(void)
{
do {
reg_read(RTL837X_TBL_CTRL);
} while (SFR_DATA_0 & TBL_EXECUTE);
}
void port_mirror_set(uint8_t port, __xdata uint16_t rx_pmask, __xdata uint16_t tx_pmask) __banked
{
print_string("Mirroring port: "); print_byte(port); print_string(" with rx-mask: ");
print_short(rx_pmask); print_string(", tx mask: "); print_short(tx_pmask);
write_char('\n');
REG_WRITE(RTL837x_MIRROR_CONF, rx_pmask >> 8, rx_pmask, tx_pmask >> 8, tx_pmask);
REG_WRITE(RTL837x_MIRROR_CTRL, 0, 0, 0, (port << 1) | 0x1);
}
void port_mirror_del(void) __banked
{
REG_SET(RTL837x_MIRROR_CTRL, 0);
}
bool port_ingress_filter(__xdata uint8_t port, __xdata vlan_ingress_mode_t type) __banked
{
if (port > 9 || type >= VLAN_INVALID) {
print_string("Invalid port or ingress filter type\n");
return false;
}
if (type & 0x1) {
reg_bit_set(RTL837x_REG_INGRESS, port << 1);
} else {
reg_bit_clear(RTL837x_REG_INGRESS, port << 1);
}
if (type & 0x2) {
reg_bit_set(RTL837x_REG_INGRESS, (port << 1) + 1);
} else {
reg_bit_clear(RTL837x_REG_INGRESS, (port << 1) + 1);
}
return true;
}
vlan_ingress_mode_t port_ingress_filter_get(__xdata uint8_t port) __banked
{
reg_read_m(RTL837x_REG_INGRESS);
if (port > 9) {
return VLAN_INVALID;
}
// Each port is represented by 2 bits in the ingress register, starting from bit 0 for port 0
const uint8_t sfr_index = 3 - (port / 4);
const uint8_t shift = (port % 4) << 1;
return (vlan_ingress_mode_t)((sfr_data[sfr_index] >> shift) & 0x03);
}
/*
* Define a Primary VLAN ID for a port
*/
static void port_pvid_write(uint8_t port, __xdata uint16_t pvid)
{
// r4e1c:00001001 R4e1c-000017d0 r6738:00000000 R6738-00000000 (no filtering)
uint16_t reg = RTL837x_PVID_BASE_REG + ((port >> 1) << 2);
reg_read_m(reg);
if (port & 0x1) {
REG_WRITE(reg, sfr_data[0], pvid >> 4, (sfr_data[2] & 0x0f) | (pvid << 4), sfr_data[3]);
} else {
REG_WRITE(reg, sfr_data[0], sfr_data[1], (sfr_data[2] & 0xf0) | (pvid >> 8), pvid);
}
}
void port_pvid_set(uint8_t port, __xdata uint16_t pvid) __banked
{
uint8_t lag = port_lag_of(port);
if (lag == PORT_LAG_NONE) {
port_pvid_write(port, pvid);
return;
}
uint16_t members = port_lag_members_get(lag);
for (uint8_t i = 0; i < 10; i++)
if ((members >> i) & 1)
port_pvid_write(i, pvid);
}
uint16_t port_pvid_get(uint8_t port) __banked
{
uint16_t reg = RTL837x_PVID_BASE_REG + ((port >> 1) << 2);
reg_read_m(reg);
if (port & 0x1) {
return (sfr_data[1] << 4) | (sfr_data[2] >> 4);
} else {
return ((sfr_data[2] & 0x0f) << 8) | sfr_data[3];
}
}
void vlan_delete(uint16_t vlan) __banked
{
if (!vlan || vlan >= 0xfff)
return;
vlan_name_remove(vlan);
REG_WRITE(RTL837x_TBL_DATA_IN_A, 0, 0, 0, 0);
REG_WRITE(RTL837X_TBL_CTRL, vlan >> 8, vlan, TBL_VLAN, TBL_WRITE | TBL_EXECUTE);
}
void vlan_name_remove(uint16_t vlan) __banked
{
static __xdata uint16_t name_pos;
static __xdata uint16_t entry_start;
static __xdata uint16_t pos;
static __xdata uint16_t entry_len;
static __xdata uint16_t move_count;
static __xdata uint16_t j;
name_pos = vlan_name(vlan);
if (name_pos == 0xffff)
return;
entry_start = name_pos - 3;
pos = entry_start;
while (pos < vlan_ptr && vlan_names[pos] != ' ')
pos++;
if (pos >= vlan_ptr)
return;
pos++;
entry_len = pos - entry_start;
move_count = vlan_ptr - pos;
for (j = 0; j < move_count; j++)
vlan_names[entry_start + j] = vlan_names[pos + j];
vlan_ptr -= entry_len;
vlan_names[vlan_ptr] = 0;
}
/*
* Reads VLAN information from VLAN table
* Returns data in sfr_data
*/
int8_t vlan_get(uint16_t vlan) __banked
{
if (vlan >= 0xfff) // VLAN 4095 is special
return -1;
REG_WRITE(RTL837X_TBL_CTRL, vlan >> 8, vlan, TBL_VLAN, TBL_EXECUTE);
wait_table_ready();
reg_read_m(RTL837x_L2_DATA_OUT_A);
return 0;
}
__xdata uint16_t vlan_name(uint16_t vlan) __banked
{
__xdata int16_t i = 0;
__xdata uint8_t begin = 1;
while (vlan_names[i]) {
if (begin && vlan_names[i] == hex[(vlan >> 8) & 0xf] && vlan_names[i + 1] == hex[(vlan >> 4) & 0xf] && vlan_names[i + 2] == hex[vlan & 0xf])
break;
begin = vlan_names[i++] == ' ' ? 1 : 0;
}
if (vlan_names[i])
return i + 3;
return 0xffff;
}
/*
* Create a VLAN
* The arguments are passed in global structure vlan_settings
* A member that is not tagged, is untagged
*/
void vlan_create(void) __banked
{
if (!vlan_settings.vlan || vlan_settings.vlan >= 0xfff) {
print_string("\nInvalid VLAN: "); print_short(vlan_settings.vlan); write_char('\n');
return;
}
// For now, the CPU-port is always a tagged member:
vlan_settings.members |= 0x0200; // Set 10th bit
vlan_settings.tagged |= 0x0200;
print_string("vlan: "); print_short(vlan_settings.vlan);
print_string(", members: "); print_short(vlan_settings.members);
print_string(", tagged: "); print_short(vlan_settings.tagged); write_char('\n');
uint16_t a = (~vlan_settings.members) ^ vlan_settings.tagged ^ vlan_settings.members;
// On RTL8372, port-bits 0-2 must be 0, although they are not members
if (!machine_detected.isRTL8373) {
a &= 0x1f8;
vlan_settings.tagged &= 0x3f8;
}
// Initialize VLAN table with VLAN 1
REG_WRITE(RTL837x_TBL_DATA_IN_A, 0x02, (a >> 6) & 0x0f, (a << 2) | (vlan_settings.members >> 8), vlan_settings.members);
REG_WRITE(RTL837X_TBL_CTRL, vlan_settings.vlan >> 8, vlan_settings.vlan, TBL_VLAN, TBL_WRITE | TBL_EXECUTE);
wait_table_ready();
}
/*
* Configures a default VLAN 1 and enables 4k VLAN tables
* All ports are made members of the VLAN and VLAN filtering
* is enabled on all ports
* PVID is set to 1 for all ports
* Called upon reboot
*/
void vlan_setup(void) __banked
{
// No VLAN names set up so far
vlan_ptr = 0;
vlan_names[0] = 0;
// Initialize VLAN table for VLAN 1, by disabling that entry
REG_SET(RTL837x_TBL_DATA_IN_A, machine_detected.isRTL8373? 0x0007ffff : 0x0007e3f8);
REG_SET(RTL837X_TBL_CTRL, 0x00010303);
wait_table_ready();
// Set PVID 1 for every port. TODO: Skip unused ports!
for (uint8_t i = machine.min_port; i <= machine.max_port + 1; i++) { // Do this also for the CPU port (+1)
uint16_t reg = RTL837x_PVID_BASE_REG + ((i >> 1) << 2);
#ifdef DEBUG
print_byte(i); write_char(':'); write_char(' '); print_short(reg); write_char('=');
print_short(reg); write_char(' ');
#endif
reg_read_m(reg);
if (i & 0x1) {
REG_WRITE(reg, sfr_data[0], 0, (sfr_data[2] & 0x0f) | 0x10, sfr_data[3]);
} else {
REG_WRITE(reg, sfr_data[0], sfr_data[1], sfr_data[2] & 0xf0, 0x01);
}
#ifdef DEBUG
reg_read_m(reg);
write_char(' '); write_char('A'); write_char('>'); print_sfr_data();
#endif
// EGRESS filtering for port: removal of additional VLAN tag (mode 0x3 for each port)
reg_bit_clear(RTL837X_VLAN_PORT_EGR_TAG, i << 1);
reg_bit_clear(RTL837X_VLAN_PORT_EGR_TAG, (i << 1) + 1);
// Enable INGRESS filtering for port: discard packets not belonging to member VLAN on that port
port_ingress_vlan_filter_set(i, true);
#ifdef DEBUG
print_string("\n");
#endif
}
// Ingress filtering. 2 bits per port: allow tagged (01) / untagged (10) and all (00)
REG_SET(RTL837x_REG_INGRESS, 0); // No filtering for all ports
// Enable 4k VLAN
REG_SET(RTL837X_VLAN_CTRL, VLAN_CVLAN_FILTER);
REG_SET(RTL837X_VLAN_L2_LRN_DIS_0, 0);
REG_SET(RTL837X_VLAN_L2_LRN_DIS_1, 0);
// Enable VLAN 1: Ports 0-9, i.e. including the CPU port are untagged members
REG_SET(RTL837x_TBL_DATA_IN_A, machine_detected.isRTL8373? 0x0207ffff : 0x0207e3f8); // 02: Entry valid, 7...: membership
REG_SET(RTL837X_TBL_CTRL, 0x00010303); // Write VLAN 1
wait_table_ready();
#ifdef DEBUG
print_string("\nvlan_setup, REG 0x6738: "); print_reg(0x6738);
print_string("\nvlan_setup, REG 0x4e10: "); print_reg(0x4e10);
print_string("\nvlan_setup, REG 0x4e18: "); print_reg(0x4e18);
print_string("\nvlan_setup, REG 0x4e14: "); print_reg(0x4e14);
print_string("\nvlan_setup, REG 0x4e30: "); print_reg(0x4e30);
print_string("\nvlan_setup, REG 0x4e34: "); print_reg(0x4e34);
print_string("\nvlan_setup, REG 0x4f48: "); print_reg(0x4f48);
print_string("\nvlan_setup, REG 0x4f4c: "); print_reg(0x4f4c);
#endif
}
/*
* Forget the dynamic L2 entries learned on one port.
*/
void port_l2_forget_port(uint8_t port) __banked
{
REG_SET(RTL837x_L2_TBL_FLUSH_CNF, 0x0); /* port-based, dynamic entries */
REG_SET(RTL837x_L2_TBL_FLUSH_CTRL, L2_TBL_FLUSH_EXEC | (((uint16_t)1) << port));
do {
reg_read(RTL837x_L2_TBL_FLUSH_CTRL);
} while (SFR_DATA_16);
}
/*
* Forget all dynamic L2 learned entries
*/
uint8_t port_l2_forget(void) __banked
{
// Configure the entries to be flushed:
// port-based (bits 0-1 are 0 and dynamic entries, bit 2 specifies dynamic entries
REG_SET(RTL837x_L2_TBL_FLUSH_CNF, 0x0);
// Flush L2 table for all ports by setting the ports and the flush-exec bit (bit 16)
uint16_t mask = PMASK_6;
if (machine_detected.isRTL8373)
mask = PMASK_9;
REG_SET(RTL837x_L2_TBL_FLUSH_CTRL, L2_TBL_FLUSH_EXEC | mask);
// Wait for flush completed
do {
reg_read_m(RTL837x_L2_TBL_FLUSH_CTRL);
} while (sfr_data[1]);
return 0;
}
void port_l2_learned(void) __banked
{
// Whait for any table action to be finished
wait_table_ready();
print_string("\n\tMAC\t\tVLAN\ttype\tport\n");
__xdata uint16_t entry = 0x0000;
__xdata uint16_t first_entry = 0xffff; // Table does not have that many entries
while (1) {
uint8_t port = 0;
uint8_t lag;
reg_read_m(RTL837x_TBL_DATA_0);
REG_WRITE(RTL837x_TBL_DATA_0, sfr_data[0], sfr_data[1],sfr_data[2] | 0xc0, sfr_data[3]);
REG_WRITE(RTL837X_TBL_CTRL, (entry >> 8) & 0xf, entry, TBL_L2_UNICAST, TBL_EXECUTE);
wait_table_ready();
reg_read_m(RTL837x_TBL_DATA_0);
entry = (((uint16_t)sfr_data[2] & 0x0f) << 8) | sfr_data[3];
if (first_entry == 0xffff) {
first_entry = entry;
} else {
if (first_entry == entry)
break;
}
// MAC
reg_read_m(RTL837x_L2_DATA_OUT_B);
if ((sfr_data[0] & 0x20)) { // Check entry is valid
print_byte(sfr_data[2]); write_char(':');
print_byte(sfr_data[3]); write_char(':');
port = (sfr_data[0] >> 6) & 0x3;
reg_read_m(RTL837x_L2_DATA_OUT_A);
print_byte(sfr_data[0]); write_char(':');
print_byte(sfr_data[1]); write_char(':');
print_byte(sfr_data[2]); write_char(':');
print_byte(sfr_data[3]); write_char('\t');
// VLAN
reg_read_m(RTL837x_L2_DATA_OUT_B);
print_short( (((uint16_t) (sfr_data[0] & 0x0f)) << 8) | sfr_data[1]); // VLAN
// type
reg_read_m(RTL837x_L2_DATA_OUT_C);
if (sfr_data[1] & 0x1)
print_string("\tstatic\t");
else
print_string("\tlearned\t");
port |= (sfr_data[3] & 0x3) << 2;
lag = port_lag_of(port);
if (lag == PORT_LAG_NONE) {
print_phys_port(port);
} else {
print_string("LAG");
itoa(lag + 1);
}
}
entry++;
print_string("\n");
}
}
/*
* Static L2 multicast entry for the link-local group 01:80:C2:00:00:<mac_last>
* in VLAN `vid`, with member portmask `pmask` (bit 9 = CPU port).
*/
void port_l2mc_set(uint8_t mac_last, __xdata uint16_t vid, __xdata uint16_t pmask) __banked
{
wait_table_ready();
REG_WRITE(RTL837x_TBL_DATA_IN_A, 0xc2, 0x00, 0x00, mac_last);
REG_WRITE(RTL837x_TBL_DATA_IN_B, 0x20 | (vid >> 8) | ((pmask & 0x3) << 6), vid, 0x01, 0x80);
REG_WRITE(RTL837x_TBL_DATA_IN_C, 0, 0, 0, pmask >> 2);
REG_WRITE(RTL837X_TBL_CTRL, 0, 0, TBL_L2_UNICAST, TBL_WRITE | TBL_EXECUTE);
wait_table_ready();
}
/*
* Basic L2 configuration such as time to forget an entry
*/
void port_l2_setup(void) __banked
{
port_l2_forget();
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
// Limit the number of automatically learned MAC-Entries per port to 0x1040
uint8_t idx = (i << 2);
REG_SET(RTL837X_L2_LRN_PORT_CONSTRAINT + idx, 0x00001040);
// All ports may communicate with each other and CPU-Port
uint16_t mask = PMASK_CPU | PMASK_6;
if (machine_detected.isRTL8373)
mask = PMASK_CPU | PMASK_9;
REG_SET(RTL837X_PORT_ISOLATION_BASE + idx, mask);
}
// When maximim entries learned, then simply flood the packet
reg_bit_set(RTL837X_L2_LRN_PORT_CONSTRT_ACT, 0);
}
void port_media_show(uint8_t port) __banked
{
uint8_t pause;
if (!machine.is_sfp[port]) {
reg_read_m(RTL837X_REG_LINKS_STS);
if (sfr_data[(port / 8) + 1] >> (port % 8) & 1) {
print_string("Link role: ");
reg_read_m(RTL837X_MAC_MSTR_SLV_STS);
if (sfr_data[3 - (port >> 3)] & (1 << (port & 7)))
print_string("master\n");
else
print_string("slave\n");
}
}
print_string("Pause negotiated:");
reg_read_m(RTL837X_MAC_TX_PAUSE_STS);
pause = sfr_data[3 - (port >> 3)] & (1 << (port & 7));
if (pause)
print_string(" TX");
reg_read_m(RTL837X_MAC_RX_PAUSE_STS);
if (sfr_data[3 - (port >> 3)] & (1 << (port & 7))) {
print_string(" RX");
pause = 1;
}
if (!pause)
print_string(" none");
write_char('\n');
}
void port_stats_print(void) __banked
{
print_string("\nPort\tState\tLink\tTxGood\t\tTxBad\t\tRxGood\t\tRxBad\n");
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
print_phys_port(i); write_char('\t');
if (!machine.is_sfp[i]) {
phy_read(i, PHY_MMD31, 0xa610);
if (SFR_DATA_8 == 0x20)
print_string("On\t");
else
print_string("Off\t");
} else { // An SFP Module
if (!gpio_pin_test(machine.sfp_port[machine.is_sfp[i]-1].pin_detect)) {
print_string("SFP IN\t");
} else {
print_string("NO SFP\t");
}
}
uint8_t b = 0;
// Determine link state
reg_read_m(RTL837X_REG_LINKS_STS);
if(!((sfr_data[(i / 8) + 1] >> ( i % 8 ) & 1)))
{
b = 99;
}
else
{
if (i < 8)
reg_read_m(RTL837X_REG_LINKS);
else
reg_read_m(RTL837X_REG_LINKS_89);
b = sfr_data[3 - ((i & 7) >> 1)];
b = (i & 1) ? b >> 4 : b & 0xf;
}
switch (b) {
case 0:
print_string("10M\t");
break;
case 1:
print_string("100M\t");
break;
case 2:
print_string("1000M\t");
break;
case 4:
print_string("10G\t");
break;
case 5:
print_string("2.5G\t");
break;
case 6:
print_string("5G\t");
break;
case 99:
print_string("Down\t");
break;
default:
print_string("Up\t");
break;
}
STAT_GET(STAT_COUNTER_TX_PKTS, i);
print_reg(RTL837X_STAT_V_LOW); write_char('\t');
STAT_GET(STAT_COUNTER_ERR_PKTS, i);
print_reg(RTL837X_STAT_V_LOW); write_char('\t');
STAT_GET(STAT_COUNTER_RX_PKTS, i);
print_reg(RTL837X_STAT_V_LOW); write_char('\t');
STAT_GET(STAT_COUNTER_ERR_PKTS, i);
print_reg(RTL837X_STAT_V_HIGH); write_char('\t');
print_string("\n");
}
}
void port_isolate(uint8_t port, __xdata uint16_t pmask) __banked
{
if (port <= machine.max_port)
REG_SET(RTL837X_PORT_ISOLATION_BASE + (port << 2), pmask);
}
uint16_t port_isolation_get(uint8_t port) __banked
{
if (port > machine.max_port)
return 0;
reg_read_m(RTL837X_PORT_ISOLATION_BASE + (port << 2));
return ((uint16_t)sfr_data[2]) << 8 | sfr_data[3];
}
void port_eee_enable(__xdata uint8_t port,__xdata uint8_t speed) __banked
{
if (machine.is_sfp[port])
{
print_string("EEE can't be enabled for SFP port "); print_phys_port(port); print_string("\n");
return;
}
REG_SET(RTL837X_EEE_CTRL_BASE + (port << 8), EEE_RX_ENABLE | EEE_TX_ENABLE);
print_string("EEE on for "); print_phys_port(port); print_string(" speed ");
// Enable all speeds up to the specified speed
if (speed & EEE_100) {
print_string("100m\n");
// Enable EEE advertisement for 100BASE-T via EEE Advertisement Reg
phy_write(port, PHY_MMD_AN, PHY_EEE_ADV, PHY_EEE_BIT_100M);
if (!(speed & EEE_NORESET))
phy_reset(port);
return;
}
if (speed & EEE_1000) {
print_string("1g\n");
// Disable EEE advertisement for 2.5GBASE-T via EEE Advertisement Reg 2
phy_write(port, PHY_MMD_AN, PHY_EEE_ADV2, 0);
// Enable EEE advertisement for 100/1000BASE-T via EEE Advertisement Reg
phy_write(port, PHY_MMD_AN, PHY_EEE_ADV, PHY_EEE_BIT_1G | PHY_EEE_BIT_100M);
if (!(speed & EEE_NORESET))
phy_reset(port);
return;
}
if (speed & EEE_2G5) {
print_string("2g5\n");
// Enable EEE advertisement for 100/1000BASE-T via EEE Advertisement Reg
phy_write(port, PHY_MMD_AN, PHY_EEE_ADV, PHY_EEE_BIT_1G | PHY_EEE_BIT_100M);
// Enable EEE advertisement for 2.5GBASE-T via EEE Advertisement Reg 2
phy_write(port, PHY_MMD_AN, PHY_EEE_ADV2, PHY_EEE_BIT_2G5);
if (!(speed & EEE_NORESET))
phy_reset(port);
return;
}
if (speed & EEE_5G) {
print_string("5g\n");
// Enable EEE advertisement for 100/1000BASE-T via EEE Advertisement Reg
phy_write(port, PHY_MMD_AN, PHY_EEE_ADV, PHY_EEE_BIT_1G | PHY_EEE_BIT_100M);
// Enable EEE advertisement for 2.5GBASE-T via EEE Advertisement Reg 2
phy_write(port, PHY_MMD_AN, PHY_EEE_ADV2, PHY_EEE_BIT_2G5 | PHY_EEE_BIT_5G);
if (!(speed & EEE_NORESET))
phy_reset(port);
return;
}
if (speed & EEE_10G) {
print_string("10g\n");
// Enable EEE advertisement for 100/1000BASE-T via EEE Advertisement Reg
phy_write(port, PHY_MMD_AN, PHY_EEE_ADV, PHY_EEE_BIT_10G | PHY_EEE_BIT_1G | PHY_EEE_BIT_100M);
// Enable EEE advertisement for 2.5GBASE-T via EEE Advertisement Reg 2
phy_write(port, PHY_MMD_AN, PHY_EEE_ADV2, PHY_EEE_BIT_2G5 | PHY_EEE_BIT_5G);
if (!(speed & EEE_NORESET))
phy_reset(port);
return;
}
}
void port_eee_disable(uint8_t port) __banked
{
if (machine.is_sfp[port])
return;
print_string("EEE off for "); print_phys_port(port); write_char('\n');
REG_SET(RTL837X_EEE_CTRL_BASE + (port << 8), 0);
// Disable EEE advertisement for 100/1000BASE-T via EEE Advertisement Reg
phy_write(port, PHY_MMD_AN, PHY_EEE_ADV, 0);
// Disable EEE advertisement for 2.5GBASE-T via EEE Advertisement Reg 2
phy_write(port, PHY_MMD_AN, PHY_EEE_ADV2, 0);
phy_reset(port);
}
void port_eee_status(uint8_t port) __banked
{
print_string("Port: "); print_phys_port(port);
print_string(": ");
if (machine.is_sfp[port]) {
print_string("SFP\n");
return;
}
uint16_t v;
print_string("Advertising: ");
if (machine.n_10g) {
phy_read(port, PHY_MMD_AN, PHY_EEE_ADV);
v = SFR_DATA_U16;
if (v & PHY_EEE_BIT_10G)
print_string(" 10G");
else
print_string(" ");
}
phy_read(port, PHY_MMD_AN, PHY_EEE_ADV2);
v = SFR_DATA_U16;
if (machine.n_10g) {
if (v & PHY_EEE_BIT_5G)
print_string(" 5G");
else
print_string(" ");
}
v = SFR_DATA_U16;
if (v & PHY_EEE_BIT_2G5)
print_string(" 2.5G");
else
print_string(" ");
phy_read(port, PHY_MMD_AN, PHY_EEE_ADV);
v = SFR_DATA_U16;
if (v & PHY_EEE_BIT_1G)
print_string(" 1G ");
else
print_string(" ");
if (v & PHY_EEE_BIT_100M)
print_string(" 100M");
else
print_string(" ");
print_string(" Link Partner: ");
if (machine.n_10g) {
phy_read(port, PHY_MMD_AN, PHY_EEE_LP_ABILITY);
v = SFR_DATA_U16;
if (v & PHY_EEE_BIT_10G)
print_string(" 10G");
else
print_string(" ");
}
phy_read(port, PHY_MMD_AN, PHY_EEE_LP_ABILITY2);
v = SFR_DATA_U16;
if (machine.n_10g) {
if (v & PHY_EEE_BIT_5G)
print_string(" 5G");
else
print_string(" ");
}
if (v & PHY_EEE_BIT_2G5)
print_string(" 2.5G");
else
print_string(" ");
phy_read(port, PHY_MMD_AN, PHY_EEE_LP_ABILITY);
v = SFR_DATA_U16;
if (v & PHY_EEE_BIT_1G)
print_string(" 1G ");
else
print_string(" ");
if (v & PHY_EEE_BIT_100M)
print_string(" 100M");
else
print_string(" ");
reg_read_m(RTL8373_PHY_EEE_ABLTY);
if (sfr_data[3] & (1 << port))
print_string(" ACTIVE ");
else
print_string(" INACTIVE ");
write_char('\n');
}
void port_eee_enable_all(__xdata uint8_t speed) __banked
{
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
if (i == 3 && machine.n_10g) {
port_eee_enable(i, speed);
} else if (i == 8 && machine.n_10g == 2) {
port_eee_enable(i, speed);
} else {
if (speed & EEE_10G)
port_eee_enable(i, (speed & EEE_NORESET) | EEE_2G5);
else
port_eee_enable(i, speed);
}
}
}
void port_eee_disable_all(void) __banked
{
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
port_eee_disable(i);
}
}
void port_eee_status_all(void) __banked
{
for (uint8_t i = machine.min_port; i <= machine.max_port; i++) {
port_eee_status(i);
}
}
/*
* Enable RLDP, Realtek's version of LLDP
*/
void port_rldp_on(__xdata uint16_t p_ms)
{
REG_WRITE(RTL8373_RLDP_TIMER, p_ms >> 8, p_ms, p_ms >> 8, p_ms);
REG_SET(RTL837X_RMA0_CONF, 0x00000000); // R4ecc
REG_SET(RTL837X_RMA_CONF, 0x00000000); // R4ecc
}
/*
* Reads the member port bitmask of a Link Aggregation Group.
* The groups have numbers 0-3; bit n is set when logical port n is a member.
* The bitmask reflects what the hardware holds, so it covers groups set up
* statically and groups a protocol brought up, without either having to say so.
*/
uint16_t port_lag_members_get(uint8_t lag) __banked
{
reg_read(RTL837X_TRK_MBR_CTRL_BASE + (lag << 2));
return ((uint16_t)SFR_DATA_8 << 8) | SFR_DATA_0;
}
uint8_t port_lag_of(uint8_t port) __banked
{
for (uint8_t lag = 0; lag < 4; lag++)
if ((port_lag_members_get(lag) >> port) & 1)
return lag;
return PORT_LAG_NONE;
}
/*
* Configure LAGs
* Sets the members via port bitmask of a given Link Aggregation Group
* The groups have numbers 0-3
* The bitmask represents up to 10 ports
* If currently no LAG has algorithm used, a default is applied
*/
void port_lag_members_set(__xdata uint8_t lag, __xdata uint16_t members) __banked
{
print_string("port_lag_members_set, lag: "); print_byte(lag); print_string(", members: "); print_short(members);
write_char('\n');
if (lag > 3) {
print_string("Link aggregation group out of range\n");
return;
}
reg_read_m(RTL837X_TRK_HASH_CTRL_BASE + (lag << 2));
if (!(sfr_data[0] | sfr_data[1] | sfr_data[2])
&& (sfr_data[3] == LAG_HASH_RESET || sfr_data[3] == 0))
REG_SET(RTL837X_TRK_HASH_CTRL_BASE + (lag << 2), LAG_HASH_DEFAULT);
REG_WRITE(RTL837X_TRK_MBR_CTRL_BASE + (lag << 2), 0, 0, members >> 8, members & 0xff);
}
/*
* Configures the hash algorithm used for a LAG
* lag is the Group to configure and hash is a bitmask
*/
void port_lag_hash_set(__xdata uint8_t lag, __xdata uint8_t hash_bits) __banked
{
print_string("port_lag_hash_set, lag: "); print_byte(lag); print_string(", hash: "); print_byte(hash_bits);
write_char('\n');
if (lag > 3) {
print_string("Link aggregation group out of range\n");
return;
}
REG_WRITE(RTL837X_TRK_HASH_CTRL_BASE + (lag << 2), 0, 0, 0, hash_bits);
}
void print_port_ingress_filter_mode(vlan_ingress_mode_t mode) __banked
{
switch (mode) {
case VLAN_UNTAGGED:
print_string("Untag.");
break;
case VLAN_TAGGED:
print_string("Tagged");
break;
case VLAN_ALL:
print_string("Any");
break;
default:
print_string("!!err!!");
}
}
void print_vlan_ingress_port(uint8_t log_port) __banked
{
print_phys_port(log_port);write_char('\t');
print_short(port_pvid_get(log_port));write_char('\t');
print_port_ingress_filter_mode(port_ingress_filter_get(log_port));write_char('\t');
port_ingress_vlan_filter_get(log_port) ? print_string("Enabled") : print_string("Disabled");
write_char('\n');
}
/*
* Dumps the VLAN ingress configuration
*/
void vlan_dump(void) __banked
{
print_string("Ingress VLAN configuration:\n");
print_string("Port\tPVID\tType\tFiltering\n");
for (uint8_t port = machine.min_port; port <= machine.max_port; port++) {
print_vlan_ingress_port(port);
}
print_vlan_ingress_port(9);
write_char('\n');
print_string("Type - Which frame types are allowed: untagged, tagged or any\n");
print_string("Filtering - Whether packets not belonging to member VLANs on that port are dropped\n");
print_string("PVID - Assumed VLAN for untagged packets\n");
}
/** Set the ingress VLAN filtering */
bool port_ingress_vlan_filter_set(uint8_t port, __xdata bool enabled) __banked
{
if (port < machine.min_port || (port > machine.max_port && port != CPU_PORT)) {
return false;
}
if (enabled)
reg_bit_set(RTL837X_VLAN_PORT_IGR_FLTR, port);
else
reg_bit_clear(RTL837X_VLAN_PORT_IGR_FLTR, port);
return true;
}
/** Get the ingress VLAN filtering status */
bool port_ingress_vlan_filter_get(uint8_t port) __banked
{
if (port < machine.min_port || (port > machine.max_port && port != CPU_PORT)) {
return false;
}
return reg_bit_test(RTL837X_VLAN_PORT_IGR_FLTR, port);
}
// C1 bit 0: static (no aging); age at C3[4:2] must be non-zero or the
// entry is invisible to lookup
static void l2_mgmt_entry_fill(__xdata uint8_t *mac, __xdata uint16_t vlan)
{
REG_WRITE(RTL837x_TBL_DATA_IN_A, mac[2], mac[3], mac[4], mac[5]);
REG_WRITE(RTL837x_TBL_DATA_IN_B, 0x20 | ((CPU_PORT & 0x3) << 6) | (vlan >> 8),
vlan & 0xff, mac[0], mac[1]);
REG_WRITE(RTL837x_TBL_DATA_IN_C, 0x00, 0x01, 0x00, (7 << 2) | (CPU_PORT >> 2));
}
static void l2_mgmt_tbl_prepare(void)
{
reg_read_m(RTL837x_TBL_DATA_0);
sfr_data[2] &= 0x3f;
sfr_data[1] &= 0xf8;
reg_write_m(RTL837x_TBL_DATA_0);
}
/** Pin the management MAC to the CPU port; remove_entry deletes it */
void port_l2_static_mgmt(__xdata uint8_t *mac, __xdata uint16_t vlan, __xdata bool remove_entry) __banked
{
wait_table_ready();
// vlan 0 = management VLAN disabled: untagged management resolves in
// VLAN 1; clients in other VLANs keep their learned entry and age as before
if (!vlan)
vlan = 1;
l2_mgmt_entry_fill(mac, vlan);
l2_mgmt_tbl_prepare();
if (remove_entry) {
// a search miss returns index 0, not a slot
REG_WRITE(RTL837X_TBL_CTRL, 0x00, 0x00, TBL_L2_UNICAST, TBL_EXECUTE);
wait_table_ready();
reg_read_m(RTL837x_TBL_DATA_0);
if (!(sfr_data[2] & 0x10))
return;
sfr_data[1] |= 0x04; // CLEAR-entry
reg_write_m(RTL837x_TBL_DATA_0);
__xdata uint16_t idx = (((uint16_t)sfr_data[2] & 0x0f) << 8) | sfr_data[3];
REG_WRITE(RTL837X_TBL_CTRL, idx >> 8, idx & 0xff, TBL_L2_UNICAST, TBL_WRITE | TBL_EXECUTE);
} else {
// method-0 write: the hardware hashes the key and places the entry
REG_WRITE(RTL837X_TBL_CTRL, 0x00, 0x00, TBL_L2_UNICAST, TBL_WRITE | TBL_EXECUTE);
}
wait_table_ready();
}