[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-Id: <1529292733-13243-1-git-send-email-liwei1412@163.com>
Date: Mon, 18 Jun 2018 11:32:12 +0800
From: Wei Li <liwei1412@....com>
To: andrew@...n.ch, f.fainelli@...il.com
Cc: netdev@...r.kernel.org
Subject: [PATCH 1/2] eth phy: add mdio bus char device interface
Add the char device interface of mdio bus, like what i2c-dev or spidev do.
They make it possible for user-space programs to access the bus directly.
Signed-off-by: Wei Li <liwei1412@....com>
---
drivers/net/phy/Kconfig | 10 ++
drivers/net/phy/Makefile | 1 +
drivers/net/phy/mdio-dev.c | 376 +++++++++++++++++++++++++++++++++++++++++++++
include/linux/mdio-dev.h | 28 ++++
4 files changed, 415 insertions(+)
create mode 100644 drivers/net/phy/mdio-dev.c
create mode 100644 include/linux/mdio-dev.h
diff --git a/drivers/net/phy/Kconfig b/drivers/net/phy/Kconfig
index bdfbabb..cd688c5 100644
--- a/drivers/net/phy/Kconfig
+++ b/drivers/net/phy/Kconfig
@@ -158,6 +158,16 @@ config MDIO_XGENE
This module provides a driver for the MDIO busses found in the
APM X-Gene SoC's.
+config MDIO_CHARDEV
+ tristate "MDIO device interface"
+ help
+ Say Y here to use mdio-* device files, usually found in the /dev
+ directory on your system. They make it possible to have user-space
+ programs use the MDIO bus.
+
+ This support is also available as a module. If so, the module
+ will be called mdio-dev.
+
endif
config PHYLINK
diff --git a/drivers/net/phy/Makefile b/drivers/net/phy/Makefile
index 01acbcb..a0566f8 100644
--- a/drivers/net/phy/Makefile
+++ b/drivers/net/phy/Makefile
@@ -38,6 +38,7 @@ obj-$(CONFIG_MDIO_OCTEON) += mdio-octeon.o
obj-$(CONFIG_MDIO_SUN4I) += mdio-sun4i.o
obj-$(CONFIG_MDIO_THUNDER) += mdio-thunder.o
obj-$(CONFIG_MDIO_XGENE) += mdio-xgene.o
+obj-$(CONFIG_MDIO_CHARDEV) += mdio-dev.o
obj-$(CONFIG_SFP) += sfp.o
sfp-obj-$(CONFIG_SFP) += sfp-bus.o
diff --git a/drivers/net/phy/mdio-dev.c b/drivers/net/phy/mdio-dev.c
new file mode 100644
index 0000000..61487d2
--- /dev/null
+++ b/drivers/net/phy/mdio-dev.c
@@ -0,0 +1,376 @@
+/*
+ * mdio-dev.c - mdio-bus driver, char device interface
+ *
+ * Copyright (C) 2018 Wei Li <liwei1412@....com>
+ *
+ * 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.
+ */
+
+#include <linux/cdev.h>
+#include <linux/device.h>
+#include <linux/fs.h>
+#include <linux/mdio-dev.h>
+#include <linux/mdio.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/notifier.h>
+#include <linux/slab.h>
+#include <linux/phy.h>
+
+/*
+ * An mdio_dev represents a mii_bus. It's coupled
+ * with a character special file which is accessed by user mode drivers.
+ *
+ * The list of mdio_dev structures is parallel to the mii_bus lists
+ * maintained by the driver model, and is updated using bus notifications.
+ */
+struct mdio_dev {
+ struct list_head list;
+ struct mii_bus *bus;
+ int nr;
+ struct device *dev;
+ struct cdev cdev;
+};
+
+static DEFINE_MUTEX(mdio_dev_lock);
+static DEFINE_IDR(mdio_dev_idr);
+static LIST_HEAD(mdio_dev_list);
+static DEFINE_SPINLOCK(mdio_dev_list_lock);
+
+static struct mdio_dev *mdio_dev_get_by_bus(struct mii_bus *bus)
+{
+ struct mdio_dev *mdio_dev;
+
+ if (!bus)
+ return NULL;
+
+ spin_lock(&mdio_dev_list_lock);
+ list_for_each_entry(mdio_dev, &mdio_dev_list, list) {
+ if (mdio_dev->bus == bus)
+ goto found;
+ }
+ mdio_dev = NULL;
+found:
+ spin_unlock(&mdio_dev_list_lock);
+ return mdio_dev;
+}
+
+static int alloc_mdio_dev_id(struct mdio_dev *mdio_dev)
+{
+ int id;
+
+ mutex_lock(&mdio_dev_lock);
+ id = idr_alloc(&mdio_dev_idr, mdio_dev, 0, MDIO_MINORS, GFP_KERNEL);
+ mutex_unlock(&mdio_dev_lock);
+ if (WARN(id < 0, "couldn't get idr"))
+ return id == -ENOSPC ? -EBUSY : id;
+
+ mdio_dev->nr = id;
+ return 0;
+}
+
+static void free_mdio_dev_id(struct mdio_dev *mdio_dev)
+{
+ mutex_lock(&mdio_dev_lock);
+ idr_remove(&mdio_dev_idr, mdio_dev->nr);
+ mutex_unlock(&mdio_dev_lock);
+ mdio_dev->nr = -1;
+}
+
+static struct mii_bus *mdiodev_get_bus(int nr)
+{
+ struct mdio_dev *found;
+
+ mutex_lock(&mdio_dev_lock);
+ found = idr_find(&mdio_dev_idr, nr);
+ if (!found)
+ goto exit;
+
+ if (try_module_get(found->bus->owner))
+ get_device(&found->bus->dev);
+ else
+ found = NULL;
+
+exit:
+ mutex_unlock(&mdio_dev_lock);
+ return found ? found->bus : NULL;
+}
+
+static void mdiodev_put_bus(struct mii_bus *bus)
+{
+ if (!bus)
+ return;
+
+ put_device(&bus->dev);
+ module_put(bus->owner);
+}
+
+static struct mdio_dev *get_free_mdio_dev(struct mii_bus *bus)
+{
+ struct mdio_dev *mdio_dev;
+
+ mdio_dev = kzalloc(sizeof(*mdio_dev), GFP_KERNEL);
+ if (!mdio_dev)
+ return ERR_PTR(-ENOMEM);
+ mdio_dev->bus = bus;
+
+ if (alloc_mdio_dev_id(mdio_dev)) {
+ printk(KERN_ERR "mdio-dev: Out of device minors\n");
+ kfree(mdio_dev);
+ return ERR_PTR(-ENODEV);
+ }
+
+ spin_lock(&mdio_dev_list_lock);
+ list_add_tail(&mdio_dev->list, &mdio_dev_list);
+ spin_unlock(&mdio_dev_list_lock);
+ return mdio_dev;
+}
+
+static void put_mdio_dev(struct mdio_dev *mdio_dev)
+{
+ spin_lock(&mdio_dev_list_lock);
+ list_del(&mdio_dev->list);
+ spin_unlock(&mdio_dev_list_lock);
+ free_mdio_dev_id(mdio_dev);
+ kfree(mdio_dev);
+}
+
+static ssize_t name_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct mii_bus *bus = mdiodev_get_bus(MINOR(dev->devt));
+
+ if (!bus)
+ return -ENODEV;
+ return sprintf(buf, "%s\n", bus->name);
+}
+static DEVICE_ATTR_RO(name);
+
+static struct attribute *mdio_attrs[] = {
+ &dev_attr_name.attr,
+ NULL,
+};
+ATTRIBUTE_GROUPS(mdio);
+
+/*-------------------------------------------------------------------------*/
+
+static long mdiodev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct mii_bus *bus = file->private_data;
+ struct mii_ioctl_data data;
+ int res;
+
+ dev_dbg(&bus->dev, "ioctl, cmd=0x%02x, arg=0x%02lx\n", cmd, arg);
+
+ switch (cmd) {
+ case SIOCSMIIREG:
+ if (copy_from_user(&data,
+ (struct mii_ioctl_data __user *)arg, sizeof(data)))
+ return -EFAULT;
+
+ res = mdiobus_write(bus, data.phy_id, data.reg_num, data.val_in);
+ if (res < 0)
+ return -EIO;
+ return 0;
+
+ case SIOCGMIIREG:
+ if (copy_from_user(&data,
+ (struct mii_ioctl_data __user *)arg, sizeof(data)))
+ return -EFAULT;
+
+ res = mdiobus_read(bus, data.phy_id, data.reg_num);
+ if (res < 0)
+ return -EIO;
+
+ data.val_out = res;
+ if (copy_to_user((struct mii_ioctl_data __user *)arg,
+ &data, sizeof(data)))
+ return -EFAULT;
+ return 0;
+
+ default:
+ return -ENOTTY;
+ }
+ return 0;
+}
+
+static int mdiodev_open(struct inode *inode, struct file *file)
+{
+ unsigned int minor = iminor(inode);
+ struct mii_bus *bus;
+
+ bus = mdiodev_get_bus(minor);
+ if (!bus)
+ return -ENODEV;
+
+ file->private_data = bus;
+
+ return 0;
+}
+
+static int mdiodev_release(struct inode *inode, struct file *file)
+{
+ struct mii_bus *bus = file->private_data;
+
+ mdiodev_put_bus(bus);
+ file->private_data = NULL;
+
+ return 0;
+}
+
+static const struct file_operations mdiodev_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+ .unlocked_ioctl = mdiodev_ioctl,
+ .open = mdiodev_open,
+ .release = mdiodev_release,
+};
+
+/*-------------------------------------------------------------------------*/
+
+static int mdio_major;
+static struct class *mdio_dev_class;
+
+static int mdiodev_attach_bus(struct device *dev, void *dummy)
+{
+ struct mii_bus *bus;
+ struct mdio_dev *mdio_dev;
+ int res;
+
+ if (dev->class != &mdio_bus_class)
+ return 0;
+ bus = to_mii_bus(dev);
+
+ mdio_dev = get_free_mdio_dev(bus);
+ if (IS_ERR(mdio_dev))
+ return PTR_ERR(mdio_dev);
+
+ cdev_init(&mdio_dev->cdev, &mdiodev_fops);
+ mdio_dev->cdev.owner = THIS_MODULE;
+ res = cdev_add(&mdio_dev->cdev, MKDEV(mdio_major, mdio_dev->nr), 1);
+ if (res)
+ goto error_cdev;
+
+ /* register this mdio device with the driver core */
+ mdio_dev->dev = device_create(mdio_dev_class, &bus->dev,
+ MKDEV(mdio_major, mdio_dev->nr), NULL,
+ "mdio-%d", mdio_dev->nr);
+ if (IS_ERR(mdio_dev->dev)) {
+ res = PTR_ERR(mdio_dev->dev);
+ goto error;
+ }
+
+ pr_debug("mdio-dev: bus [%s] registered as minor %d\n",
+ bus->name, mdio_dev->nr);
+ return 0;
+error:
+ cdev_del(&mdio_dev->cdev);
+error_cdev:
+ put_mdio_dev(mdio_dev);
+ return res;
+}
+
+static int mdiodev_detach_bus(struct device *dev, void *dummy)
+{
+ struct mii_bus *bus;
+ struct mdio_dev *mdio_dev;
+
+ if (dev->class != &mdio_bus_class)
+ return 0;
+ bus = to_mii_bus(dev);
+
+ mdio_dev = mdio_dev_get_by_bus(bus);
+ if (!mdio_dev) /* attach_bus must have failed */
+ return 0;
+
+ cdev_del(&mdio_dev->cdev);
+ device_destroy(mdio_dev_class, MKDEV(mdio_major, mdio_dev->nr));
+ put_mdio_dev(mdio_dev);
+
+ pr_debug("mdio-dev: bus [%s] unregistered\n", bus->name);
+ return 0;
+}
+
+static int mdiodev_notifier_call(struct notifier_block *nb, unsigned long action,
+ void *data)
+{
+ struct device *dev = data;
+
+ switch (action) {
+ case BUS_NOTIFY_ADD_DEVICE:
+ return mdiodev_attach_bus(dev, NULL);
+ case BUS_NOTIFY_DEL_DEVICE:
+ return mdiodev_detach_bus(dev, NULL);
+ }
+
+ return 0;
+}
+
+static struct notifier_block mdiodev_notifier = {
+ .notifier_call = mdiodev_notifier_call,
+};
+
+/*-------------------------------------------------------------------------*/
+
+static int __init mdio_dev_init(void)
+{
+ int res;
+ dev_t devid;
+
+ printk(KERN_INFO "mdio /dev entries driver\n");
+
+ res = alloc_chrdev_region(&devid, 0, MDIO_MINORS, "mdio");
+ if (res)
+ goto out;
+
+ mdio_major = MAJOR(devid);
+ mdio_dev_class = class_create(THIS_MODULE, "mdio-dev");
+ if (IS_ERR(mdio_dev_class)) {
+ res = PTR_ERR(mdio_dev_class);
+ goto out_unreg_chrdev;
+ }
+ mdio_dev_class->dev_groups = mdio_groups;
+
+ /* Keep track of buses which will be added or removed later */
+ res = mdiobus_register_notifier(&mdiodev_notifier);
+ if (res)
+ goto out_unreg_class;
+
+ /* Bind to already existing buses right away */
+ class_for_each_device(&mdio_bus_class, NULL, NULL, mdiodev_attach_bus);
+
+ return 0;
+
+out_unreg_class:
+ class_destroy(mdio_dev_class);
+out_unreg_chrdev:
+ unregister_chrdev_region(MKDEV(mdio_major, 0), MDIO_MINORS);
+out:
+ printk(KERN_ERR "%s: Driver Initialisation failed\n", __FILE__);
+ return res;
+}
+
+static void __exit mdio_dev_exit(void)
+{
+ mdiobus_unregister_notifier(&mdiodev_notifier);
+ class_for_each_device(&mdio_bus_class, NULL, NULL, mdiodev_detach_bus);
+ class_destroy(mdio_dev_class);
+ unregister_chrdev_region(MKDEV(mdio_major, 0), MDIO_MINORS);
+}
+
+MODULE_AUTHOR("Wei Li <liwei1412@....com>");
+MODULE_DESCRIPTION("MDIO /dev entries driver");
+MODULE_LICENSE("GPL");
+
+module_init(mdio_dev_init);
+module_exit(mdio_dev_exit);
diff --git a/include/linux/mdio-dev.h b/include/linux/mdio-dev.h
new file mode 100644
index 0000000..cc4ed36
--- /dev/null
+++ b/include/linux/mdio-dev.h
@@ -0,0 +1,28 @@
+/*
+ * mdio-dev.h - mdio-bus driver, char device interface
+ *
+ * Copyright (C) 2018 Wei Li <liwei1412@....com>
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA.
+ */
+#ifndef _LINUX_MDIO_DEV_H
+#define _LINUX_MDIO_DEV_H
+
+#include <uapi/linux/mii.h>
+
+#define MDIO_MINORS MINORMASK
+
+#endif /* _LINUX_MDIO_DEV_H */
--
2.1.4
Powered by blists - more mailing lists