From a41d4af2ed4b02edca9d7f69955893d407274dc2 Mon Sep 17 00:00:00 2001 From: Shreyansh Jain Date: Wed, 6 Jun 2018 14:19:34 +0530 Subject: [PATCH] staging: fsl_ppfe: add support for a char dev for link status Read and IOCTL support is added. Application would need to open, read/ioctl the /dev/pfe_us_cdev device. select is pending as it requires a wait_queue. Signed-off-by: Shreyansh Jain Signed-off-by: Calvin Johnson --- drivers/staging/fsl_ppfe/Makefile | 3 +- drivers/staging/fsl_ppfe/pfe_cdev.c | 207 ++++++++++++++++++++++++++++++++++++ drivers/staging/fsl_ppfe/pfe_cdev.h | 52 +++++++++ drivers/staging/fsl_ppfe/pfe_eth.c | 14 +++ drivers/staging/fsl_ppfe/pfe_mod.c | 14 +++ 5 files changed, 289 insertions(+), 1 deletion(-) create mode 100644 drivers/staging/fsl_ppfe/pfe_cdev.c create mode 100644 drivers/staging/fsl_ppfe/pfe_cdev.h --- a/drivers/staging/fsl_ppfe/Makefile +++ b/drivers/staging/fsl_ppfe/Makefile @@ -16,4 +16,5 @@ pfe-y += pfe_mod.o \ pfe_sysfs.o \ pfe_debugfs.o \ pfe_ls1012a_platform.o \ - pfe_hal.o + pfe_hal.o \ + pfe_cdev.o --- /dev/null +++ b/drivers/staging/fsl_ppfe/pfe_cdev.c @@ -0,0 +1,207 @@ +/* + * Copyright 2018 NXP + * + * 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, see . + */ + +/* @pfe_cdev.c. + * Dummy device representing the PFE US in userspace. + * - used for interacting with the kernel layer for link status + */ + +#include "pfe_cdev.h" + +static int pfe_majno; +static struct class *pfe_char_class; +static struct device *pfe_char_dev; + +struct pfe_shared_info link_states[PFE_CDEV_ETH_COUNT]; + +static int pfe_cdev_open(struct inode *inp, struct file *fp) +{ + pr_debug("PFE CDEV device opened.\n"); + return 0; +} + +static ssize_t pfe_cdev_read(struct file *fp, char *buf, + size_t len, loff_t *off) +{ + int ret = 0; + + pr_info("PFE CDEV attempt copying (%lu) size of user.\n", + sizeof(link_states)); + + pr_debug("Dump link_state on screen before copy_to_user\n"); + for (; ret < PFE_CDEV_ETH_COUNT; ret++) { + pr_debug("%u %u", link_states[ret].phy_id, + link_states[ret].state); + pr_debug("\n"); + } + + /* Copy to user the value in buffer sized len */ + ret = copy_to_user(buf, &link_states, sizeof(link_states)); + if (ret != 0) { + pr_err("Failed to send (%d)bytes of (%lu) requested.\n", + ret, len); + return -EFAULT; + } + + /* offset set back to 0 as there is contextual reading offset */ + *off = 0; + pr_debug("Read of (%lu) bytes performed.\n", sizeof(link_states)); + + return sizeof(link_states); +} + +/** + * This function is for getting some commands from user through non-IOCTL + * channel. It can used to configure the device. + * TODO: To be filled in future, if require duplex communication with user + * space. + */ +static ssize_t pfe_cdev_write(struct file *fp, const char *buf, + size_t len, loff_t *off) +{ + pr_info("PFE CDEV Write operation not supported!\n"); + + return -EFAULT; +} + +static int pfe_cdev_release(struct inode *inp, struct file *fp) +{ + pr_info("PFE_CDEV: Device successfully closed\n"); + return 0; +} + +static long pfe_cdev_ioctl(struct file *fp, unsigned int cmd, + unsigned long arg) +{ + int ret = -EFAULT; + int __user *argp = (int __user *)arg; + + pr_debug("PFE CDEV IOCTL Called with cmd=(%u)\n", cmd); + + switch (cmd) { + case PFE_CDEV_ETH0_STATE_GET: + /* Return an unsigned int (link state) for ETH0 */ + *argp = link_states[0].state; + pr_debug("Returning state=%d for ETH0\n", *argp); + ret = 0; + break; + case PFE_CDEV_ETH1_STATE_GET: + /* Return an unsigned int (link state) for ETH0 */ + *argp = link_states[1].state; + pr_debug("Returning state=%d for ETH1\n", *argp); + ret = 0; + break; + default: + pr_info("Unsupport cmd (%d) for PFE CDEV.\n", cmd); + break; + }; + + return ret; +} + +static unsigned int pfe_cdev_poll(struct file *fp, + struct poll_table_struct *wait) +{ + pr_info("PFE CDEV poll method not supported\n"); + return 0; +} + +static const struct file_operations pfe_cdev_fops = { + .open = pfe_cdev_open, + .read = pfe_cdev_read, + .write = pfe_cdev_write, + .release = pfe_cdev_release, + .unlocked_ioctl = pfe_cdev_ioctl, + .poll = pfe_cdev_poll, +}; + +int pfe_cdev_init(void) +{ + int ret; + + pr_debug("PFE CDEV initialization begin\n"); + + /* Register the major number for the device */ + pfe_majno = register_chrdev(0, PFE_CDEV_NAME, &pfe_cdev_fops); + if (pfe_majno < 0) { + pr_err("Unable to register PFE CDEV. PFE CDEV not available\n"); + ret = pfe_majno; + goto cleanup; + } + + pr_debug("PFE CDEV assigned major number: %d\n", pfe_majno); + + /* Register the class for the device */ + pfe_char_class = class_create(THIS_MODULE, PFE_CLASS_NAME); + if (IS_ERR(pfe_char_class)) { + pr_err( + "Failed to init class for PFE CDEV. PFE CDEV not available.\n"); + goto cleanup; + } + + pr_debug("PFE CDEV Class created successfully.\n"); + + /* Create the device without any parent and without any callback data */ + pfe_char_dev = device_create(pfe_char_class, NULL, + MKDEV(pfe_majno, 0), NULL, + PFE_CDEV_NAME); + if (IS_ERR(pfe_char_dev)) { + pr_err("Unable to PFE CDEV device. PFE CDEV not available.\n"); + ret = PTR_ERR(pfe_char_dev); + goto cleanup; + } + + /* Information structure being shared with the userspace */ + memset(link_states, 0, sizeof(struct pfe_shared_info) * + PFE_CDEV_ETH_COUNT); + + pr_info("PFE CDEV created: %s\n", PFE_CDEV_NAME); + + ret = 0; + return ret; + +cleanup: + if (!IS_ERR(pfe_char_class)) + class_destroy(pfe_char_class); + + if (pfe_majno > 0) + unregister_chrdev(pfe_majno, PFE_CDEV_NAME); + + ret = -EFAULT; + return ret; +} + +void pfe_cdev_exit(void) +{ + if (!IS_ERR(pfe_char_dev)) + device_destroy(pfe_char_class, MKDEV(pfe_majno, 0)); + + if (!IS_ERR(pfe_char_class)) { + class_unregister(pfe_char_class); + class_destroy(pfe_char_class); + } + + if (pfe_majno > 0) + unregister_chrdev(pfe_majno, PFE_CDEV_NAME); + + /* reset the variables */ + pfe_majno = 0; + pfe_char_class = NULL; + pfe_char_dev = NULL; + + pr_info("PFE CDEV Removed.\n"); +} --- /dev/null +++ b/drivers/staging/fsl_ppfe/pfe_cdev.h @@ -0,0 +1,52 @@ +/* + * Copyright 2018 NXP + * + * 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, see . + */ + +#ifndef _PFE_CDEV_H_ +#define _PFE_CDEV_H_ + +#include +#include +#include +#include +#include +#include +#include + +#define PFE_CDEV_NAME "pfe_us_cdev" +#define PFE_CLASS_NAME "ppfe_us" + +/* Extracted from ls1012a_pfe_platform_data, there are 3 interfaces which are + * supported by PFE driver. Should be updated if number of eth devices are + * changed. + */ +#define PFE_CDEV_ETH_COUNT 3 + +struct pfe_shared_info { + uint32_t phy_id; /* Link phy ID */ + uint8_t state; /* Has either 0 or 1 */ +}; + +extern struct pfe_shared_info link_states[PFE_CDEV_ETH_COUNT]; + +/* IOCTL Commands */ +#define PFE_CDEV_ETH0_STATE_GET 0 +#define PFE_CDEV_ETH1_STATE_GET 1 + +int pfe_cdev_init(void); +void pfe_cdev_exit(void); + +#endif /* _PFE_CDEV_H_ */ --- a/drivers/staging/fsl_ppfe/pfe_eth.c +++ b/drivers/staging/fsl_ppfe/pfe_eth.c @@ -55,6 +55,7 @@ #include "pfe_mod.h" #include "pfe_eth.h" +#include "pfe_cdev.h" #define LS1012A_REV_1_0 0x87040010 @@ -1160,6 +1161,19 @@ static void pfe_eth_adjust_link(struct n phy_print_status(phydev); spin_unlock_irqrestore(&priv->lock, flags); + + /* Now, dump the details to the cdev. + * XXX: Locking would be required? (uniprocess arch) + * Or, maybe move it in spinlock above + */ + if (us && priv->einfo->gem_id < PFE_CDEV_ETH_COUNT) { + pr_debug("Changing link state from (%u) to (%u) for ID=(%u)\n", + link_states[priv->einfo->gem_id].state, + phydev->link, + priv->einfo->gem_id); + link_states[priv->einfo->gem_id].phy_id = priv->einfo->gem_id; + link_states[priv->einfo->gem_id].state = phydev->link; + } } /* pfe_phy_exit --- a/drivers/staging/fsl_ppfe/pfe_mod.c +++ b/drivers/staging/fsl_ppfe/pfe_mod.c @@ -18,6 +18,7 @@ #include #include "pfe_mod.h" +#include "pfe_cdev.h" unsigned int us; module_param(us, uint, 0444); @@ -92,8 +93,18 @@ firmware_init: if (rc < 0) goto err_debugfs; + if (us) { + /* Creating a character device */ + rc = pfe_cdev_init(); + if (rc < 0) + goto err_cdev; + } + return 0; +err_cdev: + pfe_debugfs_exit(pfe); + err_debugfs: pfe_sysfs_exit(pfe); @@ -129,6 +140,9 @@ int pfe_remove(struct pfe *pfe) { pr_info("%s\n", __func__); + if (us) + pfe_cdev_exit(); + pfe_debugfs_exit(pfe); pfe_sysfs_exit(pfe);