/* A single URB buffer must be large enough to hold a complete jumbo packet
*/ #define TX_SS_URB_SIZE (32 * 1024) #define TX_HS_URB_SIZE (16 * 1024) #define TX_FS_URB_SIZE (10 * 1024)
/* use ethtool to change the level for any given device */ staticint msg_level = -1;
module_param(msg_level, int, 0);
MODULE_PARM_DESC(msg_level, "Override default message level");
staticstruct sk_buff *lan78xx_get_buf(struct sk_buff_head *buf_pool)
{ if (skb_queue_empty(buf_pool)) return NULL;
staticint lan78xx_start_tx_path(struct lan78xx_net *dev)
{ int ret;
netif_dbg(dev, drv, dev->net, "start tx path");
/* Start the MAC transmitter */
ret = lan78xx_start_hw(dev, MAC_TX, MAC_TX_TXEN_); if (ret < 0) return ret;
/* Start the Tx FIFO */
ret = lan78xx_start_hw(dev, FCT_TX_CTL, FCT_TX_CTL_EN_); if (ret < 0) return ret;
return 0;
}
staticint lan78xx_stop_tx_path(struct lan78xx_net *dev)
{ int ret;
netif_dbg(dev, drv, dev->net, "stop tx path");
/* Stop the Tx FIFO */
ret = lan78xx_stop_hw(dev, FCT_TX_CTL, FCT_TX_CTL_EN_, FCT_TX_CTL_DIS_); if (ret < 0) return ret;
/* Stop the MAC transmitter */
ret = lan78xx_stop_hw(dev, MAC_TX, MAC_TX_TXEN_, MAC_TX_TXD_); if (ret < 0) return ret;
return 0;
}
/* The caller must ensure the Tx path is stopped before calling * lan78xx_flush_tx_fifo().
*/ staticint lan78xx_flush_tx_fifo(struct lan78xx_net *dev)
{ return lan78xx_flush_fifo(dev, FCT_TX_CTL, FCT_TX_CTL_RST_);
}
staticint lan78xx_start_rx_path(struct lan78xx_net *dev)
{ int ret;
netif_dbg(dev, drv, dev->net, "start rx path");
/* Start the Rx FIFO */
ret = lan78xx_start_hw(dev, FCT_RX_CTL, FCT_RX_CTL_EN_); if (ret < 0) return ret;
/* Start the MAC receiver*/
ret = lan78xx_start_hw(dev, MAC_RX, MAC_RX_RXEN_); if (ret < 0) return ret;
return 0;
}
staticint lan78xx_stop_rx_path(struct lan78xx_net *dev)
{ int ret;
netif_dbg(dev, drv, dev->net, "stop rx path");
/* Stop the MAC receiver */
ret = lan78xx_stop_hw(dev, MAC_RX, MAC_RX_RXEN_, MAC_RX_RXD_); if (ret < 0) return ret;
/* Stop the Rx FIFO */
ret = lan78xx_stop_hw(dev, FCT_RX_CTL, FCT_RX_CTL_EN_, FCT_RX_CTL_DIS_); if (ret < 0) return ret;
return 0;
}
/* The caller must ensure the Rx path is stopped before calling * lan78xx_flush_rx_fifo().
*/ staticint lan78xx_flush_rx_fifo(struct lan78xx_net *dev)
{ return lan78xx_flush_fifo(dev, FCT_RX_CTL, FCT_RX_CTL_RST_);
}
/* Loop until the read is completed with timeout called with mdiobus_mutex held */ staticint lan78xx_mdiobus_wait_not_busy(struct lan78xx_net *dev)
{ unsignedlong start_time = jiffies;
u32 val; int ret;
do {
ret = lan78xx_read_reg(dev, MII_ACC, &val); if (ret < 0) return ret;
if (!(val & MII_ACC_MII_BUSY_)) return 0;
} while (!time_after(jiffies, start_time + HZ));
return -ETIMEDOUT;
}
staticinline u32 mii_access(int id, int index, int read)
{
u32 ret;
ret = ((u32)id << MII_ACC_PHY_ADDR_SHIFT_) & MII_ACC_PHY_ADDR_MASK_;
ret |= ((u32)index << MII_ACC_MIIRINDA_SHIFT_) & MII_ACC_MIIRINDA_MASK_; if (read)
ret |= MII_ACC_MII_READ_; else
ret |= MII_ACC_MII_WRITE_;
ret |= MII_ACC_MII_BUSY_;
do {
ret = lan78xx_read_reg(dev, E2P_CMD, &val); if (ret < 0) return ret;
if (!(val & E2P_CMD_EPC_BUSY_)) return 0;
usleep_range(40, 100);
} while (!time_after(jiffies, start_time + HZ));
netdev_warn(dev->net, "EEPROM is busy"); return -ETIMEDOUT;
}
staticint lan78xx_read_raw_eeprom(struct lan78xx_net *dev, u32 offset,
u32 length, u8 *data)
{
u32 val, saved; int i, ret;
/* depends on chip, some EEPROM pins are muxed with LED function. * disable & restore LED function to access EEPROM.
*/
ret = lan78xx_read_reg(dev, HW_CFG, &val); if (ret < 0) return ret;
saved = val; if (dev->chipid == ID_REV_CHIP_ID_7800_) {
val &= ~(HW_CFG_LED1_EN_ | HW_CFG_LED0_EN_);
ret = lan78xx_write_reg(dev, HW_CFG, val); if (ret < 0) return ret;
}
ret = lan78xx_eeprom_confirm_not_busy(dev); if (ret == -ETIMEDOUT) goto read_raw_eeprom_done; /* If USB fails, there is nothing to do */ if (ret < 0) return ret;
for (i = 0; i < length; i++) {
val = E2P_CMD_EPC_BUSY_ | E2P_CMD_EPC_CMD_READ_;
val |= (offset & E2P_CMD_EPC_ADDR_MASK_);
ret = lan78xx_write_reg(dev, E2P_CMD, val); if (ret < 0) return ret;
ret = lan78xx_wait_eeprom(dev); /* Looks like not USB specific error, try to recover */ if (ret == -ETIMEDOUT) goto read_raw_eeprom_done; /* If USB fails, there is nothing to do */ if (ret < 0) return ret;
ret = lan78xx_read_reg(dev, E2P_DATA, &val); if (ret < 0) return ret;
data[i] = val & 0xFF;
offset++;
}
read_raw_eeprom_done: if (dev->chipid == ID_REV_CHIP_ID_7800_) { int rc = lan78xx_write_reg(dev, HW_CFG, saved); /* If USB fails, there is nothing to do */ if (rc < 0) return rc;
} return ret;
}
staticint lan78xx_write_raw_eeprom(struct lan78xx_net *dev, u32 offset,
u32 length, u8 *data)
{
u32 val;
u32 saved; int i, ret;
/* depends on chip, some EEPROM pins are muxed with LED function. * disable & restore LED function to access EEPROM.
*/
ret = lan78xx_read_reg(dev, HW_CFG, &val); if (ret < 0) return ret;
saved = val; if (dev->chipid == ID_REV_CHIP_ID_7800_) {
val &= ~(HW_CFG_LED1_EN_ | HW_CFG_LED0_EN_);
ret = lan78xx_write_reg(dev, HW_CFG, val); if (ret < 0) return ret;
}
ret = lan78xx_eeprom_confirm_not_busy(dev); /* Looks like not USB specific error, try to recover */ if (ret == -ETIMEDOUT) goto write_raw_eeprom_done; /* If USB fails, there is nothing to do */ if (ret < 0) return ret;
/* Issue write/erase enable command */
val = E2P_CMD_EPC_BUSY_ | E2P_CMD_EPC_CMD_EWEN_;
ret = lan78xx_write_reg(dev, E2P_CMD, val); if (ret < 0) return ret;
ret = lan78xx_wait_eeprom(dev); /* Looks like not USB specific error, try to recover */ if (ret == -ETIMEDOUT) goto write_raw_eeprom_done; /* If USB fails, there is nothing to do */ if (ret < 0) return ret;
for (i = 0; i < length; i++) { /* Fill data register */
val = data[i];
ret = lan78xx_write_reg(dev, E2P_DATA, val); if (ret < 0) return ret;
/* Send "write" command */
val = E2P_CMD_EPC_BUSY_ | E2P_CMD_EPC_CMD_WRITE_;
val |= (offset & E2P_CMD_EPC_ADDR_MASK_);
ret = lan78xx_write_reg(dev, E2P_CMD, val); if (ret < 0) return ret;
ret = lan78xx_wait_eeprom(dev); /* Looks like not USB specific error, try to recover */ if (ret == -ETIMEDOUT) goto write_raw_eeprom_done; /* If USB fails, there is nothing to do */ if (ret < 0) return ret;
offset++;
}
write_raw_eeprom_done: if (dev->chipid == ID_REV_CHIP_ID_7800_) { int rc = lan78xx_write_reg(dev, HW_CFG, saved); /* If USB fails, there is nothing to do */ if (rc < 0) return rc;
} return ret;
}
/* returns hash bit number for given MAC address */ staticinline u32 lan78xx_hash(char addr[ETH_ALEN])
{ return (ether_crc(ETH_ALEN, addr) >> 23) & 0x1ff;
}
i = 1;
netdev_for_each_mc_addr(ha, netdev) { /* set first 32 into Perfect Filter */ if (i < 33) {
lan78xx_set_addr_filter(pdata, i, ha->addr);
} else {
u32 bitnum = lan78xx_hash(ha->addr);
/* Resetting the device while there is activity on the MDIO * bus can result in the MAC interface locking up and not * completing register access transactions.
*/
ret = lan78xx_mdiobus_wait_not_busy(dev); if (ret < 0) goto exit_unlock;
ret = lan78xx_read_reg(dev, MAC_CR, &val); if (ret < 0) goto exit_unlock;
val |= MAC_CR_RST_;
ret = lan78xx_write_reg(dev, MAC_CR, val); if (ret < 0) goto exit_unlock;
/* Wait for the reset to complete before allowing any further * MAC register accesses otherwise the MAC may lock up.
*/ do {
ret = lan78xx_read_reg(dev, MAC_CR, &val); if (ret < 0) goto exit_unlock;
if (!(val & MAC_CR_RST_)) {
ret = 0; goto exit_unlock;
}
} while (!time_after(jiffies, start_time + HZ));
ret = -ETIMEDOUT;
exit_unlock:
mutex_unlock(&dev->mdiobus_mutex);
return ret;
}
/** * lan78xx_phy_int_ack - Acknowledge PHY interrupt * @dev: pointer to the LAN78xx device structure * * This function acknowledges the PHY interrupt by setting the * INT_STS_PHY_INT_ bit in the interrupt status register (INT_STS). * * Return: 0 on success or a negative error code on failure.
*/ staticint lan78xx_phy_int_ack(struct lan78xx_net *dev)
{ return lan78xx_write_reg(dev, INT_STS, INT_STS_PHY_INT_);
}
/* some work can't be done in tasklets, so we use keventd * * NOTE: annoying asymmetry: if it's active, schedule_work() fails, * but tasklet_schedule() doesn't. hope the failure is rare.
*/ staticvoid lan78xx_defer_kevent(struct lan78xx_net *dev, int work)
{
set_bit(work, &dev->flags); if (!schedule_delayed_work(&dev->wq, 0))
netdev_err(dev->net, "kevent %d may have been dropped\n", work);
}
ret = usb_autopm_get_interface(dev->intf); if (ret) return ret;
/* Invalid EEPROM_INDICATOR at offset zero will result in a failure * to load data from EEPROM
*/ if (ee->magic == LAN78XX_EEPROM_MAGIC)
ret = lan78xx_write_raw_eeprom(dev, ee->offset, ee->len, data); elseif ((ee->magic == LAN78XX_OTP_MAGIC) &&
(ee->offset == 0) &&
(ee->len == 512) &&
(data[0] == OTP_INDICATOR_1))
ret = lan78xx_write_raw_otp(dev, ee->offset, ee->len, data);
ret = lan78xx_write_reg(dev, RX_ADDRL, addr_lo); if (ret < 0) return ret;
ret = lan78xx_write_reg(dev, RX_ADDRH, addr_hi); if (ret < 0) return ret;
}
ret = lan78xx_write_reg(dev, MAF_LO(0), addr_lo); if (ret < 0) return ret;
ret = lan78xx_write_reg(dev, MAF_HI(0), addr_hi | MAF_HI_VALID_); if (ret < 0) return ret;
eth_hw_addr_set(dev->net, addr);
return 0;
}
/* MDIO read and write wrappers for phylib */ staticint lan78xx_mdiobus_read(struct mii_bus *bus, int phy_id, int idx)
{ struct lan78xx_net *dev = bus->priv;
u32 val, addr; int ret;
ret = usb_autopm_get_interface(dev->intf); if (ret < 0) return ret;
mutex_lock(&dev->mdiobus_mutex);
/* confirm MII not busy */
ret = lan78xx_mdiobus_wait_not_busy(dev); if (ret < 0) goto done;
/* set the address, index & direction (read from PHY) */
addr = mii_access(phy_id, idx, MII_READ);
ret = lan78xx_write_reg(dev, MII_ACC, addr); if (ret < 0) goto done;
ret = lan78xx_mdiobus_wait_not_busy(dev); if (ret < 0) goto done;
ret = lan78xx_read_reg(dev, MII_DATA, &val); if (ret < 0) goto done;
staticint lan78xx_mdiobus_write(struct mii_bus *bus, int phy_id, int idx,
u16 regval)
{ struct lan78xx_net *dev = bus->priv;
u32 val, addr; int ret;
ret = usb_autopm_get_interface(dev->intf); if (ret < 0) return ret;
mutex_lock(&dev->mdiobus_mutex);
/* confirm MII not busy */
ret = lan78xx_mdiobus_wait_not_busy(dev); if (ret < 0) goto done;
val = (u32)regval;
ret = lan78xx_write_reg(dev, MII_DATA, val); if (ret < 0) goto done;
/* set the address, index & direction (write to PHY) */
addr = mii_access(phy_id, idx, MII_WRITE);
ret = lan78xx_write_reg(dev, MII_ACC, addr); if (ret < 0) goto done;
ret = lan78xx_mdiobus_wait_not_busy(dev); if (ret < 0) goto done;
/* call register access here because irq_bus_lock & irq_bus_sync_unlock * are only two callbacks executed in non-atomic contex.
*/
ret = lan78xx_read_reg(dev, INT_EP_CTL, &buf); if (ret < 0) goto irq_bus_sync_unlock;
if (buf != data->irqenable)
ret = lan78xx_write_reg(dev, INT_EP_CTL, data->irqenable);
irq_bus_sync_unlock: if (ret < 0)
netdev_err(dev->net, "Failed to sync IRQ enable register: %pe\n",
ERR_PTR(ret));
/* MAC reset will not de-assert TXEN/RXEN, we need to stop them * manually before reset. TX and RX should be disabled before running * link_up sequence.
*/
ret = lan78xx_stop_tx_path(dev); if (ret < 0) goto link_down_fail;
ret = lan78xx_stop_rx_path(dev); if (ret < 0) goto link_down_fail;
/* MAC reset seems to not affect MAC configuration, no idea if it is * really needed, but it was done in previous driver version. So, leave * it here.
*/
ret = lan78xx_mac_reset(dev); if (ret < 0) goto link_down_fail;
return;
link_down_fail:
netdev_err(dev->net, "Failed to set MAC down with error %pe\n",
ERR_PTR(ret));
}
/** * lan78xx_configure_usb - Configure USB link power settings * @dev: pointer to the LAN78xx device structure * @speed: negotiated Ethernet link speed (in Mbps) * * This function configures U1/U2 link power management for SuperSpeed * USB devices based on the current Ethernet link speed. It uses the * USB_CFG1 register to enable or disable U1 and U2 low-power states. * * Note: Only LAN7800 and LAN7801 support SuperSpeed (USB 3.x). * LAN7850 is a High-Speed-only (USB 2.0) device and is skipped. * * Return: 0 on success or a negative error code on failure.
*/ staticint lan78xx_configure_usb(struct lan78xx_net *dev, int speed)
{
u32 mask, val; int ret;
/* Only configure USB settings for SuperSpeed devices */ if (dev->udev->speed != USB_SPEED_SUPER) return 0;
/* LAN7850 does not support USB 3.x */ if (dev->chipid == ID_REV_CHIP_ID_7850_) {
netdev_warn_once(dev->net, "Unexpected SuperSpeed for LAN7850 (USB 2.0 only)\n"); return 0;
}
switch (speed) { case SPEED_1000: /* Disable U2, enable U1 */
ret = lan78xx_update_reg(dev, USB_CFG1,
USB_CFG1_DEV_U2_INIT_EN_, 0); if (ret < 0) return ret;
case SPEED_100: case SPEED_10: /* Enable both U1 and U2 */
mask = USB_CFG1_DEV_U1_INIT_EN_ | USB_CFG1_DEV_U2_INIT_EN_;
val = mask; return lan78xx_update_reg(dev, USB_CFG1, mask, val);
/** * lan78xx_configure_flowcontrol - Set MAC and FIFO flow control configuration * @dev: pointer to the LAN78xx device structure * @tx_pause: enable transmission of pause frames * @rx_pause: enable reception of pause frames * * This function configures the LAN78xx flow control settings by writing * to the FLOW and FCT_FLOW registers. The pause time is set to the * maximum allowed value (65535 quanta). FIFO thresholds are selected * based on USB speed. * * The Pause Time field is measured in units of 512-bit times (quanta): * - At 1 Gbps: 1 quanta = 512 ns → max ~33.6 ms pause * - At 100 Mbps: 1 quanta = 5.12 µs → max ~335 ms pause * - At 10 Mbps: 1 quanta = 51.2 µs → max ~3.3 s pause * * Flow control thresholds (FCT_FLOW) are used to trigger pause/resume: * - RXUSED is the number of bytes used in the RX FIFO * - Flow is turned ON when RXUSED ≥ FLOW_ON threshold * - Flow is turned OFF when RXUSED ≤ FLOW_OFF threshold * - Both thresholds are encoded in units of 512 bytes (rounded up) * * Thresholds differ by USB speed because available USB bandwidth * affects how fast packets can be drained from the RX FIFO: * - USB 3.x (SuperSpeed): * FLOW_ON = 9216 bytes → 18 units * FLOW_OFF = 4096 bytes → 8 units * - USB 2.0 (High-Speed): * FLOW_ON = 8704 bytes → 17 units * FLOW_OFF = 1024 bytes → 2 units * * Note: The FCT_FLOW register must be configured before enabling TX pause * (i.e., before setting FLOW_CR_TX_FCEN_), as required by the hardware. * * Return: 0 on success or a negative error code on failure.
*/ staticint lan78xx_configure_flowcontrol(struct lan78xx_net *dev, bool tx_pause, bool rx_pause)
{ /* Use maximum pause time: 65535 quanta (512-bit times) */ const u32 pause_time_quanta = 65535;
u32 fct_flow = 0;
u32 flow = 0; int ret;
/* Prepare MAC flow control bits */ if (tx_pause)
flow |= FLOW_CR_TX_FCEN_ | pause_time_quanta;
if (rx_pause)
flow |= FLOW_CR_RX_FCEN_;
/* Select RX FIFO thresholds based on USB speed * * FCT_FLOW layout: * bits [6:0] FLOW_ON threshold (RXUSED ≥ ON → assert pause) * bits [14:8] FLOW_OFF threshold (RXUSED ≤ OFF → deassert pause) * thresholds are expressed in units of 512 bytes
*/ switch (dev->udev->speed) { case USB_SPEED_SUPER:
fct_flow = FLOW_CTRL_THRESHOLD(FLOW_ON_SS, FLOW_OFF_SS); break; case USB_SPEED_HIGH:
fct_flow = FLOW_CTRL_THRESHOLD(FLOW_ON_HS, FLOW_OFF_HS); break; default:
netdev_warn(dev->net, "Unsupported USB speed: %d\n",
dev->udev->speed); return -EINVAL;
}
/* Step 1: Write FIFO thresholds before enabling pause frames */
ret = lan78xx_write_reg(dev, FCT_FLOW, fct_flow); if (ret < 0) return ret;
switch (speed) { case SPEED_1000:
mac_cr |= MAC_CR_SPEED_1000_; break; case SPEED_100:
mac_cr |= MAC_CR_SPEED_100_; break; case SPEED_10:
mac_cr |= MAC_CR_SPEED_10_; break; default:
netdev_err(dev->net, "Unsupported speed %d\n", speed); return;
}
if (duplex == DUPLEX_FULL)
mac_cr |= MAC_CR_FULL_DUPLEX_;
/* make sure TXEN and RXEN are disabled before reconfiguring MAC */
ret = lan78xx_update_reg(dev, MAC_CR, MAC_CR_SPEED_MASK_ |
MAC_CR_FULL_DUPLEX_ | MAC_CR_EEE_EN_, mac_cr); if (ret < 0) goto link_up_fail;
ret = lan78xx_configure_flowcontrol(dev, tx_pause, rx_pause); if (ret < 0) goto link_up_fail;
ret = lan78xx_configure_usb(dev, speed); if (ret < 0) goto link_up_fail;
lan78xx_rx_urb_submit_all(dev);
ret = lan78xx_flush_rx_fifo(dev); if (ret < 0) goto link_up_fail;
ret = lan78xx_flush_tx_fifo(dev); if (ret < 0) goto link_up_fail;
ret = lan78xx_start_tx_path(dev); if (ret < 0) goto link_up_fail;
ret = lan78xx_start_rx_path(dev); if (ret < 0) goto link_up_fail;
netif_start_queue(net);
return;
link_up_fail:
netdev_err(dev->net, "Failed to set MAC up with error %pe\n",
ERR_PTR(ret));
}
/** * lan78xx_mac_eee_enable - Enable or disable MAC-side EEE support * @dev: LAN78xx device * @enable: true to enable EEE, false to disable * * This function sets or clears the MAC_CR_EEE_EN_ bit to control Energy * Efficient Ethernet (EEE) operation. According to current understanding * of the LAN7800 documentation, this bit can be modified while TX and RX * are enabled. No explicit requirement was found to disable data paths * before changing this bit. * * Return: 0 on success or a negative error code
*/ staticint lan78xx_mac_eee_enable(struct lan78xx_net *dev, bool enable)
{
u32 mac_cr = 0;
/* Software should only change this field when Energy Efficient * Ethernet Enable (EEEEN) is cleared. We ensure that by clearing * EEEEN during probe, and phylink itself guarantees that * mac_disable_tx_lpi() will have been previously called.
*/
ret = lan78xx_write_reg(dev, EEE_TX_LPI_REQ_DLY, timer); if (ret < 0) return ret;
/** * lan78xx_set_fixed_link() - Set fixed link configuration for LAN7801 * @dev: LAN78xx device * * Use fixed link configuration with 1 Gbps full duplex. This is used in special * cases like EVB-KSZ9897-1, where LAN7801 acts as a USB-to-Ethernet interface * to a switch without a visible PHY. * * Return: pointer to the registered fixed PHY, or ERR_PTR() on error.
*/ staticint lan78xx_set_fixed_link(struct lan78xx_net *dev)
{ staticconststruct phylink_link_state state = {
.speed = SPEED_1000,
.duplex = DUPLEX_FULL,
};
netdev_info(dev->net, "No PHY found on LAN7801 – using fixed link instead (e.g. EVB-KSZ9897-1)\n");
/** * lan78xx_get_phy() - Probe or register PHY device and set interface mode * @dev: LAN78xx device structure * * This function attempts to find a PHY on the MDIO bus. If no PHY is found * and the chip is LAN7801, it registers a fixed PHY as fallback. It also * sets dev->interface based on chip ID and detected PHY type. * * Return: a valid PHY device pointer, or ERR_PTR() on failure.
*/ staticstruct phy_device *lan78xx_get_phy(struct lan78xx_net *dev)
{ struct phy_device *phydev;
/* Attempt to locate a PHY on the MDIO bus */
phydev = phy_find_first(dev->mdiobus);
switch (dev->chipid) { case ID_REV_CHIP_ID_7801_: if (phydev) { /* External RGMII PHY detected */
dev->interface = PHY_INTERFACE_MODE_RGMII_ID;
phydev->is_internal = false;
if (!phydev->drv)
netdev_warn(dev->net, "PHY driver not found – assuming RGMII delays are on PCB or strapped for the PHY\n");
return phydev;
}
dev->interface = PHY_INTERFACE_MODE_RGMII; /* No PHY found – fallback to fixed PHY (e.g. KSZ switch board) */ return NULL;
case ID_REV_CHIP_ID_7800_: case ID_REV_CHIP_ID_7850_: if (!phydev) return ERR_PTR(-ENODEV);
/* These use internal GMII-connected PHY */
dev->interface = PHY_INTERFACE_MODE_GMII;
phydev->is_internal = true; return phydev;
/** * lan78xx_mac_prepare_for_phy() - Preconfigure MAC-side interface settings * @dev: LAN78xx device * * Configure MAC-side registers according to dev->interface, which should be * set by lan78xx_get_phy(). * * - For PHY_INTERFACE_MODE_RGMII: * Enable MAC-side TXC delay. This mode seems to be used in a special setup * without a real PHY, likely on EVB-KSZ9897-1. In that design, LAN7801 is * connected to the KSZ9897 switch, and the link timing is expected to be * hardwired (e.g. via strapping or board layout). No devicetree support is * assumed here. * * - For PHY_INTERFACE_MODE_RGMII_ID: * Disable MAC-side delay and rely on the PHY driver to provide delay. * * - For GMII, no MAC-specific config is needed. * * Return: 0 on success or a negative error code.
*/ staticint lan78xx_mac_prepare_for_phy(struct lan78xx_net *dev)
{ int ret;
switch (dev->interface) { case PHY_INTERFACE_MODE_RGMII: /* Enable MAC-side TX clock delay */
ret = lan78xx_write_reg(dev, MAC_RGMII_ID,
MAC_RGMII_ID_TXC_DELAY_EN_); if (ret < 0) return ret;
ret = lan78xx_write_reg(dev, RGMII_TX_BYP_DLL, 0x3D00); if (ret < 0) return ret;
ret = lan78xx_update_reg(dev, HW_CFG,
HW_CFG_CLK125_EN_ | HW_CFG_REFCLK25_EN_,
HW_CFG_CLK125_EN_ | HW_CFG_REFCLK25_EN_); if (ret < 0) return ret;
break;
case PHY_INTERFACE_MODE_RGMII_ID: /* Disable MAC-side TXC delay, PHY provides it */
ret = lan78xx_write_reg(dev, MAC_RGMII_ID, 0); if (ret < 0) return ret;
break;
case PHY_INTERFACE_MODE_GMII: /* No MAC-specific configuration required */ break;
/** * lan78xx_configure_leds_from_dt() - Configure LED enables based on DT * @dev: LAN78xx device * @phydev: PHY device (must be valid) * * Reads "microchip,led-modes" property from the PHY's DT node and enables * the corresponding number of LEDs by writing to HW_CFG. * * This helper preserves the original logic, enabling up to 4 LEDs. * If the property is not present, this function does nothing. * * Return: 0 on success or a negative error code.
*/ staticint lan78xx_configure_leds_from_dt(struct lan78xx_net *dev, struct phy_device *phydev)
{ struct device_node *np = phydev->mdio.dev.of_node;
u32 reg; int len, ret;
if (!np) return 0;
len = of_property_count_elems_of_size(np, "microchip,led-modes", sizeof(u32)); if (len < 0) return 0;
ret = lan78xx_read_reg(dev, HW_CFG, ®); if (ret < 0) return ret;
pc->dev = &dev->net->dev;
pc->type = PHYLINK_NETDEV;
pc->mac_capabilities = MAC_SYM_PAUSE | MAC_ASYM_PAUSE | MAC_10 |
MAC_100 | MAC_1000FD;
pc->mac_managed_pm = true;
pc->lpi_capabilities = MAC_100FD | MAC_1000FD; /* * Default TX LPI (Low Power Idle) request delay count is set to 50us. * * Source: LAN7800 Documentation, DS00001992H, Section 15.1.57, Page 204. * * Reasoning: * According to the application note in the LAN7800 documentation, a * zero delay may negatively impact the TX data path’s ability to * support Gigabit operation. A value of 50us is recommended as a * reasonable default when the part operates at Gigabit speeds, * balancing stability and power efficiency in EEE mode. This delay can * be increased based on performance testing, as EEE is designed for * scenarios with mostly idle links and occasional bursts of full * bandwidth transmission. The goal is to ensure reliable Gigabit * performance without overly aggressive power optimization during * inactive periods.
*/
pc->lpi_timer_default = 50;
pc->eee_enabled_default = true;
if (dev->chipid == ID_REV_CHIP_ID_7801_)
phy_interface_set_rgmii(pc->supported_interfaces); else
__set_bit(PHY_INTERFACE_MODE_GMII, pc->supported_interfaces);
staticint lan78xx_phy_init(struct lan78xx_net *dev)
{ struct phy_device *phydev; int ret;
phydev = lan78xx_get_phy(dev); /* phydev can be NULL if no PHY is found and the chip is LAN7801, * which will use a fixed link later. * If an error occurs, return the error code immediately.
*/ if (IS_ERR(phydev)) return PTR_ERR(phydev);
ret = lan78xx_phylink_setup(dev); if (ret < 0) return ret;
ret = lan78xx_mac_prepare_for_phy(dev); if (ret < 0) goto phylink_uninit;
/* If no PHY is found, set up a fixed link. It is very specific to * the LAN7801 and is used in special cases like EVB-KSZ9897-1 where * LAN7801 acts as a USB-to-Ethernet interface to a switch without * a visible PHY.
*/ if (!phydev) {
ret = lan78xx_set_fixed_link(dev); if (ret < 0) goto phylink_uninit;
/* No PHY found, so set up a fixed link and return early. * No need to configure PHY IRQ or attach to phylink.
*/ return 0;
}
/* if phyirq is not set, use polling mode in phylib */ if (dev->domain_data.phyirq > 0)
phydev->irq = dev->domain_data.phyirq; else
phydev->irq = PHY_POLL;
netdev_dbg(dev->net, "phydev->irq = %d\n", phydev->irq);
ret = phylink_connect_phy(dev->phylink, phydev); if (ret) {
netdev_err(dev->net, "can't attach PHY to %s, error %pe\n",
dev->mdiobus->id, ERR_PTR(ret)); goto phylink_uninit;
}
ret = lan78xx_configure_leds_from_dt(dev, phydev); if (ret < 0) goto phylink_uninit;
return 0;
phylink_uninit:
lan78xx_phy_uninit(dev);
return ret;
}
staticint lan78xx_set_rx_max_frame_length(struct lan78xx_net *dev, int size)
{ bool rxenabled;
u32 buf; int ret;
ret = lan78xx_read_reg(dev, MAC_RX, &buf); if (ret < 0) return ret;
rxenabled = ((buf & MAC_RX_RXEN_) != 0);
if (rxenabled) {
buf &= ~MAC_RX_RXEN_;
ret = lan78xx_write_reg(dev, MAC_RX, buf); if (ret < 0) return ret;
}
/* add 4 to size for FCS */
buf &= ~MAC_RX_MAX_SIZE_MASK_;
buf |= (((size + 4) << MAC_RX_MAX_SIZE_SHIFT_) & MAC_RX_MAX_SIZE_MASK_);
ret = lan78xx_write_reg(dev, MAC_RX, buf); if (ret < 0) return ret;
if (rxenabled) {
buf |= MAC_RX_RXEN_;
ret = lan78xx_write_reg(dev, MAC_RX, buf); if (ret < 0) return ret;
}
/* Get reference count of the URB to avoid it to be * freed during usb_unlink_urb, which may trigger * use-after-free problem inside usb_unlink_urb since * usb_unlink_urb is always racing with .complete * handler(include defer_bh).
*/
usb_get_urb(urb);
spin_unlock_irqrestore(&q->lock, flags); /* during some PM-driven resume scenarios, * these (async) unlinks complete immediately
*/
ret = usb_unlink_urb(urb); if (ret != -EINPROGRESS && ret != 0)
netdev_dbg(dev->net, "unlink urb err, %d\n", ret); else
count++;
usb_put_urb(urb);
spin_lock_irqsave(&q->lock, flags);
}
spin_unlock_irqrestore(&q->lock, flags); return count;
}
staticint lan78xx_change_mtu(struct net_device *netdev, int new_mtu)
{ struct lan78xx_net *dev = netdev_priv(netdev); int max_frame_len = RX_MAX_FRAME_LEN(new_mtu); int ret;
/* no second zero-length packet read wanted after mtu-sized packets */ if ((max_frame_len % dev->maxpacket) == 0) return -EDOM;
ret = usb_autopm_get_interface(dev->intf); if (ret < 0) return ret;
ret = lan78xx_set_rx_max_frame_length(dev, max_frame_len); if (ret < 0)
netdev_err(dev->net, "MTU changed to %d from %d failed with %pe\n",
new_mtu, netdev->mtu, ERR_PTR(ret)); else
WRITE_ONCE(netdev->mtu, new_mtu);
ret = lan78xx_read_reg(dev, HW_CFG, &buf); if (ret < 0) return ret;
buf |= HW_CFG_LRST_;
ret = lan78xx_write_reg(dev, HW_CFG, buf); if (ret < 0) return ret;
timeout = jiffies + HZ; do {
mdelay(1);
ret = lan78xx_read_reg(dev, HW_CFG, &buf); if (ret < 0) return ret;
if (time_after(jiffies, timeout)) {
netdev_warn(dev->net, "timeout on completion of LiteReset");
ret = -ETIMEDOUT; return ret;
}
} while (buf & HW_CFG_LRST_);
/* save DEVID for later usage */
ret = lan78xx_read_reg(dev, ID_REV, &buf); if (ret < 0) return ret;
/* LAN7801 only has RGMII mode */ if (dev->chipid == ID_REV_CHIP_ID_7801_)
buf &= ~MAC_CR_GMII_EN_;
ret = lan78xx_write_reg(dev, MAC_CR, buf); if (ret < 0) return ret;
ret = lan78xx_set_rx_max_frame_length(dev,
RX_MAX_FRAME_LEN(dev->net->mtu));
return ret;
}
staticvoid lan78xx_init_stats(struct lan78xx_net *dev)
{
u32 *p; int i;
/* initialize for stats update * some counters are 20bits and some are 32bits
*/
p = (u32 *)&dev->stats.rollover_max; for (i = 0; i < (sizeof(dev->stats.rollover_max) / (sizeof(u32))); i++)
p[i] = 0xFFFFF;
ret = usb_autopm_get_interface(dev->intf); if (ret < 0) return ret;
mutex_lock(&dev->dev_mutex);
lan78xx_init_stats(dev);
napi_enable(&dev->napi);
set_bit(EVENT_DEV_OPEN, &dev->flags);
/* for Link Check */ if (dev->urb_intr) {
ret = usb_submit_urb(dev->urb_intr, GFP_KERNEL); if (ret < 0) {
netif_err(dev, ifup, dev->net, "intr submit %d\n", ret); goto done;
}
}
phylink_start(dev->phylink);
done:
mutex_unlock(&dev->dev_mutex);
if (ret < 0)
usb_autopm_put_interface(dev->intf);
return ret;
}
staticvoid lan78xx_terminate_urbs(struct lan78xx_net *dev)
{
DECLARE_WAIT_QUEUE_HEAD_ONSTACK(unlink_wakeup);
DECLARE_WAITQUEUE(wait, current); int temp;
/* ensure there are no more active urbs */
add_wait_queue(&unlink_wakeup, &wait);
set_current_state(TASK_UNINTERRUPTIBLE);
dev->wait = &unlink_wakeup;
temp = unlink_urbs(dev, &dev->txq) + unlink_urbs(dev, &dev->rxq);
/* maybe wait for deletions to finish. */ while (!skb_queue_empty(&dev->rxq) ||
!skb_queue_empty(&dev->txq)) {
schedule_timeout(msecs_to_jiffies(UNLINK_TIMEOUT_MS));
set_current_state(TASK_UNINTERRUPTIBLE);
netif_dbg(dev, ifdown, dev->net, "waited for %d urb completions", temp);
}
set_current_state(TASK_RUNNING);
dev->wait = NULL;
remove_wait_queue(&unlink_wakeup, &wait);
/* empty Rx done, Rx overflow and Tx pend queues
*/ while (!skb_queue_empty(&dev->rxq_done)) { struct sk_buff *skb = skb_dequeue(&dev->rxq_done);
/* deferred work (task, timer, softirq) must also stop. * can't flush_scheduled_work() until we drop rtnl (later), * else workers could deadlock; so make workers a NOP.
*/
clear_bit(EVENT_TX_HALT, &dev->flags);
clear_bit(EVENT_RX_HALT, &dev->flags);
clear_bit(EVENT_PHY_INT_ACK, &dev->flags);
clear_bit(EVENT_STAT_UPDATE, &dev->flags);
switch (urb->status) { case -EPIPE:
lan78xx_defer_kevent(dev, EVENT_TX_HALT); break;
/* software-driven interface shutdown */ case -ECONNRESET: case -ESHUTDOWN:
netif_dbg(dev, tx_err, dev->net, "tx err interface gone %d\n",
entry->urb->status); break;
case -EPROTO: case -ETIME: case -EILSEQ:
netif_stop_queue(dev->net);
netif_dbg(dev, tx_err, dev->net, "tx err queue stopped %d\n",
entry->urb->status); break; default:
netif_dbg(dev, tx_err, dev->net, "unknown tx err %d\n",
entry->urb->status); break;
}
}
usb_autopm_put_interface_async(dev->intf);
skb_unlink(skb, &dev->txq);
lan78xx_release_tx_buf(dev, skb);
/* Re-schedule NAPI if Tx data pending but no URBs in progress.
*/ if (skb_queue_empty(&dev->txq) &&
!skb_queue_empty(&dev->txq_pend))
napi_schedule(&dev->napi);
}
if (skb_queue_empty(&dev->txq))
napi_schedule(&dev->napi);
/* Stop stack Tx queue if we have enough data to fill * all the free Tx URBs.
*/ if (tx_pend_data_len > lan78xx_tx_urb_space(dev)) {
netif_stop_queue(net);
netif_dbg(dev, hw, dev->net, "tx data len: %u, urb space %u",
tx_pend_data_len, lan78xx_tx_urb_space(dev));
/* Kick off transmission of pending data */
if (!skb_queue_empty(&dev->txq_free))
napi_schedule(&dev->napi);
}
return NETDEV_TX_OK;
}
staticint lan78xx_bind(struct lan78xx_net *dev, struct usb_interface *intf)
{ struct lan78xx_priv *pdata = NULL; int ret; int i;
netif_dbg(dev, rx_status, dev->net, "< rx, len %zu, type 0x%x\n",
skb->len + sizeof(struct ethhdr), skb->protocol);
memset(skb->cb, 0, sizeof(struct skb_data));
if (skb_defer_rx_timestamp(skb)) return;
napi_gro_receive(&dev->napi, skb);
}
staticint lan78xx_rx(struct lan78xx_net *dev, struct sk_buff *skb, int budget, int *work_done)
{ if (skb->len < RX_SKB_MIN_LEN) return 0;
/* Extract frames from the URB buffer and pass each one to * the stack in a new NAPI SKB.
*/ while (skb->len > 0) {
u32 rx_cmd_a, rx_cmd_b, align_count, size;
u16 rx_cmd_c; unsignedchar *packet;
/* Processing of the URB buffer must complete once * it has started. If the NAPI work budget is exhausted * while frames remain they are added to the overflow * queue for delivery in the next NAPI polling cycle.
*/ if (*work_done < budget) {
lan78xx_skb_return(dev, skb2);
++(*work_done);
} else {
skb_queue_tail(&dev->rxq_overflow, skb2);
}
}
skb_pull(skb, size);
/* skip padding bytes before the next frame starts */ if (skb->len)
skb_pull(skb, align_count);
}
return 1;
}
staticinlinevoid rx_process(struct lan78xx_net *dev, struct sk_buff *skb, int budget, int *work_done)
{ if (!lan78xx_rx(dev, skb, budget, work_done)) {
netif_dbg(dev, rx_err, dev->net, "drop\n");
dev->net->stats.rx_errors++;
}
}
/* Ensure the maximum number of Rx URBs is submitted
*/ while ((rx_buf = lan78xx_get_rx_buf(dev)) != NULL) { if (rx_submit(dev, rx_buf, GFP_ATOMIC) != 0) break;
}
}
/* Work through the pending SKBs and copy the data of each SKB into * the URB buffer if there room for all the SKB data. * * There must be at least DST+SRC+TYPE in the SKB (with padding enabled)
*/ while (remain >= TX_SKB_MIN_LEN) { unsignedint pending_bytes; unsignedint align_bytes; struct sk_buff *skb; unsignedint len;
staticvoid lan78xx_tx_bh(struct lan78xx_net *dev)
{ int ret;
/* Start the stack Tx queue if it was stopped
*/
netif_tx_lock(dev->net); if (netif_queue_stopped(dev->net)) { if (lan78xx_tx_pend_data_len(dev) < lan78xx_tx_urb_space(dev))
netif_wake_queue(dev->net);
}
netif_tx_unlock(dev->net);
/* Go through the Tx pending queue and set up URBs to transfer * the data to the device. Stop if no more pending data or URBs, * or if an error occurs when a URB is submitted.
*/ do { struct skb_data *entry; struct sk_buff *tx_buf; unsignedlong flags;
if (skb_queue_empty(&dev->txq_pend)) break;
tx_buf = lan78xx_get_tx_buf(dev); if (!tx_buf) break;
entry = lan78xx_tx_buf_fill(dev, tx_buf);
spin_lock_irqsave(&dev->txq.lock, flags);
ret = usb_autopm_get_interface_async(dev->intf); if (ret < 0) {
spin_unlock_irqrestore(&dev->txq.lock, flags); goto out;
}
/* Pass frames received in the last NAPI cycle before * working on newly completed URBs.
*/ while (!skb_queue_empty(&dev->rxq_overflow)) {
lan78xx_skb_return(dev, skb_dequeue(&dev->rxq_overflow));
++work_done;
}
/* Take a snapshot of the done queue and move items to a * temporary queue. Rx URB completions will continue to add * to the done queue.
*/
__skb_queue_head_init(&done);
/* Extract receive frames from completed URBs and * pass them to the stack. Re-submit each completed URB.
*/ while ((work_done < budget) &&
(rx_buf = __skb_dequeue(&done))) {
entry = (struct skb_data *)(rx_buf->cb); switch (entry->state) { case rx_done:
rx_process(dev, rx_buf, budget, &work_done); break; case rx_cleanup: break; default:
netdev_dbg(dev->net, "rx buf state %d\n",
entry->state); break;
}
lan78xx_rx_urb_resubmit(dev, rx_buf);
}
/* If budget was consumed before processing all the URBs put them * back on the front of the done queue. They will be first to be * processed in the next NAPI cycle.
*/
spin_lock_irqsave(&dev->rxq_done.lock, flags);
skb_queue_splice(&done, &dev->rxq_done);
spin_unlock_irqrestore(&dev->rxq_done.lock, flags);
if (!test_bit(EVENT_RX_HALT, &dev->flags))
lan78xx_rx_urb_submit_all(dev);
/* Submit new Tx URBs */
lan78xx_tx_bh(dev);
}
return work_done;
}
staticint lan78xx_poll(struct napi_struct *napi, int budget)
{ struct lan78xx_net *dev = container_of(napi, struct lan78xx_net, napi); int result = budget; int work_done;
/* Don't do any work if the device is suspended */
if (test_bit(EVENT_DEV_ASLEEP, &dev->flags)) {
napi_complete_done(napi, 0); return 0;
}
/* Process completed URBs and submit new URBs */
work_done = lan78xx_bh(dev, budget);
if (work_done < budget) {
napi_complete_done(napi, work_done);
/* Start a new polling cycle if data was received or * data is waiting to be transmitted.
*/ if (!skb_queue_empty(&dev->rxq_done)) {
napi_schedule(napi);
} elseif (netif_carrier_ok(dev->net)) { if (skb_queue_empty(&dev->txq) &&
!skb_queue_empty(&dev->txq_pend)) {
napi_schedule(napi);
} else {
netif_tx_lock(dev->net); if (netif_queue_stopped(dev->net)) {
netif_wake_queue(dev->net);
napi_schedule(napi);
}
netif_tx_unlock(dev->net);
}
}
result = work_done;
}
return result;
}
staticvoid lan78xx_delayedwork(struct work_struct *work)
{ int status; struct lan78xx_net *dev;
dev = container_of(work, struct lan78xx_net, wq.work);
if (test_bit(EVENT_DEV_DISCONNECT, &dev->flags)) return;
if (usb_autopm_get_interface(dev->intf) < 0) return;
if (test_bit(EVENT_TX_HALT, &dev->flags)) {
unlink_urbs(dev, &dev->txq);
status = usb_clear_halt(dev->udev, dev->pipe_out); if (status < 0 &&
status != -EPIPE &&
status != -ESHUTDOWN) { if (netif_msg_tx_err(dev))
netdev_err(dev->net, "can't clear tx halt, status %d\n",
status);
} else {
clear_bit(EVENT_TX_HALT, &dev->flags); if (status != -ESHUTDOWN)
netif_wake_queue(dev->net);
}
}
if (test_bit(EVENT_RX_HALT, &dev->flags)) {
unlink_urbs(dev, &dev->rxq);
status = usb_clear_halt(dev->udev, dev->pipe_in); if (status < 0 &&
status != -EPIPE &&
status != -ESHUTDOWN) { if (netif_msg_rx_err(dev))
netdev_err(dev->net, "can't clear rx halt, status %d\n",
status);
} else {
clear_bit(EVENT_RX_HALT, &dev->flags);
napi_schedule(&dev->napi);
}
}
if (test_bit(EVENT_PHY_INT_ACK, &dev->flags)) { int ret = 0;
clear_bit(EVENT_PHY_INT_ACK, &dev->flags);
ret = lan78xx_phy_int_ack(dev); if (ret)
netdev_info(dev->net, "PHY INT ack failed (%pe)\n",
ERR_PTR(ret));
}
if (test_bit(EVENT_STAT_UPDATE, &dev->flags)) {
lan78xx_update_stats(dev);
/* NOTE: not throttling like RX/TX, since this endpoint * already polls infrequently
*/ default:
netdev_dbg(dev->net, "intr status %d\n", status); break;
}
if (!netif_device_present(dev->net) ||
!netif_running(dev->net)) {
netdev_warn(dev->net, "not submitting new status URB"); return;
}
memset(urb->transfer_buffer, 0, urb->transfer_buffer_length);
status = usb_submit_urb(urb, GFP_ATOMIC);
ret = lan78xx_phy_init(dev); if (ret < 0) goto free_urbs;
ret = register_netdev(netdev); if (ret != 0) {
netif_err(dev, probe, netdev, "couldn't register the device\n"); goto phy_uninit;
}
usb_set_intfdata(intf, dev);
ret = device_set_wakeup_enable(&udev->dev, true);
/* Default delay of 2sec has more overhead than advantage. * Set to 10sec as default.
*/
pm_runtime_set_autosuspend_delay(&udev->dev,
DEFAULT_AUTOSUSPEND_DELAY);
ret = lan78xx_write_reg(dev, WUCSR, temp_wucsr); if (ret < 0) return ret;
/* when multiple WOL bits are set */ if (hweight_long((unsignedlong)wol) > 1) {
temp_pmt_ctl |= PMT_CTL_WOL_EN_;
temp_pmt_ctl &= ~PMT_CTL_SUS_MODE_MASK_;
temp_pmt_ctl |= PMT_CTL_SUS_MODE_0_;
}
ret = lan78xx_write_reg(dev, PMT_CTL, temp_pmt_ctl); if (ret < 0) return ret;
/* clear WUPS */
ret = lan78xx_read_reg(dev, PMT_CTL, &buf); if (ret < 0) return ret;
buf |= PMT_CTL_WUPS_MASK_;
ret = lan78xx_write_reg(dev, PMT_CTL, buf); if (ret < 0) return ret;
/* stop RX */
ret = lan78xx_stop_rx_path(dev); if (ret < 0) goto out;
ret = lan78xx_flush_rx_fifo(dev); if (ret < 0) goto out;
/* stop Tx */
ret = lan78xx_stop_tx_path(dev); if (ret < 0) goto out;
/* empty out the Rx and Tx queues */
netif_device_detach(dev->net);
lan78xx_terminate_urbs(dev);
usb_kill_urb(dev->urb_intr);
/* reattach */
netif_device_attach(dev->net);
timer_delete(&dev->stat_monitor);
if (PMSG_IS_AUTO(message)) {
ret = lan78xx_set_auto_suspend(dev); if (ret < 0) goto out;
} else { struct lan78xx_priv *pdata;
pdata = (struct lan78xx_priv *)(dev->data[0]);
netif_carrier_off(dev->net);
ret = lan78xx_set_suspend(dev, pdata->wol); if (ret < 0) goto out;
}
} else { /* Interface is down; don't allow WOL and PHY * events to wake up the host
*/
u32 buf;
set_bit(EVENT_DEV_ASLEEP, &dev->flags);
ret = lan78xx_write_reg(dev, WUCSR, 0); if (ret < 0) goto out;
ret = lan78xx_write_reg(dev, WUCSR2, 0); if (ret < 0) goto out;
ret = lan78xx_read_reg(dev, PMT_CTL, &buf); if (ret < 0) goto out;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.