net: dsa: microchip: add implementation for ethtool get_regs
The return regs-values follow a simple protocol, with a header identifying the type of reg-values and their length. Each reg-value consists of the register number and its value. In order to decode the result in userspace a small helper tool can be used. For the stat-mac-table and the dyn-mac table, the corresponding structs are added to the get-regs data. TI has the same approach for their am65-cpsw-nuss driver. Signed-off-by: Jan Sondhauss <jan.sondhauss@wago.com>
This commit is contained in:
@@ -9,6 +9,7 @@ ifdef CONFIG_NET_DSA_MICROCHIP_KSZ_PTP
|
||||
ksz_switch-objs += ksz_ptp.o
|
||||
endif
|
||||
|
||||
ksz_switch-objs += ksz8_ethtool.o
|
||||
ksz_switch-objs += ksz_devlink.o
|
||||
obj-$(CONFIG_NET_DSA_MICROCHIP_KSZ9477_I2C) += ksz9477_i2c.o
|
||||
obj-$(CONFIG_NET_DSA_MICROCHIP_KSZ_SPI) += ksz_spi.o
|
||||
|
||||
@@ -56,5 +56,6 @@ int ksz8_reset_switch(struct ksz_device *dev);
|
||||
int ksz8_switch_init(struct ksz_device *dev);
|
||||
void ksz8_switch_exit(struct ksz_device *dev);
|
||||
int ksz8_change_mtu(struct ksz_device *dev, int port, int mtu);
|
||||
|
||||
int ksz8_r_sta_mac_table(struct ksz_device *dev, u16 addr,
|
||||
struct alu_struct *alu, bool *valid);
|
||||
#endif
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "ksz8795_reg.h"
|
||||
#include "ksz8.h"
|
||||
|
||||
|
||||
static void ksz_cfg(struct ksz_device *dev, u32 addr, u8 bits, bool set)
|
||||
{
|
||||
regmap_update_bits(ksz_regmap_8(dev), addr, bits, set ? bits : 0);
|
||||
@@ -471,8 +472,8 @@ int ksz8_r_dyn_mac_table(struct ksz_device *dev, u16 addr, u8 *mac_addr,
|
||||
return rc;
|
||||
}
|
||||
|
||||
static int ksz8_r_sta_mac_table(struct ksz_device *dev, u16 addr,
|
||||
struct alu_struct *alu, bool *valid)
|
||||
int ksz8_r_sta_mac_table(struct ksz_device *dev, u16 addr,
|
||||
struct alu_struct *alu, bool *valid)
|
||||
{
|
||||
u32 data_hi, data_lo;
|
||||
const u8 *shifts;
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "ksz8_ethtool.h"
|
||||
#include "ksz8.h"
|
||||
|
||||
enum {
|
||||
KSZ8_REGDUMP_ID_GLOBAL = 0x1100,
|
||||
KSZ8_REGDUMP_ID_PORT = 0x1200,
|
||||
KSZ8_REGDUMP_ID_STAT_MAC_TBL = 0x2000,
|
||||
KSZ8_REGDUMP_ID_FDB = 0x2100,
|
||||
};
|
||||
|
||||
struct ksz_regdump_hdr {
|
||||
u32 module_id;
|
||||
u32 len;
|
||||
};
|
||||
|
||||
struct ksz_regdump_record {
|
||||
struct ksz_regdump_hdr hdr;
|
||||
u32 start_off;
|
||||
u32 end_off;
|
||||
};
|
||||
|
||||
// creates a regdump record entry
|
||||
// note we use u32 to transport register values, despite their size in
|
||||
// the device
|
||||
#define KSZ_REGDUMP_ENTRY(id, start, end) { \
|
||||
.hdr.module_id = (id), \
|
||||
.hdr.len = (((end) - (start)) * sizeof(u32)) * 2 \
|
||||
+ sizeof(struct ksz_regdump_hdr), \
|
||||
.start_off = (start), \
|
||||
.end_off = (end), \
|
||||
}
|
||||
|
||||
#define SIZE_IS_MULTIPLE_OF(T, T2) (sizeof(T) == (DIV_ROUND_UP(sizeof(T), sizeof(T2))* sizeof(T2)))
|
||||
|
||||
// sizeof(ksz8_dyn_mac_entry) == 12
|
||||
struct ksz8_dyn_mac_entry {
|
||||
/*entry 1*/
|
||||
u32 : 24;
|
||||
u8 timestamp;
|
||||
/* entry 2 & 3 */
|
||||
u8 fid;
|
||||
u8 src_port;
|
||||
u8 mac[ETH_ALEN];
|
||||
};
|
||||
|
||||
#define KSZ8_REGDUMP_MAC_ENTRY(id, entry_size, max_entries) { \
|
||||
.hdr.module_id = (id), \
|
||||
.hdr.len = 2 * sizeof(u32) + (entry_size) * (max_entries) \
|
||||
+ sizeof(struct ksz_regdump_hdr), \
|
||||
.start_off = 0, \
|
||||
.end_off = 0, \
|
||||
}
|
||||
|
||||
static const struct ksz_regdump_record ksz8_regdump_records[] = {
|
||||
KSZ_REGDUMP_ENTRY(KSZ8_REGDUMP_ID_GLOBAL, 0x00, 0x10),
|
||||
KSZ_REGDUMP_ENTRY(KSZ8_REGDUMP_ID_PORT, 0x10, 0x20),
|
||||
KSZ_REGDUMP_ENTRY(KSZ8_REGDUMP_ID_PORT + 1, 0x20, 0x30),
|
||||
KSZ_REGDUMP_ENTRY(KSZ8_REGDUMP_ID_PORT + 2, 0x30, 0x40),
|
||||
KSZ8_REGDUMP_MAC_ENTRY(KSZ8_REGDUMP_ID_STAT_MAC_TBL, sizeof(struct alu_struct), 8),
|
||||
KSZ8_REGDUMP_MAC_ENTRY(KSZ8_REGDUMP_ID_FDB, sizeof(struct ksz8_dyn_mac_entry), 1024),
|
||||
};
|
||||
|
||||
int ksz8_get_regs_len(struct ksz_device *dev, int port)
|
||||
{
|
||||
int i, length = 0;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(ksz8_regdump_records); i++)
|
||||
length += ksz8_regdump_records[i].hdr.len;
|
||||
|
||||
return length;
|
||||
}
|
||||
|
||||
static void ksz8_get_regs_dump_static_mac_table(struct ksz_device *dev, u32 *reg, u32 *pos)
|
||||
{
|
||||
BUILD_BUG_ON(!SIZE_IS_MULTIPLE_OF(struct alu_struct, u32));
|
||||
int index, ret;
|
||||
u32 cnt = *pos;
|
||||
|
||||
reg[cnt++] = KSZ8_REGDUMP_ID_STAT_MAC_TBL;
|
||||
|
||||
const u32 pos_length = cnt++;
|
||||
reg[pos_length] = sizeof(struct ksz_regdump_hdr);
|
||||
|
||||
for (index = 0; index < dev->info->num_statics; index++) {
|
||||
bool valid;
|
||||
struct alu_struct* alu = (struct alu_struct*)®[cnt];
|
||||
|
||||
ret = ksz8_r_sta_mac_table(dev, index, alu, &valid);
|
||||
if (ret)
|
||||
continue;
|
||||
|
||||
if (!valid)
|
||||
continue;
|
||||
|
||||
reg[pos_length] += sizeof(struct alu_struct);
|
||||
cnt += sizeof(struct alu_struct) / sizeof(u32);
|
||||
}
|
||||
*pos = cnt;
|
||||
}
|
||||
|
||||
static void ksz8_get_regs_dump_fdb(struct ksz_device *dev, u32 *reg, u32 *pos)
|
||||
{
|
||||
BUILD_BUG_ON(!SIZE_IS_MULTIPLE_OF(struct ksz8_dyn_mac_entry, u32));
|
||||
u16 i = 0;
|
||||
u16 entries = 0;
|
||||
u32 cnt = *pos;
|
||||
|
||||
reg[cnt++] = KSZ8_REGDUMP_ID_FDB;
|
||||
|
||||
const u32 pos_length = cnt++;
|
||||
reg[pos_length] = sizeof(struct ksz_regdump_hdr);
|
||||
|
||||
do {
|
||||
struct ksz8_dyn_mac_entry *entry = (struct ksz8_dyn_mac_entry*) ®[cnt];
|
||||
int ret = ksz8_r_dyn_mac_table(dev, i, entry->mac, &entry->fid, &entry->src_port,
|
||||
&entry->timestamp, &entries);
|
||||
|
||||
if (!ret) {
|
||||
reg[pos_length] += sizeof(struct ksz8_dyn_mac_entry);
|
||||
cnt += DIV_ROUND_UP(sizeof(struct ksz8_dyn_mac_entry), sizeof(u32));
|
||||
}
|
||||
i++;
|
||||
} while (i < entries);
|
||||
*pos = cnt;
|
||||
}
|
||||
|
||||
void ksz8_get_regs(struct ksz_device *dev, int port,
|
||||
struct ethtool_regs *regs, void *p)
|
||||
{
|
||||
int i, j;
|
||||
u32 pos = 0;
|
||||
u32 *reg = p;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(ksz8_regdump_records); i++) {
|
||||
struct ksz_regdump_record rec = ksz8_regdump_records[i];
|
||||
|
||||
if (rec.hdr.module_id == KSZ8_REGDUMP_ID_STAT_MAC_TBL) {
|
||||
ksz8_get_regs_dump_static_mac_table(dev, reg, &pos);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (rec.hdr.module_id == KSZ8_REGDUMP_ID_FDB) {
|
||||
ksz8_get_regs_dump_fdb(dev, reg, &pos);
|
||||
continue;
|
||||
}
|
||||
|
||||
reg[pos++] = rec.hdr.module_id;
|
||||
reg[pos++] = rec.hdr.len;
|
||||
|
||||
for (j = rec.start_off;
|
||||
j < rec.end_off; j++) {
|
||||
u8 val;
|
||||
|
||||
reg[pos++] = j;
|
||||
ksz_read8(dev, j, &val);
|
||||
reg[pos++] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#ifndef __KSZ8XXX_ETHTOOL_H
|
||||
#define __KSZ8XXX_ETHTOOL_H
|
||||
|
||||
#include <linux/types.h>
|
||||
#include "ksz_common.h"
|
||||
|
||||
int ksz8_get_regs_len(struct ksz_device *dev, int port);
|
||||
void ksz8_get_regs(struct ksz_device *dev, int port,
|
||||
struct ethtool_regs *regs, void *p);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "ksz9477.h"
|
||||
#include "lan937x.h"
|
||||
#include "ksz_devlink.h"
|
||||
#include "ksz8_ethtool.h"
|
||||
|
||||
#define MIB_COUNTER_NUM 0x20
|
||||
|
||||
@@ -222,6 +223,8 @@ static const struct ksz_dev_ops ksz8_dev_ops = {
|
||||
.devlink_param_get = ksz8_devlink_param_get,
|
||||
.devlink_param_set = ksz8_devlink_param_set,
|
||||
.devlink_info_get= ksz8_devlink_info_get,
|
||||
.get_regs = ksz8_get_regs,
|
||||
.get_regs_len = ksz8_get_regs_len,
|
||||
};
|
||||
|
||||
static void ksz9477_phylink_mac_link_up(struct ksz_device *dev, int port,
|
||||
@@ -3479,6 +3482,25 @@ static int ksz_devlink_param_set(struct dsa_switch *ds, u32 id,
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static int ksz_get_regs_len(struct dsa_switch *ds, int port)
|
||||
{
|
||||
struct ksz_device *dev = ds->priv;
|
||||
|
||||
if (dev->info->ops->get_regs_len)
|
||||
return dev->info->ops->get_regs_len(dev, port);
|
||||
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
static void ksz_get_regs(struct dsa_switch *ds, int port,
|
||||
struct ethtool_regs *regs, void *p)
|
||||
{
|
||||
struct ksz_device *dev = ds->priv;
|
||||
|
||||
if (dev->info->ops->get_regs)
|
||||
dev->info->ops->get_regs(dev, port, regs, p);
|
||||
}
|
||||
|
||||
static const struct dsa_switch_ops ksz_switch_ops = {
|
||||
.get_tag_protocol = ksz_get_tag_protocol,
|
||||
.connect_tag_protocol = ksz_connect_tag_protocol,
|
||||
@@ -3527,6 +3549,8 @@ static const struct dsa_switch_ops ksz_switch_ops = {
|
||||
.devlink_param_get = ksz_devlink_param_get,
|
||||
.devlink_param_set = ksz_devlink_param_set,
|
||||
.devlink_info_get = ksz_devlink_info_get,
|
||||
.get_regs = ksz_get_regs,
|
||||
.get_regs_len = ksz_get_regs_len,
|
||||
};
|
||||
|
||||
struct ksz_device *ksz_switch_alloc(struct device *base, void *priv)
|
||||
@@ -3724,6 +3748,7 @@ int ksz_switch_register(struct ksz_device *dev)
|
||||
schedule_delayed_work(&dev->mib_read, 0);
|
||||
|
||||
ret = ksz_devlink_param_setup(dev->ds);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL(ksz_switch_register);
|
||||
@@ -3741,7 +3766,6 @@ void ksz_switch_remove(struct ksz_device *dev)
|
||||
|
||||
if (dev->reset_gpio)
|
||||
gpiod_set_value_cansleep(dev->reset_gpio, 1);
|
||||
|
||||
}
|
||||
EXPORT_SYMBOL(ksz_switch_remove);
|
||||
|
||||
|
||||
@@ -377,6 +377,9 @@ struct ksz_dev_ops {
|
||||
int (*devlink_info_get)(struct ksz_device *dev,
|
||||
struct devlink_info_req *req,
|
||||
struct netlink_ext_ack *extack);
|
||||
int (*get_regs_len)(struct ksz_device *dev, int port);
|
||||
void (*get_regs)(struct ksz_device *dev, int port,
|
||||
struct ethtool_regs *regs, void *p);
|
||||
};
|
||||
|
||||
struct ksz_device *ksz_switch_alloc(struct device *base, void *priv);
|
||||
|
||||
Reference in New Issue
Block a user