ethernet: stmmac: dwmac-rk: add system serial number based MAC address generation

Add support for generating deterministic MAC addresses using the system serial
number as a seed. When the Kconfig option DWMAC_ROCKCHIP_SERIAL_MAC is enabled,
the driver will attempt to derive a MAC address from the system serial number.

To ensure robustness, the implementation includes multiple fallback mechanisms:
1. Check for pre-configured valid MAC address
2. Read from vendor storage (historical fallback)
3. Generate random locally administered address with interface ID encoding

The generated MAC addresses follow the locally administered address format
(02:xx:xx:xx:xx:xx) and are stored in vendor storage when possible to ensure
persistence across reboots.

This feature is particularly useful for devices without pre-programmed MAC
addresses in OTP, providing stable network identities.

Signed-off-by: meilimao <meilimao@meilimao.org>
This commit is contained in:
2025-12-15 23:47:52 +08:00
parent a36ef91ed6
commit 2ec3569f3f
3 changed files with 190 additions and 36 deletions

View File

@@ -168,11 +168,21 @@ config DWMAC_ROCKCHIP
depends on OF && (ARCH_ROCKCHIP || COMPILE_TEST)
select MFD_SYSCON
help
Support for Ethernet controller on Rockchip RK3288 SoC.
Support for Ethernet controller on Rockchip SoC.
This selects the Rockchip RK3288 SoC glue layer support for
This selects the Rockchip SoC glue layer support for
the stmmac device driver.
config DWMAC_ROCKCHIP_SERIAL_MAC
bool "Generate static ethernet mac address from system serial number"
depends on DWMAC_ROCKCHIP
default n
help
Generate consistent MAC address from system serial number.
This selects uses the system serial number to generate a
fixed Ethernet Mac address instead of a random one.
config DWMAC_ROCKCHIP_TOOL
bool "Rockchip dwmac tool support"
depends on DWMAC_ROCKCHIP

View File

@@ -47,3 +47,5 @@ obj-$(CONFIG_STMMAC_PCI) += stmmac-pci.o
obj-$(CONFIG_DWMAC_INTEL) += dwmac-intel.o
obj-$(CONFIG_DWMAC_LOONGSON) += dwmac-loongson.o
stmmac-pci-objs:= stmmac_pci.o
ccflags-$(CONFIG_DWMAC_ROCKCHIP_SERIAL_MAC) += -DDWMAC_ROCKCHIP_SERIAL_MAC

View File

@@ -1,6 +1,6 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/**
* DOC: dwmac-rk.c - Rockchip RK3288 DWMAC specific glue layer
* DOC: dwmac-rk.c - Rockchip DWMAC specific glue layer
*
* Copyright (C) 2014 Chen-Zhi (Roger Chen)
*
@@ -28,6 +28,9 @@
#include <soc/rockchip/rockchip_csu.h>
#include "stmmac_platform.h"
#include "dwmac-rk-tool.h"
#ifdef DWMAC_ROCKCHIP_SERIAL_MAC
#include <asm/system_info.h>
#endif
#define MAX_ETH 2
@@ -3254,45 +3257,184 @@ int dwmac_rk_get_phy_interface(struct stmmac_priv *priv)
}
EXPORT_SYMBOL(dwmac_rk_get_phy_interface);
/**
* rk_generate_fallback_mac - Generate a fallback MAC address
* @addr: Buffer to store the MAC address (6 bytes)
* @iface_id: Interface identifier
*
* Generate a random MAC address with a local-admin OUI as fallback.
* Always returns a valid MAC address.
*/
static void rk_generate_fallback_mac(u8 *addr, int iface_id)
{
/* Generate a random MAC address */
eth_random_addr(addr);
/* Ensure the address is valid */
if (!is_valid_ether_addr(addr)) {
eth_zero_addr(addr);
/* Set a fallback locally administered address */
addr[0] = 0x02; /* Locally administered bit is set */
addr[1] = 0x00;
addr[2] = 0xa4;
addr[3] = 0xff;
addr[4] = iface_id & 0xff;
addr[5] = 0xfe;
}
}
#ifdef DWMAC_ROCKCHIP_SERIAL_MAC
/*
* rk_get_mac_from_serial - Generate MAC address from system serial number
* @addr: Buffer to store the MAC address (6 bytes)
* @iface_id: Interface identifier
*
* Return: 0 on success, negative error code on failure
*/
static int rk_get_mac_from_serial(u8 *addr, int iface_id)
{
unsigned long long serial;
/* Check the validity of the serial number */
if (system_serial_low == 0 && system_serial_high == 0)
return -ENODEV;
/* Combine the 64-bit serial number */
serial = ((unsigned long long)system_serial_high << 32) | system_serial_low;
/* Validate the serial number (optional but recommended) */
if (serial == 0 || serial == 0xFFFFFFFFFFFFFFFFULL)
return -EINVAL;
/* Generate OUI: 02:00:a4 (locally administered address) */
addr[0] = 0x02; /* Locally administered bit is set */
addr[1] = 0x00;
addr[2] = 0xa4;
/* Use parts of the serial number */
addr[3] = 0x10 + ((serial >> 40) & 0xff);
addr[4] = (serial >> 32) & 0xff;
addr[5] = ((serial >> 24) & 0xff) + iface_id;
return 0;
}
#endif /* DWMAC_ROCKCHIP_SERIAL_MAC */
/*
* rk_set_mac_address - Set up MAC address for an interface
* @addr: Buffer to store the MAC address (6 bytes)
* @iface_id: Interface identifier
*
* This function attempts to generate a deterministic MAC address from the
* system serial number if DWMAC_ROCKCHIP_SERIAL_MAC is enabled. If that fails
* or the feature is disabled, it falls back to a random MAC address.
*
* Return: Always returns 0 (success), as we guarantee a valid address.
*/
static int rk_set_mac_address(u8 *addr, int iface_id)
{
#ifdef DWMAC_ROCKCHIP_SERIAL_MAC
int ret;
/* Attempt to generate MAC from serial number */
ret = rk_get_mac_from_serial(addr, iface_id);
if (ret == 0 && is_valid_ether_addr(addr)) {
return 0; /* Success, return immediately */
}
/* Fall back to a random address on failure */
printk_once(KERN_WARNING "dwc-rockchip: Serial MAC generation failed, using random for interface %d\n",
iface_id);
#endif
/* Generate fallback MAC address */
rk_generate_fallback_mac(addr, iface_id);
return 0;
}
static void rk_get_eth_addr(void *priv, unsigned char *addr)
{
struct rk_priv_data *bsp_priv = priv;
struct device *dev = &bsp_priv->pdev->dev;
unsigned char ethaddr[ETH_ALEN * MAX_ETH] = {0};
int ret, id = bsp_priv->id;
struct rk_priv_data *bsp_priv = priv;
struct device *dev = &bsp_priv->pdev->dev;
unsigned char ethaddr[ETH_ALEN * MAX_ETH] = {0};
int ret, id = bsp_priv->id;
if (is_valid_ether_addr(addr))
goto out;
/* Check if the address is already valid */
if (is_valid_ether_addr(addr)) {
dev_info(dev, "%s: MAC address already set: %pM\n", __func__, addr);
return;
}
if (id < 0 || id >= MAX_ETH) {
dev_err(dev, "%s: Invalid ethernet bus id %d\n", __func__, id);
return;
}
if (id < 0 || id >= MAX_ETH) {
dev_err(dev, "%s: Invalid ethernet bus id %d\n", __func__, id);
/* Still try to set a MAC address even with invalid ID */
id = 0;
}
ret = rk_vendor_read(LAN_MAC_ID, ethaddr, ETH_ALEN * MAX_ETH);
if (ret <= 0 ||
!is_valid_ether_addr(&ethaddr[id * ETH_ALEN])) {
dev_err(dev, "%s: rk_vendor_read eth mac address failed (%d)\n",
__func__, ret);
eth_random_addr(&ethaddr[id * ETH_ALEN]);
memcpy(addr, &ethaddr[id * ETH_ALEN], ETH_ALEN);
dev_err(dev, "%s: generate random eth mac address: %pM\n", __func__, addr);
#ifdef DWMAC_ROCKCHIP_SERIAL_MAC
/* Try to generate MAC from system serial number first */
dev_info(dev, "%s: Attempting to generate MAC from system serial number for interface %d\n",
__func__, id);
if (rk_set_mac_address(addr, id) == 0 && is_valid_ether_addr(addr)) {
dev_info(dev, "%s: Generated MAC from serial: %pM\n", __func__, addr);
/* Store the generated MAC address to vendor storage for persistence */
ret = rk_vendor_read(LAN_MAC_ID, ethaddr, ETH_ALEN * MAX_ETH);
if (ret < 0) {
/* Initialize the buffer if read failed */
memset(ethaddr, 0, ETH_ALEN * MAX_ETH);
}
/* Copy our generated MAC to the appropriate position */
memcpy(&ethaddr[id * ETH_ALEN], addr, ETH_ALEN);
/* Write back to vendor storage */
ret = rk_vendor_write(LAN_MAC_ID, ethaddr, ETH_ALEN * MAX_ETH);
if (ret != 0) {
dev_warn(dev, "%s: Failed to store MAC to vendor storage (%d)\n",
__func__, ret);
} else {
dev_info(dev, "%s: Stored generated MAC to vendor storage\n", __func__);
}
return;
}
dev_warn(dev, "%s: Serial-based MAC generation failed for interface %d, falling back to vendor storage\n",
__func__, id);
#endif /* DWMAC_ROCKCHIP_SERIAL_MAC */
ret = rk_vendor_write(LAN_MAC_ID, ethaddr, ETH_ALEN * MAX_ETH);
if (ret != 0)
dev_err(dev, "%s: rk_vendor_write eth mac address failed (%d)\n",
__func__, ret);
/* Fallback: try to read from vendor storage */
ret = rk_vendor_read(LAN_MAC_ID, ethaddr, ETH_ALEN * MAX_ETH);
if (ret > 0 && is_valid_ether_addr(&ethaddr[id * ETH_ALEN])) {
memcpy(addr, &ethaddr[id * ETH_ALEN], ETH_ALEN);
dev_info(dev, "%s: Retrieved MAC from vendor storage: %pM\n",
__func__, addr);
return;
}
ret = rk_vendor_read(LAN_MAC_ID, ethaddr, ETH_ALEN * MAX_ETH);
if (ret != ETH_ALEN * MAX_ETH)
dev_err(dev, "%s: id: %d rk_vendor_read eth mac address failed (%d)\n",
__func__, id, ret);
} else {
memcpy(addr, &ethaddr[id * ETH_ALEN], ETH_ALEN);
}
out:
dev_err(dev, "%s: mac address: %pM\n", __func__, addr);
/* Last resort: generate fallback MAC */
dev_warn(dev, "%s: No valid MAC found, generating fallback for interface %d\n",
__func__, id);
rk_generate_fallback_mac(addr, id);
/* Store the fallback MAC to vendor storage */
if (ret < 0) {
/* Initialize the buffer if read failed */
memset(ethaddr, 0, ETH_ALEN * MAX_ETH);
}
memcpy(&ethaddr[id * ETH_ALEN], addr, ETH_ALEN);
ret = rk_vendor_write(LAN_MAC_ID, ethaddr, ETH_ALEN * MAX_ETH);
if (ret != 0) {
dev_err(dev, "%s: Failed to store fallback MAC to vendor storage (%d)\n",
__func__, ret);
} else {
dev_info(dev, "%s: Stored fallback MAC to vendor storage: %pM\n",
__func__, addr);
}
}
static int rk_gmac_probe(struct platform_device *pdev)
@@ -3483,5 +3625,5 @@ static struct platform_driver rk_gmac_dwmac_driver = {
module_platform_driver(rk_gmac_dwmac_driver);
MODULE_AUTHOR("Chen-Zhi (Roger Chen) <roger.chen@rock-chips.com>");
MODULE_DESCRIPTION("Rockchip RK3288 DWMAC specific glue layer");
MODULE_DESCRIPTION("Rockchip DWMAC specific glue layer");
MODULE_LICENSE("GPL");