From 92a33e4847ff6d0a682397538fd7901910307490 Mon Sep 17 00:00:00 2001 From: tianshangfei <31125751+tianshangfei@users.noreply.github.com> Date: Wed, 14 Dec 2022 22:29:45 +0800 Subject: [PATCH] The Transceiver driver framework module complies with s3ip sysfs specification (#12889) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Why I did it Provide a transceiver driver framework that complies with s3ip sysfs specification How I did it 1、 The framework module provides register and unregister interface and implementation. 2、 The framework will help you create the sysfs node How to verify it A demo driver base on this framework will display the sysfs node wich conform to the s3ip sysfs specification --- .../include/transceiver_sysfs.h | 26 + .../s3ip_sysfs_frame/transceiver_sysfs.c | 618 ++++++++++++++++++ 2 files changed, 644 insertions(+) create mode 100644 platform/s3ip-sysfs/s3ip_sysfs_frame/include/transceiver_sysfs.h create mode 100644 platform/s3ip-sysfs/s3ip_sysfs_frame/transceiver_sysfs.c diff --git a/platform/s3ip-sysfs/s3ip_sysfs_frame/include/transceiver_sysfs.h b/platform/s3ip-sysfs/s3ip_sysfs_frame/include/transceiver_sysfs.h new file mode 100644 index 0000000000..7e8634c1ef --- /dev/null +++ b/platform/s3ip-sysfs/s3ip_sysfs_frame/include/transceiver_sysfs.h @@ -0,0 +1,26 @@ +#ifndef _TRANSCEIVER_SYSFS_H_ +#define _TRANSCEIVER_SYSFS_H_ + +struct s3ip_sysfs_transceiver_drivers_s { + int (*get_eth_number)(void); + ssize_t (*get_transceiver_power_on_status)(char *buf, size_t count); + int (*set_transceiver_power_on_status)(int status); + ssize_t (*get_eth_power_on_status)(unsigned int eth_index, char *buf, size_t count); + int (*set_eth_power_on_status)(unsigned int eth_index, int status); + ssize_t (*get_eth_tx_fault_status)(unsigned int eth_index, char *buf, size_t count); + ssize_t (*get_eth_tx_disable_status)(unsigned int eth_index, char *buf, size_t count); + int (*set_eth_tx_disable_status)(unsigned int eth_index, int status); + ssize_t (*get_eth_present_status)(unsigned int eth_index, char *buf, size_t count); + ssize_t (*get_eth_rx_los_status)(unsigned int eth_index, char *buf, size_t count); + ssize_t (*get_eth_reset_status)(unsigned int eth_index, char *buf, size_t count); + int (*set_eth_reset_status)(unsigned int eth_index, int status); + ssize_t (*get_eth_low_power_mode_status)(unsigned int eth_index, char *buf, size_t count); + ssize_t (*get_eth_interrupt_status)(unsigned int eth_index, char *buf, size_t count); + int (*get_eth_eeprom_size)(unsigned int eth_index); + ssize_t (*read_eth_eeprom_data)(unsigned int eth_index, char *buf, loff_t offset, size_t count); + ssize_t (*write_eth_eeprom_data)(unsigned int eth_index, char *buf, loff_t offset, size_t count); +}; + +extern int s3ip_sysfs_sff_drivers_register(struct s3ip_sysfs_transceiver_drivers_s *drv); +extern void s3ip_sysfs_sff_drivers_unregister(void); +#endif /*_TRANSCEIVER_SYSFS_H_ */ diff --git a/platform/s3ip-sysfs/s3ip_sysfs_frame/transceiver_sysfs.c b/platform/s3ip-sysfs/s3ip_sysfs_frame/transceiver_sysfs.c new file mode 100644 index 0000000000..02ad4d7a86 --- /dev/null +++ b/platform/s3ip-sysfs/s3ip_sysfs_frame/transceiver_sysfs.c @@ -0,0 +1,618 @@ +/* + * transceiver_sysfs.c + * + * This module create eth kobjects and attributes in /sys/s3ip/transceiver + * + * History + * [Version] [Date] [Description] + * * v1.0 2021-08-31 S3IP sysfs + */ + +#include + +#include "switch.h" +#include "transceiver_sysfs.h" + +static int g_sff_loglevel = 0; + +#define SFF_INFO(fmt, args...) do { \ + if (g_sff_loglevel & INFO) { \ + printk(KERN_INFO "[SFF_SYSFS][func:%s line:%d]\n"fmt, __func__, __LINE__, ## args); \ + } \ +} while (0) + +#define SFF_ERR(fmt, args...) do { \ + if (g_sff_loglevel & ERR) { \ + printk(KERN_ERR "[SFF_SYSFS][func:%s line:%d]\n"fmt, __func__, __LINE__, ## args); \ + } \ +} while (0) + +#define SFF_DBG(fmt, args...) do { \ + if (g_sff_loglevel & DBG) { \ + printk(KERN_DEBUG "[SFF_SYSFS][func:%s line:%d]\n"fmt, __func__, __LINE__, ## args); \ + } \ +} while (0) + +struct sff_obj_s { + struct switch_obj *sff_obj; + struct bin_attribute bin; + int sff_creat_bin_flag; +}; + +struct sff_s { + unsigned int sff_number; + struct sff_obj_s *sff; +}; + +static struct sff_s g_sff; +static struct switch_obj *g_sff_obj = NULL; +static struct s3ip_sysfs_transceiver_drivers_s *g_sff_drv = NULL; + +static ssize_t transceiver_power_on_show(struct switch_obj *obj, struct switch_attribute *attr, + char *buf) +{ + int ret; + + check_p(g_sff_drv); + check_p(g_sff_drv->get_transceiver_power_on_status); + + ret = g_sff_drv->get_transceiver_power_on_status(buf, PAGE_SIZE); + if (ret < 0) { + SFF_ERR("get transceiver power on status failed, ret: %d\n", ret); + return (ssize_t)snprintf(buf, PAGE_SIZE, "%s\n", SYSFS_DEV_ERROR); + } + return ret; +} + +static ssize_t transceiver_power_on_store(struct switch_obj *obj, struct switch_attribute *attr, + const char* buf, size_t count) +{ + int ret, value; + + check_p(g_sff_drv); + check_p(g_sff_drv->set_transceiver_power_on_status); + + sscanf(buf, "%d", &value); + if (value < 0 || value > 1) { + SFF_ERR("invalid value: %d, can't set transceiver power on status.\n", value); + return -EINVAL; + } + + ret = g_sff_drv->set_transceiver_power_on_status(value); + if (ret < 0) { + SFF_ERR("set transceiver power on status %d failed, ret: %d\n", value, ret); + return -EIO; + } + return count; +} + +static ssize_t eth_power_on_show(struct switch_obj *obj, struct switch_attribute *attr, char *buf) +{ + unsigned int eth_index; + int ret; + + check_p(g_sff_drv); + check_p(g_sff_drv->get_eth_power_on_status); + + eth_index = obj->index; + SFF_DBG("eth index: %u\n", eth_index); + ret = g_sff_drv->get_eth_power_on_status(eth_index, buf, PAGE_SIZE); + if (ret < 0) { + SFF_ERR("get eth%u power on status failed, ret: %d\n", eth_index, ret); + return (ssize_t)snprintf(buf, PAGE_SIZE, "%s\n", SYSFS_DEV_ERROR); + } + return ret; +} + +static ssize_t eth_power_on_store(struct switch_obj *obj, struct switch_attribute *attr, + const char* buf, size_t count) +{ + unsigned int eth_index; + int ret, value; + + check_p(g_sff_drv); + check_p(g_sff_drv->set_eth_power_on_status); + + sscanf(buf, "%d", &value); + eth_index = obj->index; + if (value < 0 || value > 1) { + SFF_ERR("invalid value: %d, can't set eth%u power on status.\n", value, eth_index); + return -EINVAL; + } + + ret = g_sff_drv->set_eth_power_on_status(eth_index, value); + if (ret < 0) { + SFF_ERR("set eth%u power on status %d failed, ret: %d\n", eth_index, value, ret); + return -EIO; + } + SFF_DBG("set eth%u power on status %d success\n", eth_index, value); + return count; +} + +static ssize_t eth_tx_fault_show(struct switch_obj *obj, struct switch_attribute *attr, char *buf) +{ + unsigned int eth_index; + int ret; + + check_p(g_sff_drv); + check_p(g_sff_drv->get_eth_tx_fault_status); + + eth_index = obj->index; + SFF_DBG("eth index: %u\n", eth_index); + ret = g_sff_drv->get_eth_tx_fault_status(eth_index, buf, PAGE_SIZE); + if (ret < 0) { + SFF_ERR("get eth%u tx fault status failed, ret: %d\n", eth_index, ret); + return (ssize_t)snprintf(buf, PAGE_SIZE, "%s\n", SYSFS_DEV_ERROR); + } + return ret; +} + +static ssize_t eth_tx_disable_show(struct switch_obj *obj, struct switch_attribute *attr, + char *buf) +{ + unsigned int eth_index; + int ret; + + check_p(g_sff_drv); + check_p(g_sff_drv->get_eth_tx_disable_status); + + eth_index = obj->index; + SFF_DBG("eth index: %u\n", eth_index); + ret = g_sff_drv->get_eth_tx_disable_status(eth_index, buf, PAGE_SIZE); + if (ret < 0) { + SFF_ERR("get eth%u tx disable status failed, ret: %d\n", eth_index, ret); + return (ssize_t)snprintf(buf, PAGE_SIZE, "%s\n", SYSFS_DEV_ERROR); + } + return ret; +} + +static ssize_t eth_tx_disable_store(struct switch_obj *obj, struct switch_attribute *attr, + const char* buf, size_t count) +{ + unsigned int eth_index; + int ret, value; + + check_p(g_sff_drv); + check_p(g_sff_drv->set_eth_tx_disable_status); + + sscanf(buf, "%d", &value); + eth_index = obj->index; + if (value < 0 || value > 1) { + SFF_ERR("invalid value: %d, can't set eth%u tx disable status.\n", value, eth_index); + return -EINVAL; + } + + ret = g_sff_drv->set_eth_tx_disable_status(eth_index, value); + if (ret < 0) { + SFF_ERR("set eth%u tx disable status %d failed, ret: %d\n", eth_index, value, ret); + return -EIO; + } + SFF_DBG("set eth%u tx disable status %d success\n", eth_index, value); + return count; +} + +static ssize_t eth_present_show(struct switch_obj *obj, struct switch_attribute *attr, char *buf) +{ + unsigned int eth_index; + int ret; + + check_p(g_sff_drv); + check_p(g_sff_drv->get_eth_present_status); + + eth_index = obj->index; + SFF_DBG("eth index: %u\n", eth_index); + ret = g_sff_drv->get_eth_present_status(eth_index, buf, PAGE_SIZE); + if (ret < 0) { + SFF_ERR("get eth%u present status failed, ret: %d\n", eth_index, ret); + return (ssize_t)snprintf(buf, PAGE_SIZE, "%s\n", SYSFS_DEV_ERROR); + } + return ret; +} + +static ssize_t eth_rx_los_show(struct switch_obj *obj, struct switch_attribute *attr, char *buf) +{ + unsigned int eth_index; + int ret; + + check_p(g_sff_drv); + check_p(g_sff_drv->get_eth_rx_los_status); + + eth_index = obj->index; + SFF_DBG("eth index: %u\n", eth_index); + ret = g_sff_drv->get_eth_rx_los_status(eth_index, buf, PAGE_SIZE); + if (ret < 0) { + SFF_ERR("get eth%u rx los status failed, ret: %d\n", eth_index, ret); + return (ssize_t)snprintf(buf, PAGE_SIZE, "%s\n", SYSFS_DEV_ERROR); + } + return ret; +} + +static ssize_t eth_reset_show(struct switch_obj *obj, struct switch_attribute *attr, char *buf) +{ + unsigned int eth_index; + int ret; + + check_p(g_sff_drv); + check_p(g_sff_drv->get_eth_reset_status); + + eth_index = obj->index; + SFF_DBG("eth index: %u\n", eth_index); + ret = g_sff_drv->get_eth_reset_status(eth_index, buf, PAGE_SIZE); + if (ret < 0) { + SFF_ERR("get eth%u reset status failed, ret: %d\n", eth_index, ret); + return (ssize_t)snprintf(buf, PAGE_SIZE, "%s\n", SYSFS_DEV_ERROR); + } + return ret; +} + +static ssize_t eth_reset_store(struct switch_obj *obj, struct switch_attribute *attr, + const char* buf, size_t count) +{ + unsigned int eth_index; + int ret, value; + + check_p(g_sff_drv); + check_p(g_sff_drv->set_eth_reset_status); + + sscanf(buf, "%d", &value); + eth_index = obj->index; + if (value < 0 || value > 1) { + SFF_ERR("invalid value: %d, can't set eth%u reset status.\n", value, eth_index); + return -EINVAL; + } + + ret = g_sff_drv->set_eth_reset_status(eth_index, value); + if (ret < 0) { + SFF_ERR("set eth%u reset status %d failed, ret: %d\n", eth_index, value, ret); + return -EIO; + } + SFF_DBG("set eth%u reset status %d success\n", eth_index, value); + return count; +} + +static ssize_t eth_low_power_mode_show(struct switch_obj *obj, struct switch_attribute *attr, char *buf) +{ + unsigned int eth_index; + int ret; + + check_p(g_sff_drv); + check_p(g_sff_drv->get_eth_low_power_mode_status); + + eth_index = obj->index; + SFF_DBG("eth index: %u\n", eth_index); + ret = g_sff_drv->get_eth_low_power_mode_status(eth_index, buf, PAGE_SIZE); + if (ret < 0) { + SFF_ERR("get eth%u low power mode status failed, ret: %d\n", eth_index, ret); + return (ssize_t)snprintf(buf, PAGE_SIZE, "%s\n", SYSFS_DEV_ERROR); + } + return ret; +} + +static ssize_t eth_interrupt_show(struct switch_obj *obj, struct switch_attribute *attr, char *buf) +{ + unsigned int eth_index; + int ret; + + check_p(g_sff_drv); + check_p(g_sff_drv->get_eth_interrupt_status); + + eth_index = obj->index; + SFF_DBG("eth index: %u\n", eth_index); + ret = g_sff_drv->get_eth_interrupt_status(eth_index, buf, PAGE_SIZE); + if (ret < 0) { + SFF_ERR("get eth%u interrupt status failed, ret: %d\n", eth_index, ret); + return (ssize_t)snprintf(buf, PAGE_SIZE, "%s\n", SYSFS_DEV_ERROR); + } + return ret; +} + +static ssize_t eth_eeprom_read(struct file *filp, struct kobject *kobj, struct bin_attribute *attr, + char *buf, loff_t offset, size_t count) +{ + struct switch_obj *eth_obj; + ssize_t rd_len; + unsigned int eth_index; + + check_p(g_sff_drv); + check_p(g_sff_drv->read_eth_eeprom_data); + + eth_obj = to_switch_obj(kobj); + eth_index = eth_obj->index; + memset(buf, 0, count); + rd_len = g_sff_drv->read_eth_eeprom_data(eth_index, buf, offset, count); + if (rd_len < 0) { + SFF_ERR("read eth%u eeprom data error, offset: 0x%llx, read len: %lu, ret: %ld.\n", + eth_index, offset, count, rd_len); + return -EIO; + } + + SFF_DBG("read eth%u eeprom data success, offset:0x%llx, read len:%lu, really read len:%ld.\n", + eth_index, offset, count, rd_len); + + return rd_len; +} + +static ssize_t eth_eeprom_write(struct file *filp, struct kobject *kobj, struct bin_attribute *attr, + char *buf, loff_t offset, size_t count) +{ + struct switch_obj *eth_obj; + ssize_t wr_len; + unsigned int eth_index; + + check_p(g_sff_drv); + check_p(g_sff_drv->write_eth_eeprom_data); + + eth_obj = to_switch_obj(kobj); + eth_index = eth_obj->index; + wr_len = g_sff_drv->write_eth_eeprom_data(eth_index, buf, offset, count); + if (wr_len < 0) { + SFF_ERR("write eth%u eeprom data error, offset: 0x%llx, read len: %lu, ret: %ld.\n", + eth_index, offset, count, wr_len); + return -EIO; + } + + SFF_DBG("write eth%u eeprom data success, offset:0x%llx, write len:%lu, really write len:%ld.\n", + eth_index, offset, count, wr_len); + + return wr_len; +} + +/************************************eth* signal attrs*******************************************/ +static struct switch_attribute eth_power_on_attr = __ATTR(power_on, S_IRUGO | S_IWUSR, eth_power_on_show, eth_power_on_store); +static struct switch_attribute eth_tx_fault_attr = __ATTR(tx_fault, S_IRUGO, eth_tx_fault_show, NULL); +static struct switch_attribute eth_tx_disable_attr = __ATTR(tx_disable, S_IRUGO | S_IWUSR, eth_tx_disable_show, eth_tx_disable_store); +static struct switch_attribute eth_present_attr = __ATTR(present, S_IRUGO, eth_present_show, NULL); +static struct switch_attribute eth_rx_los_attr = __ATTR(rx_los, S_IRUGO, eth_rx_los_show, NULL); +static struct switch_attribute eth_reset_attr = __ATTR(reset, S_IRUGO | S_IWUSR, eth_reset_show, eth_reset_store); +static struct switch_attribute eth_low_power_mode_attr = __ATTR(low_power_mode, S_IRUGO, eth_low_power_mode_show, NULL); +static struct switch_attribute eth_interrupt_attr = __ATTR(interrupt, S_IRUGO, eth_interrupt_show, NULL); + +static struct attribute *sff_signal_attrs[] = { + ð_power_on_attr.attr, + ð_tx_fault_attr.attr, + ð_tx_disable_attr.attr, + ð_present_attr.attr, + ð_rx_los_attr.attr, + ð_reset_attr.attr, + ð_low_power_mode_attr.attr, + ð_interrupt_attr.attr, + NULL, +}; + +static struct attribute_group sff_signal_attr_group = { + .attrs = sff_signal_attrs, +}; + +/*******************************transceiver dir and attrs*******************************************/ +static struct switch_attribute transceiver_power_on_attr = __ATTR(power_on, S_IRUGO | S_IWUSR, transceiver_power_on_show, transceiver_power_on_store); + +static struct attribute *transceiver_dir_attrs[] = { + &transceiver_power_on_attr.attr, + NULL, +}; + +static struct attribute_group sff_transceiver_attr_group = { + .attrs = transceiver_dir_attrs, +}; + +/* create eth* eeprom attributes */ +static int sff_sub_single_create_eeprom_attrs(unsigned int index) +{ + int ret, eeprom_size; + struct sff_obj_s *curr_sff; + + check_p(g_sff_drv->get_eth_eeprom_size); + eeprom_size = g_sff_drv->get_eth_eeprom_size(index); + if (eeprom_size <= 0) { + SFF_INFO("eth%u, eeprom_size: %d, don't need to creat eeprom attr.\n", + index, eeprom_size); + return 0; + } + + curr_sff = &g_sff.sff[index - 1]; + sysfs_bin_attr_init(&curr_sff->bin); + curr_sff->bin.attr.name = "eeprom"; + curr_sff->bin.attr.mode = 0644; + curr_sff->bin.read = eth_eeprom_read; + curr_sff->bin.write = eth_eeprom_write; + curr_sff->bin.size = eeprom_size; + + ret = sysfs_create_bin_file(&curr_sff->sff_obj->kobj, &curr_sff->bin); + if (ret) { + SFF_ERR("eth%u, create eeprom bin error, ret: %d. \n", index, ret); + return -EBADRQC; + } + + SFF_DBG("eth%u, create bin file success, eeprom size:%d.\n", index, eeprom_size); + curr_sff->sff_creat_bin_flag = 1; + return 0; +} + +static int sff_sub_single_create_kobj(struct kobject *parent, unsigned int index) +{ + struct sff_obj_s *curr_sff; + char sff_dir_name[DIR_NAME_MAX_LEN]; + + curr_sff = &g_sff.sff[index - 1]; + memset(sff_dir_name, 0, sizeof(sff_dir_name)); + snprintf(sff_dir_name, sizeof(sff_dir_name), "eth%d", index); + curr_sff->sff_obj = switch_kobject_create(sff_dir_name, parent); + if (!curr_sff->sff_obj) { + SFF_ERR("create eth%d object error! \n", index); + return -EBADRQC; + } + curr_sff->sff_obj->index = index; + if (sysfs_create_group(&curr_sff->sff_obj->kobj, &sff_signal_attr_group) != 0) { + switch_kobject_delete(&curr_sff->sff_obj); + return -EBADRQC; + } + + SFF_DBG("create eth%d dir and attrs success\n", index); + return 0; +} + +/* remove eth directory and attributes */ +static void sff_sub_single_remove_kobj_and_attrs(unsigned int index) +{ + struct sff_obj_s *curr_sff; + + curr_sff = &g_sff.sff[index - 1]; + if (curr_sff->sff_obj) { + if (curr_sff->sff_creat_bin_flag) { + sysfs_remove_bin_file(&curr_sff->sff_obj->kobj, &curr_sff->bin); + curr_sff->sff_creat_bin_flag = 0; + } + sysfs_remove_group(&curr_sff->sff_obj->kobj, &sff_signal_attr_group); + switch_kobject_delete(&curr_sff->sff_obj); + } + + return; +} + +static int sff_sub_single_create_kobj_and_attrs(struct kobject *parent, unsigned int index) +{ + int ret; + + ret = sff_sub_single_create_kobj(parent, index); + if (ret < 0) { + SFF_ERR("create eth%d dir error.\n", index); + return ret; + } + + sff_sub_single_create_eeprom_attrs(index); + return 0; +} + +static int sff_sub_create_kobj_and_attrs(struct kobject *parent, int sff_num) +{ + unsigned int sff_index, i; + + g_sff.sff = kzalloc(sizeof(struct sff_obj_s) * sff_num, GFP_KERNEL); + if (!g_sff.sff) { + SFF_ERR("kzalloc g_sff.sff error, sff number = %d.\n", sff_num); + return -ENOMEM; + } + + for (sff_index = 1; sff_index <= sff_num; sff_index++) { + if (sff_sub_single_create_kobj_and_attrs(parent, sff_index) != 0 ) { + goto error; + } + } + return 0; +error: + for (i = sff_index; i > 0; i--) { + sff_sub_single_remove_kobj_and_attrs(i); + } + kfree(g_sff.sff); + g_sff.sff = NULL; + return -EBADRQC; +} + +/* create eth directory and attributes */ +static int sff_sub_create(void) +{ + int ret; + + ret = sff_sub_create_kobj_and_attrs(&g_sff_obj->kobj, g_sff.sff_number); + return ret; +} + +/* delete eth directory and attributes */ +static void sff_sub_remove(void) +{ + unsigned int sff_index; + + if (g_sff.sff) { + for (sff_index = g_sff.sff_number; sff_index > 0; sff_index--) { + sff_sub_single_remove_kobj_and_attrs(sff_index); + } + kfree(g_sff.sff); + g_sff.sff = NULL; + } + g_sff.sff_number = 0; + return; +} + +/* create transceiver directory and attributes */ +static int sff_transceiver_create(void) +{ + g_sff_obj = switch_kobject_create("transceiver", NULL); + if (!g_sff_obj) { + SFF_ERR("switch_kobject_create transceiver error!\n"); + return -ENOMEM; + } + g_sff_obj->index = 0; + if (sysfs_create_group(&g_sff_obj->kobj, &sff_transceiver_attr_group) != 0) { + switch_kobject_delete(&g_sff_obj); + SFF_ERR("create transceiver dir attrs error!\n"); + return -EBADRQC; + } + return 0; +} + +/* delete transceiver directory and attributes */ +static void sff_transceiver_remove(void) +{ + if (g_sff_obj) { + sysfs_remove_group(&g_sff_obj->kobj, &sff_transceiver_attr_group); + switch_kobject_delete(&g_sff_obj); + } + + return; +} + +int s3ip_sysfs_sff_drivers_register(struct s3ip_sysfs_transceiver_drivers_s *drv) +{ + int ret, sff_num; + + SFF_INFO("s3ip_sysfs_sff_drivers_register...\n"); + if (g_sff_drv) { + SFF_ERR("g_sff_drv is not NULL, can't register\n"); + return -EPERM; + } + + check_p(drv); + check_p(drv->get_eth_number); + g_sff_drv = drv; + + sff_num = g_sff_drv->get_eth_number(); + if (sff_num <= 0) { + SFF_ERR("eth number: %d, don't need to create transceiver dirs and attrs.\n", sff_num); + g_sff_drv = NULL; + return -EINVAL; + } + + memset(&g_sff, 0, sizeof(struct sff_s)); + g_sff.sff_number = sff_num; + ret = sff_transceiver_create(); + if (ret < 0) { + SFF_ERR("create transceiver root dir and attrs failed, ret: %d\n", ret); + g_sff_drv = NULL; + return ret; + } + ret = sff_sub_create(); + if (ret < 0) { + SFF_ERR("create transceiver sub dir and attrs failed, ret: %d\n", ret); + sff_transceiver_remove(); + g_sff_drv = NULL; + return ret; + } + SFF_INFO("s3ip_sysfs_sff_drivers_register success\n"); + return ret; +} + +void s3ip_sysfs_sff_drivers_unregister(void) +{ + if (g_sff_drv) { + sff_sub_remove(); + sff_transceiver_remove(); + g_sff_drv = NULL; + SFF_DBG("s3ip_sysfs_sff_drivers_unregister success.\n"); + } + return; +} + +EXPORT_SYMBOL(s3ip_sysfs_sff_drivers_register); +EXPORT_SYMBOL(s3ip_sysfs_sff_drivers_unregister); +module_param(g_sff_loglevel, int, 0644); +MODULE_PARM_DESC(g_sff_loglevel, "the log level(info=0x1, err=0x2, dbg=0x4).\n");