diff --git a/platform/broadcom/sonic-platform-modules-nexthop/common/modules/Makefile b/platform/broadcom/sonic-platform-modules-nexthop/common/modules/Makefile index 415298697af2..298fa1e64c06 100644 --- a/platform/broadcom/sonic-platform-modules-nexthop/common/modules/Makefile +++ b/platform/broadcom/sonic-platform-modules-nexthop/common/modules/Makefile @@ -1,4 +1,5 @@ obj-m := pddf_custom_fpga_algo.o +obj-m += pddf_custom_mdio_algo.o obj-m += nh_tda38740.o obj-m += nh_isl68137.o obj-m += nh_pmbus_core.o diff --git a/platform/broadcom/sonic-platform-modules-nexthop/common/modules/pddf_custom_mdio_algo.c b/platform/broadcom/sonic-platform-modules-nexthop/common/modules/pddf_custom_mdio_algo.c new file mode 100644 index 000000000000..557572cc0086 --- /dev/null +++ b/platform/broadcom/sonic-platform-modules-nexthop/common/modules/pddf_custom_mdio_algo.c @@ -0,0 +1,229 @@ +/* + * Copyright (C) 2025 Nexthop Systems Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#define __STDC_WANT_LIB_EXT1__ 1 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../../../../pddf/i2c/modules/include/pddf_multifpgapci_defs.h" +#include "../../../../pddf/i2c/modules/include/pddf_multifpgapci_mdio_defs.h" + +// MDIO Control Register +#define FPGA_MDIO_CTRL_REG_OFFSET 0x0000 + +// Bit definitions for MDIO Control Register +#define FPGA_MDIO_CTRL_WRITE_CMD_BIT_POS 31 +#define FPGA_MDIO_CTRL_READ_CMD_BIT_POS 30 + +#define FPGA_MDIO_CTRL_OP_SHIFT 26 +#define FPGA_MDIO_CTRL_OP_MASK 0x3 + +#define FPGA_MDIO_OP_ADDRESS_VAL 0x0 +#define FPGA_MDIO_OP_WRITE_VAL 0x1 +#define FPGA_MDIO_OP_READ_VAL 0x3 +#define FPGA_MDIO_OP_POSTREAD_VAL 0x2 + +#define FPGA_MDIO_CTRL_PHY_ADDR_SHIFT 21 +#define FPGA_MDIO_CTRL_DEV_ADDR_SHIFT 16 +#define FPGA_MDIO_CTRL_DATA_SHIFT 0 +#define FPGA_MDIO_CTRL_DATA_MASK 0xFFFF + +// MDIO Read Data Register +#define FPGA_MDIO_READ_DATA_REG_OFFSET 0x0004 + +// Bit definitions for MDIO Read Data Register +#define FPGA_MDIO_READ_DATA_VALID_BIT BIT(31) +#define FPGA_MDIO_READ_DATA_BUSY_BIT BIT(30) +#define FPGA_MDIO_READ_DATA_MASK 0xFFFF + +#define MDIO_DEVAD_SHIFT 16 + +static int fpga_mdio_wait_for_idle(struct fpga_mdio_priv *priv) +{ + // Timeout after 100ms if busy bit has not cleared + unsigned long timeout = jiffies + msecs_to_jiffies(100); + uint32_t read_data_reg; + + do { + read_data_reg = ioread32(priv->reg_base + + FPGA_MDIO_READ_DATA_REG_OFFSET); + if (!(read_data_reg & FPGA_MDIO_READ_DATA_BUSY_BIT)) { + // Busy bit is cleared, transaction is complete + return 0; + } + usleep_range(5, 10); + } while (time_before(jiffies, timeout)); + + pddf_dbg( + MULTIFPGA, + KERN_ERR + "[%s]: MDIO transaction timed out waiting for busy bit to clear\n", + __FUNCTION__); + return -ETIMEDOUT; +} + +static int fpga_mdio_do_transaction(struct fpga_mdio_priv *priv, int phy_addr, + int dev_addr, uint32_t op_val, + uint16_t data_val, uint32_t cmd_bit) +{ + uint32_t cmd_val = + (BIT(cmd_bit) | (op_val << FPGA_MDIO_CTRL_OP_SHIFT) | + ((phy_addr & 0x1F) << FPGA_MDIO_CTRL_PHY_ADDR_SHIFT) | + ((dev_addr & 0x1F) << FPGA_MDIO_CTRL_DEV_ADDR_SHIFT) | + ((data_val & FPGA_MDIO_CTRL_DATA_MASK) + << FPGA_MDIO_CTRL_DATA_SHIFT)); + + iowrite32(cmd_val, priv->reg_base + FPGA_MDIO_CTRL_REG_OFFSET); + int ret = fpga_mdio_wait_for_idle(priv); + if (ret < 0) + return ret; + + iowrite32(0x0, priv->reg_base + FPGA_MDIO_CTRL_REG_OFFSET); + return fpga_mdio_wait_for_idle(priv); +} + +int fpga_mdio_read(struct mii_bus *bus, int phy_addr, int reg_num) +{ + struct fpga_mdio_priv *priv = bus->priv; + int ret = -EIO; + + if (!priv || !priv->reg_base) { + pddf_dbg( + MULTIFPGA, + KERN_ERR + "[%s]: Bus private data (reg_base) not properly set for bus %s\n", + __FUNCTION__, dev_name(&bus->dev)); + return -EINVAL; + } + + // Extract the device and reg addresses from reg_num + int dev_addr = (reg_num >> MDIO_DEVAD_SHIFT) & 0x1F; + int c45_reg_addr = reg_num & 0xFFFF; + + mutex_lock(&priv->lock); + + // Clause 45 Handling + + // Step 1: Write the register address + ret = fpga_mdio_do_transaction(priv, phy_addr, dev_addr, + FPGA_MDIO_OP_ADDRESS_VAL, c45_reg_addr, + FPGA_MDIO_CTRL_WRITE_CMD_BIT_POS); + if (ret < 0) + goto out; + + // Step 2: Read the data from the register + ret = fpga_mdio_do_transaction(priv, phy_addr, dev_addr, + FPGA_MDIO_OP_READ_VAL, 0, + FPGA_MDIO_CTRL_READ_CMD_BIT_POS); + if (ret < 0) + goto out; + + // Poll for Read Data Valid and read data + unsigned long timeout = jiffies + msecs_to_jiffies(100); + do { + uint32_t read_data_reg = ioread32( + priv->reg_base + FPGA_MDIO_READ_DATA_REG_OFFSET); + if (read_data_reg & FPGA_MDIO_READ_DATA_VALID_BIT) { + ret = (read_data_reg >> FPGA_MDIO_CTRL_DATA_SHIFT) & + FPGA_MDIO_READ_DATA_MASK; + goto out; + } + usleep_range(10, 20); + } while (time_before(jiffies, timeout)); + + pddf_dbg( + MULTIFPGA, + KERN_ERR + "[%s]: C45 READ data not valid within timeout for bus %s, PHY 0x%x, MMD 0x%x\n", + __FUNCTION__, dev_name(&bus->dev), phy_addr, dev_addr); + ret = -ETIMEDOUT; + +out: + mutex_unlock(&priv->lock); + return ret; +} +EXPORT_SYMBOL(fpga_mdio_read); + +int fpga_mdio_write(struct mii_bus *bus, int phy_addr, int reg_num, + uint16_t val) +{ + struct fpga_mdio_priv *priv = bus->priv; + int ret; + + if (!priv || !priv->reg_base) { + pddf_dbg( + MULTIFPGA, + KERN_ERR + "[%s]: Bus private data (reg_base) not properly set for bus %s\n", + __FUNCTION__, dev_name(&bus->dev)); + return -EINVAL; + } + + // Extract the device and reg addresses from reg_num + int dev_addr = (reg_num >> MDIO_DEVAD_SHIFT) & 0x1F; + int c45_reg_addr = reg_num & 0xFFFF; + + mutex_lock(&priv->lock); + + // Clause 45 Handling + + // Step 1: Write the register address + ret = fpga_mdio_do_transaction(priv, phy_addr, dev_addr, + FPGA_MDIO_OP_ADDRESS_VAL, c45_reg_addr, + FPGA_MDIO_CTRL_WRITE_CMD_BIT_POS); + if (ret < 0) + goto out; + + // Step 2: Write the data to the register + ret = fpga_mdio_do_transaction(priv, phy_addr, dev_addr, + FPGA_MDIO_OP_WRITE_VAL, val, + FPGA_MDIO_CTRL_WRITE_CMD_BIT_POS); + if (ret < 0) + goto out; + +out: + mutex_unlock(&priv->lock); + return 0; +} +EXPORT_SYMBOL(fpga_mdio_write); + +static struct mdio_fpga_ops fpga_algo_ops_instance = { + .read = fpga_mdio_read, + .write = fpga_mdio_write, +}; + +static int __init pddf_custom_mdio_algo_init(void) +{ + pddf_dbg(MULTIFPGA, KERN_INFO "[%s]\n", __FUNCTION__); + mdio_fpga_algo_ops = &fpga_algo_ops_instance; + return 0; +} + +static void __exit pddf_custom_mdio_algo_exit(void) +{ + pddf_dbg(MULTIFPGA, KERN_INFO "[%s]\n", __FUNCTION__); + mdio_fpga_algo_ops = NULL; + return; +} + +module_init(pddf_custom_mdio_algo_init); +module_exit(pddf_custom_mdio_algo_exit); + +MODULE_DESCRIPTION("Custom algorithm for Nexthop FPGAPCIe MDIO implementation"); +MODULE_VERSION("1.0.0"); +MODULE_LICENSE("GPL"); diff --git a/platform/pddf/i2c/debian/rules b/platform/pddf/i2c/debian/rules index 9d78317880fc..a982a7f58606 100755 --- a/platform/pddf/i2c/debian/rules +++ b/platform/pddf/i2c/debian/rules @@ -19,7 +19,7 @@ PACKAGE_PRE_NAME := sonic-platform-pddf KVERSION ?= $(shell uname -r) KERNEL_SRC := /lib/modules/$(KVERSION) MOD_SRC_DIR:= $(shell pwd) -MODULE_DIRS:= client cpld cpld/driver cpldmux cpldmux/driver fpgai2c fpgai2c/driver fpgapci fpgapci/driver fpgapci/algos multifpgapci multifpgapci/driver multifpgapci/gpio multifpgapci/gpio/driver multifpgapci/i2c fan fan/driver mux gpio led psu psu/driver sysstatus xcvr xcvr/driver +MODULE_DIRS:= client cpld cpld/driver cpldmux cpldmux/driver fpgai2c fpgai2c/driver fpgapci fpgapci/driver fpgapci/algos multifpgapci multifpgapci/driver multifpgapci/gpio multifpgapci/gpio/driver multifpgapci/i2c multifpgapci/mdio fan fan/driver mux gpio led psu psu/driver sysstatus xcvr xcvr/driver MODULE_DIR:= modules UTILS_DIR := utils SERVICE_DIR := service diff --git a/platform/pddf/i2c/modules/include/pddf_multifpgapci_defs.h b/platform/pddf/i2c/modules/include/pddf_multifpgapci_defs.h index ba5c9ee35866..07d420dbb6b8 100644 --- a/platform/pddf/i2c/modules/include/pddf_multifpgapci_defs.h +++ b/platform/pddf/i2c/modules/include/pddf_multifpgapci_defs.h @@ -23,6 +23,7 @@ #include "pddf_multifpgapci_gpio_defs.h" #include "pddf_multifpgapci_i2c_defs.h" +#include "pddf_multifpgapci_mdio_defs.h" #define NAME_SIZE 32 diff --git a/platform/pddf/i2c/modules/include/pddf_multifpgapci_mdio_defs.h b/platform/pddf/i2c/modules/include/pddf_multifpgapci_mdio_defs.h new file mode 100644 index 000000000000..6829b259550c --- /dev/null +++ b/platform/pddf/i2c/modules/include/pddf_multifpgapci_mdio_defs.h @@ -0,0 +1,79 @@ +/* + * Copyright 2025 Nexthop Systems Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#ifndef __PDDF_MULTIFPGAPCI_MDIO_DEFS_H__ +#define __PDDF_MULTIFPGAPCI_MDIO_DEFS_H__ + +#include "linux/types.h" +#include +#include +#include +#include + +#include "pddf_client_defs.h" + +#define MDIO_MAX_BUS 512 + +struct mdio_bus_attrs { + PDDF_ATTR attr_ch_base_offset; + PDDF_ATTR attr_ch_size; + PDDF_ATTR attr_num_virt_ch; + PDDF_ATTR attr_new_mdio_bus; + PDDF_ATTR attr_del_mdio_bus; +}; + +#define NUM_MDIO_BUS_ATTRS (sizeof(struct mdio_bus_attrs) / sizeof(PDDF_ATTR)) + +struct mdio_bus_sysfs_vals { + uint32_t ch_base_offset; + uint32_t ch_size; + uint32_t num_virt_ch; +}; + +struct mdio_bus_drvdata { + struct pci_dev *pci_dev; + size_t bar_length; + struct kobject *mdio_kobj; + + // temp_sysfs_vals store temporary values provided by sysfs, + // which are eventually copied/saved to MDIO bus platform data. + struct mdio_bus_sysfs_vals temp_sysfs_vals; + + // platform data + struct mii_bus *mdio_buses[MDIO_MAX_BUS]; + bool mdio_bus_registered[MDIO_MAX_BUS]; + void *__iomem ch_base_addr; + int ch_size; + int num_virt_ch; + + // sysfs attrs + struct mdio_bus_attrs attrs; + struct attribute *mdio_bus_attrs[NUM_MDIO_BUS_ATTRS + 1]; + struct attribute_group mdio_bus_attr_group; +}; + +struct fpga_mdio_priv { + void __iomem *reg_base; // Base address for this MDIO instance + struct mutex lock; // Mutex for this MDIO instance + int last_read_value; +}; + +struct mdio_fpga_ops { + int (*read)(struct mii_bus *bus, int phy_id, int regnum); + int (*write)(struct mii_bus *bus, int phy_id, int regnum, u16 val); +}; + +extern struct mdio_fpga_ops *mdio_fpga_algo_ops; + +#endif diff --git a/platform/pddf/i2c/modules/multifpgapci/Makefile b/platform/pddf/i2c/modules/multifpgapci/Makefile index ad6cac982ccf..e862c936d69d 100644 --- a/platform/pddf/i2c/modules/multifpgapci/Makefile +++ b/platform/pddf/i2c/modules/multifpgapci/Makefile @@ -1,4 +1,4 @@ -obj-m := driver/ gpio/ i2c/ +obj-m := driver/ gpio/ i2c/ mdio/ obj-m += pddf_multifpgapci_module.o ccflags-y := -I$(M)/modules/include diff --git a/platform/pddf/i2c/modules/multifpgapci/mdio/Makefile b/platform/pddf/i2c/modules/multifpgapci/mdio/Makefile new file mode 100644 index 000000000000..efbc19465c55 --- /dev/null +++ b/platform/pddf/i2c/modules/multifpgapci/mdio/Makefile @@ -0,0 +1,3 @@ +obj-m := pddf_multifpgapci_mdio_module.o + +ccflags-y := -I$(M)/modules/include diff --git a/platform/pddf/i2c/modules/multifpgapci/mdio/pddf_multifpgapci_mdio_module.c b/platform/pddf/i2c/modules/multifpgapci/mdio/pddf_multifpgapci_mdio_module.c new file mode 100644 index 000000000000..98b49b0bb75f --- /dev/null +++ b/platform/pddf/i2c/modules/multifpgapci/mdio/pddf_multifpgapci_mdio_module.c @@ -0,0 +1,501 @@ +/* + * Copyright 2025 Nexthop Systems Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * Description: + * PDDF MULTIFPGAPCI kernel module for registering MDIO buses. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pddf_multifpgapci_defs.h" +#include "pddf_multifpgapci_mdio_defs.h" + +DEFINE_XARRAY(mdio_drvdata_map); + +struct mdio_fpga_ops *mdio_fpga_algo_ops = NULL; +EXPORT_SYMBOL_GPL(mdio_fpga_algo_ops); + +static ssize_t mdio_access_show(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t mdio_access_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count); + +static DEVICE_ATTR_RW(mdio_access); + +static ssize_t mdio_access_store(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct mii_bus *bus = to_mii_bus(dev); + struct fpga_mdio_priv *priv = bus->priv; + char op[10]; + int phy_addr, reg_num, val; + int ret = -EINVAL; + + int num_args = + sscanf(buf, "%9s %i %i %i", op, &phy_addr, ®_num, &val); + if (num_args < 1) { + pddf_dbg(MULTIFPGA, KERN_ERR + "Invalid MDIO access format. No command provided.\n"); + return -EINVAL; + } + + if (strcmp(op, "write") == 0) { + // MDIO write + if (num_args < 4) { + pddf_dbg( + MULTIFPGA, KERN_ERR + "Invalid MDIO write access format. Expected 'write '\n"); + return -EINVAL; + } + ret = mdio_fpga_algo_ops->write(bus, phy_addr, reg_num, val); + if (ret == 0) { + return count; + } else { + pddf_dbg(MULTIFPGA, + KERN_ERR + "MDIO write failed for phy=%d, reg=0x%x\n", + phy_addr, reg_num); + return ret; + } + } else if (strcmp(op, "read") == 0) { + // MDIO read - the result is stored in priv->last_read_value. + if (num_args < 3) { + pddf_dbg( + MULTIFPGA, KERN_ERR + "Invalid MDIO read access format. Expected 'read '\n"); + return -EINVAL; + } + ret = mdio_fpga_algo_ops->read(bus, phy_addr, reg_num); + if (ret >= 0) { + priv->last_read_value = ret; + return count; + } else { + pddf_dbg(MULTIFPGA, + KERN_ERR + "MDIO read failed for phy=%d, reg=0x%x\n", + phy_addr, reg_num); + return ret; + } + } else { + pddf_dbg(MULTIFPGA, KERN_ERR "Invalid MDIO operation: %s\n", + op); + return -EINVAL; + } + + return ret; +} + +// The show function returns the value of the last read operation to user space. +// This is triggered by a cat command on the mdio_access file. +static ssize_t mdio_access_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mii_bus *bus = to_mii_bus(dev); + struct fpga_mdio_priv *priv = bus->priv; + + return scnprintf(buf, PAGE_SIZE, "0x%x\n", priv->last_read_value); +} + +ssize_t new_mdio_bus(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + int index, err; + struct mii_bus *new_bus = NULL; + struct fpga_mdio_priv *algo_priv; + + err = kstrtoint(buf, 10, &index); + if (err != 0) { + pddf_dbg(MULTIFPGA, KERN_ERR "Error converting string: %d\n", + err); + return -EINVAL; + } + + if (index < 0 || index >= MDIO_MAX_BUS) { + pddf_dbg(MULTIFPGA, + KERN_ERR "[%s] MDIO bus %d out of range [0, %d)\n", + __FUNCTION__, index, MDIO_MAX_BUS); + return -ENODEV; + } + + struct pddf_data_attribute *_ptr = (struct pddf_data_attribute *)da; + struct pci_dev *pci_dev = (struct pci_dev *)_ptr->addr; + struct mdio_bus_drvdata *mdio_privdata; + + pddf_dbg(MULTIFPGA, KERN_INFO "[%s] pci_dev %s\n", __FUNCTION__, + pci_name(pci_dev)); + + unsigned dev_index = multifpgapci_get_pci_dev_index(pci_dev); + mdio_privdata = xa_load(&mdio_drvdata_map, dev_index); + if (!mdio_privdata) { + pddf_dbg(MULTIFPGA, + KERN_ERR + "[%s] unable to retrieve mdio_privdata for device %s", + __FUNCTION__, pci_name(pci_dev)); + return -ENODEV; + } + + if (mdio_privdata->mdio_bus_registered[index]) { + pddf_dbg(MULTIFPGA, + KERN_ERR "[%s] MDIO bus %d already registered\n", + __FUNCTION__, index); + return -ENODEV; + } + + new_bus = mdiobus_alloc(); + if (!new_bus) { + pddf_dbg(MULTIFPGA, + KERN_ERR "[%s] Failed to allocate MDIO bus %d\n", + __FUNCTION__, index); + return -ENOMEM; + } + + // Allocate the private data for the MDIO algorithm + algo_priv = kzalloc(sizeof(*algo_priv), GFP_KERNEL); + if (!algo_priv) { + pddf_dbg( + MULTIFPGA, + KERN_ERR + "[%s] Failed to allocate FPGA MDIO algo private data\n", + __FUNCTION__); + mdiobus_free(new_bus); + return -ENOMEM; + } + algo_priv->reg_base = + mdio_privdata->ch_base_addr + index * mdio_privdata->ch_size; + mutex_init(&algo_priv->lock); + new_bus->priv = algo_priv; + + mdio_privdata->mdio_buses[index] = new_bus; + + new_bus->name = "pci-mdio-bus"; + snprintf(new_bus->id, MII_BUS_ID_SIZE, "pci-mdio-%d", index); + + if (!mdio_fpga_algo_ops || !mdio_fpga_algo_ops->read || + !mdio_fpga_algo_ops->write) { + pddf_dbg( + MULTIFPGA, + KERN_ERR + "[%s] MDIO FPGA algorithm module not loaded or incomplete!\n", + __FUNCTION__); + goto err_cleanup; + } + + new_bus->read = mdio_fpga_algo_ops->read; + new_bus->write = mdio_fpga_algo_ops->write; + new_bus->owner = THIS_MODULE; + new_bus->parent = &pci_dev->dev; + + err = mdiobus_register(new_bus); + if (err != 0) { + pddf_dbg(MULTIFPGA, KERN_ERR "Could not register MDIO bus %d\n", + index); + goto err_cleanup; + } + mdio_privdata->mdio_bus_registered[index] = true; + + // Attach the new sysfs path for user-space access + err = device_create_file(&new_bus->dev, &dev_attr_mdio_access); + if (err) { + pddf_dbg(MULTIFPGA, + KERN_ERR + "Failed to create sysfs file for MDIO bus %s: %d\n", + dev_name(&new_bus->dev), err); + goto err_cleanup_registered; + } + + pddf_dbg(MULTIFPGA, KERN_INFO "[%s] Registered MDIO bus id: %s\n", + __FUNCTION__, dev_name(&new_bus->dev)); + + return count; + +err_cleanup_registered: + mdiobus_unregister(new_bus); + +err_cleanup: + if (new_bus) { + mdiobus_free(new_bus); + mdio_privdata->mdio_buses[index] = NULL; + } + if (algo_priv) { + kfree(algo_priv); + } + mdio_privdata->mdio_bus_registered[index] = false; + return err; +} + +ssize_t del_mdio_bus(struct device *dev, struct device_attribute *da, + const char *buf, size_t count) +{ + int index, error; + + error = kstrtoint(buf, 10, &index); + if (error != 0) { + pddf_dbg(MULTIFPGA, KERN_ERR "Error converting string: %d\n", + error); + return -EINVAL; + } + + if (index < 0 || index >= MDIO_MAX_BUS) { + pddf_dbg(MULTIFPGA, + KERN_ERR "[%s] MDIO bus %d out of range [0, %d)\n", + __FUNCTION__, index, MDIO_MAX_BUS); + return -ENODEV; + } + + struct pddf_data_attribute *_ptr = (struct pddf_data_attribute *)da; + struct pci_dev *pci_dev = (struct pci_dev *)_ptr->addr; + struct mdio_bus_drvdata *mdio_privdata; + + unsigned dev_index = multifpgapci_get_pci_dev_index(pci_dev); + mdio_privdata = xa_load(&mdio_drvdata_map, dev_index); + if (!mdio_privdata) { + pddf_dbg(MULTIFPGA, + KERN_ERR + "[%s] unable to retrieve mdio_privdata for device %s", + __FUNCTION__, pci_name(pci_dev)); + return -ENODEV; + } + + struct mii_bus *bus_to_unregister = mdio_privdata->mdio_buses[index]; + + if (!bus_to_unregister) { + pddf_dbg( + MULTIFPGA, + KERN_ERR + "%s: MDIO bus %d marked registered but poiner is NULL\n", + __FUNCTION__, index); + return -ENODEV; + } + + pddf_dbg(MULTIFPGA, + KERN_INFO "[%s] Attempting to unregister MDIO bus: %d\n", + __FUNCTION__, index); + + // Remove the custom sysfs file + device_remove_file(&bus_to_unregister->dev, &dev_attr_mdio_access); + + mdiobus_unregister(bus_to_unregister); + mdiobus_free(bus_to_unregister); + + mdio_privdata->mdio_bus_registered[index] = false; + mdio_privdata->mdio_buses[index] = NULL; + + return count; +} + +int pddf_multifpgapci_mdio_attach(struct pci_dev *pci_dev, struct kobject *kobj) +{ + pddf_dbg(MULTIFPGA, KERN_INFO "[%s] pci_dev %s\n", __FUNCTION__, + pci_name(pci_dev)); + struct mdio_bus_drvdata *mdio_privdata; + int err; + + mdio_privdata = kzalloc(sizeof(struct mdio_bus_drvdata), GFP_KERNEL); + if (!mdio_privdata) { + return -ENOMEM; + } + mdio_privdata->pci_dev = pci_dev; + + mdio_privdata->mdio_kobj = kobject_create_and_add("mdio", kobj); + if (!mdio_privdata->mdio_kobj) { + pddf_dbg(MULTIFPGA, KERN_ERR "[%s] create mdio kobj failed\n", + __FUNCTION__); + err = -ENOMEM; + goto free_privdata; + } + + memset(mdio_privdata->mdio_bus_registered, 0, + sizeof(mdio_privdata->mdio_bus_registered)); + + PDDF_DATA_ATTR(ch_base_offset, S_IWUSR | S_IRUGO, show_pddf_data, + store_pddf_data, PDDF_UINT32, sizeof(uint32_t), + (void *)&mdio_privdata->temp_sysfs_vals.ch_base_offset, + NULL); + + PDDF_DATA_ATTR(ch_size, S_IWUSR | S_IRUGO, show_pddf_data, + store_pddf_data, PDDF_UINT32, sizeof(uint32_t), + (void *)&mdio_privdata->temp_sysfs_vals.ch_size, NULL); + + PDDF_DATA_ATTR(num_virt_ch, S_IWUSR | S_IRUGO, show_pddf_data, + store_pddf_data, PDDF_UINT32, sizeof(uint32_t), + (void *)&mdio_privdata->temp_sysfs_vals.num_virt_ch, + NULL); + + PDDF_DATA_ATTR(new_mdio_bus, S_IWUSR | S_IRUGO, show_pddf_data, + new_mdio_bus, PDDF_CHAR, NAME_SIZE, (void *)pci_dev, + NULL); + + PDDF_DATA_ATTR(del_mdio_bus, S_IWUSR | S_IRUGO, show_pddf_data, + del_mdio_bus, PDDF_CHAR, NAME_SIZE, (void *)pci_dev, + NULL); + + mdio_privdata->attrs.attr_ch_base_offset = attr_ch_base_offset; + mdio_privdata->attrs.attr_ch_size = attr_ch_size; + mdio_privdata->attrs.attr_num_virt_ch = attr_num_virt_ch; + mdio_privdata->attrs.attr_new_mdio_bus = attr_new_mdio_bus; + mdio_privdata->attrs.attr_del_mdio_bus = attr_del_mdio_bus; + + struct attribute *mdio_bus_attrs[NUM_MDIO_BUS_ATTRS + 1] = { + &mdio_privdata->attrs.attr_ch_base_offset.dev_attr.attr, + &mdio_privdata->attrs.attr_ch_size.dev_attr.attr, + &mdio_privdata->attrs.attr_num_virt_ch.dev_attr.attr, + &mdio_privdata->attrs.attr_new_mdio_bus.dev_attr.attr, + &mdio_privdata->attrs.attr_del_mdio_bus.dev_attr.attr, + NULL, + }; + + memcpy(mdio_privdata->mdio_bus_attrs, mdio_bus_attrs, + sizeof(mdio_privdata->mdio_bus_attrs)); + + mdio_privdata->mdio_bus_attr_group.attrs = + mdio_privdata->mdio_bus_attrs; + + err = sysfs_create_group(mdio_privdata->mdio_kobj, + &mdio_privdata->mdio_bus_attr_group); + if (err) { + pddf_dbg(MULTIFPGA, + KERN_ERR "[%s] sysfs_create_group error, status: %d\n", + __FUNCTION__, err); + goto free_kobj; + } + unsigned dev_index = multifpgapci_get_pci_dev_index(pci_dev); + xa_store(&mdio_drvdata_map, dev_index, mdio_privdata, GFP_KERNEL); + + return 0; + +free_kobj: + kobject_put(mdio_privdata->mdio_kobj); +free_privdata: + kfree(mdio_privdata); + return err; +} + +static void pddf_multifpgapci_mdio_detach(struct pci_dev *pci_dev, + struct kobject *kobj) +{ + struct mdio_bus_drvdata *mdio_privdata; + int i; + pddf_dbg(MULTIFPGA, KERN_INFO "[%s] pci_dev %s\n", __FUNCTION__, + pci_name(pci_dev)); + + unsigned dev_index = multifpgapci_get_pci_dev_index(pci_dev); + mdio_privdata = xa_load(&mdio_drvdata_map, dev_index); + if (!mdio_privdata) { + pddf_dbg(MULTIFPGA, + KERN_ERR + "[%s] unable to find mdio module data for device %s\n", + __FUNCTION__, pci_name(pci_dev)); + return; + } + + for (i = 0; i < MDIO_MAX_BUS; i++) { + if (mdio_privdata->mdio_bus_registered[i]) { + pddf_dbg(MULTIFPGA, + KERN_INFO "[%s] unregistering MDIO bus: %s\n", + __FUNCTION__, + mdio_privdata->mdio_buses[i]->name); + mdiobus_unregister(mdio_privdata->mdio_buses[i]); + } + } + if (mdio_privdata->mdio_kobj) { + sysfs_remove_group(mdio_privdata->mdio_kobj, + &mdio_privdata->mdio_bus_attr_group); + kobject_put(mdio_privdata->mdio_kobj); + mdio_privdata->mdio_kobj = NULL; + } + xa_erase(&mdio_drvdata_map, (unsigned long)pci_dev); +} + +static void pddf_multifpgapci_mdio_map_bar(struct pci_dev *pci_dev, + void __iomem *bar_base, + unsigned long bar_start, + unsigned long bar_len) +{ + struct mdio_bus_drvdata *mdio_privdata; + pddf_dbg(MULTIFPGA, KERN_INFO "[%s] pci_dev %s\n", __FUNCTION__, + pci_name(pci_dev)); + unsigned dev_index = multifpgapci_get_pci_dev_index(pci_dev); + mdio_privdata = xa_load(&mdio_drvdata_map, dev_index); + if (!mdio_privdata) { + pddf_dbg(MULTIFPGA, + KERN_ERR + "[%s] unable to find mdio module data for device %s\n", + __FUNCTION__, pci_name(pci_dev)); + return; + } + struct mdio_bus_sysfs_vals *mdio_pddf_data = + &mdio_privdata->temp_sysfs_vals; + + mdio_privdata->ch_base_addr = bar_base + mdio_pddf_data->ch_base_offset; + mdio_privdata->num_virt_ch = mdio_pddf_data->num_virt_ch; + mdio_privdata->ch_size = mdio_pddf_data->ch_size; +} + +static void pddf_multifpgapci_mdio_unmap_bar(struct pci_dev *pci_dev, + void __iomem *bar_base, + unsigned long bar_start, + unsigned long bar_len) +{ + struct mdio_bus_drvdata *mdio_privdata; + pddf_dbg(MULTIFPGA, KERN_INFO "[%s] pci_dev %s\n", __FUNCTION__, + pci_name(pci_dev)); + unsigned dev_index = multifpgapci_get_pci_dev_index(pci_dev); + mdio_privdata = xa_load(&mdio_drvdata_map, dev_index); + if (!mdio_privdata) { + pddf_dbg(MULTIFPGA, + KERN_ERR + "[%s] unable to find mdio module data for device %s\n", + __FUNCTION__, pci_name(pci_dev)); + return; + } + mdio_privdata->ch_base_addr = NULL; +} + +static struct protocol_ops mdio_protocol_ops = { + .attach = pddf_multifpgapci_mdio_attach, + .detach = pddf_multifpgapci_mdio_detach, + .map_bar = pddf_multifpgapci_mdio_map_bar, + .unmap_bar = pddf_multifpgapci_mdio_unmap_bar, + .name = "mdio", +}; + +static int __init pddf_multifpgapci_mdio_init(void) +{ + pddf_dbg(MULTIFPGA, KERN_INFO "Loading MDIO protocol module\n"); + xa_init(&mdio_drvdata_map); + return multifpgapci_register_protocol("mdio", &mdio_protocol_ops); +} + +static void __exit pddf_multifpgapci_mdio_exit(void) +{ + pddf_dbg(MULTIFPGA, KERN_INFO "Unloading MDIO protocol module\n"); + multifpgapci_unregister_protocol("mdio"); + xa_destroy(&mdio_drvdata_map); +} + +module_init(pddf_multifpgapci_mdio_init); +module_exit(pddf_multifpgapci_mdio_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Nexthop Systems"); +MODULE_DESCRIPTION( + "PDDF MULTIFPGAPCI kernel module for registering MDIO buses."); diff --git a/platform/pddf/i2c/utils/pddfparse.py b/platform/pddf/i2c/utils/pddfparse.py index ff1197461352..45e40b672e46 100755 --- a/platform/pddf/i2c/utils/pddfparse.py +++ b/platform/pddf/i2c/utils/pddfparse.py @@ -530,7 +530,13 @@ def create_multifpgapci_device(self, dev, ops): ret = self.create_device(dev['i2c']['dev_attr'], "pddf/devices/multifpgapci/{}/i2c".format(bdf), ops) if ret != 0: return create_ret.append(ret) - + + # MDIO specific data store + if 'mdio' in dev: + ret = self.create_device(dev['mdio']['dev_attr'], "pddf/devices/multifpgapci/{}/mdio".format(bdf), ops) + if ret != 0: + return create_ret.append(ret) + # TODO: add GPIO & SPI specific data stores cmd = "echo 'fpgapci_init' > /sys/kernel/pddf/devices/multifpgapci/{}/dev_ops".format(bdf) @@ -544,6 +550,11 @@ def create_multifpgapci_device(self, dev, ops): if ret != 0: return create_ret.append(ret) + if 'mdio' in dev: + ret = self.create_mdio_bus(bdf, dev['mdio'], ops) + if ret != 0: + return create_ret.append(ret) + if 'gpio' in dev: ret = self.create_multifpgapci_gpio_device(bdf, dev['gpio'], ops) if ret != 0: @@ -551,6 +562,15 @@ def create_multifpgapci_device(self, dev, ops): return create_ret.append(ret) + def create_mdio_bus(self, bdf, mdio_dev, ops): + for bus in range(int(mdio_dev['dev_attr']['num_virt_ch'], 16)): + cmd = "echo {} > /sys/kernel/pddf/devices/multifpgapci/{}/mdio/new_mdio_bus".format(bus, bdf) + ret = self.runcmd(cmd) + if ret != 0: + return ret + + return 0 + def create_multifpgapci_gpio_device(self, bdf, gpio_dev, ops): create_ret = [] ret = 0