| /* |
| * Copyright 2004 Digi International (www.digi.com) |
| * Scott H Kilau <Scott_Kilau at digi dot 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, or (at your option) |
| * any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY, EXPRESS OR IMPLIED; without even the |
| * implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR |
| * PURPOSE. See the GNU General Public License for more details. |
| * |
| */ |
| |
| #include "dgrp_common.h" |
| |
| #include <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/ctype.h> |
| #include <linux/string.h> |
| #include <linux/serial_reg.h> |
| #include <linux/pci.h> |
| #include <linux/kdev_t.h> |
| |
| |
| #define PORTSERVER_DIVIDEND 1843200 |
| #define SERIAL_TYPE_NORMAL 1 |
| #define SERIAL_TYPE_CALLOUT 2 |
| #define SERIAL_TYPE_XPRINT 3 |
| |
| |
| static struct class *dgrp_class; |
| static struct device *dgrp_class_nodes_dev; |
| static struct device *dgrp_class_global_settings_dev; |
| |
| |
| static ssize_t dgrp_class_version_show(struct class *class, |
| struct class_attribute *attr, char *buf) |
| { |
| return snprintf(buf, PAGE_SIZE, "%s\n", DIGI_VERSION); |
| } |
| static CLASS_ATTR(driver_version, 0400, dgrp_class_version_show, NULL); |
| |
| |
| static ssize_t dgrp_class_register_with_sysfs_show(struct device *c, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| return snprintf(buf, PAGE_SIZE, "1\n"); |
| } |
| static DEVICE_ATTR(register_with_sysfs, 0400, |
| dgrp_class_register_with_sysfs_show, NULL); |
| |
| |
| static ssize_t dgrp_class_pollrate_show(struct device *c, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| return snprintf(buf, PAGE_SIZE, "%d\n", dgrp_poll_tick); |
| } |
| |
| static ssize_t dgrp_class_pollrate_store(struct device *c, |
| struct device_attribute *attr, |
| const char *buf, size_t count) |
| { |
| if (sscanf(buf, "0x%x\n", &dgrp_poll_tick) != 1) |
| return -EINVAL; |
| |
| return count; |
| } |
| static DEVICE_ATTR(pollrate, 0600, dgrp_class_pollrate_show, |
| dgrp_class_pollrate_store); |
| |
| static struct attribute *dgrp_sysfs_global_settings_entries[] = { |
| &dev_attr_pollrate.attr, |
| &dev_attr_register_with_sysfs.attr, |
| NULL |
| }; |
| |
| |
| static struct attribute_group dgrp_global_settings_attribute_group = { |
| .name = NULL, |
| .attrs = dgrp_sysfs_global_settings_entries, |
| }; |
| |
| |
| |
| int dgrp_create_class_sysfs_files(void) |
| { |
| int ret = 0; |
| int max_majors = 1U << (32 - MINORBITS); |
| |
| dgrp_class = class_create(THIS_MODULE, "digi_realport"); |
| if (IS_ERR(dgrp_class)) |
| return PTR_ERR(dgrp_class); |
| ret = class_create_file(dgrp_class, &class_attr_driver_version); |
| if (ret) |
| goto err_class; |
| |
| dgrp_class_global_settings_dev = device_create(dgrp_class, NULL, |
| MKDEV(0, max_majors + 1), NULL, "driver_settings"); |
| if (IS_ERR(dgrp_class_global_settings_dev)) { |
| ret = PTR_ERR(dgrp_class_global_settings_dev); |
| goto err_file; |
| } |
| ret = sysfs_create_group(&dgrp_class_global_settings_dev->kobj, |
| &dgrp_global_settings_attribute_group); |
| if (ret) { |
| pr_alert("%s: failed to create sysfs global settings device attributes.\n", |
| __func__); |
| goto err_dev1; |
| } |
| |
| dgrp_class_nodes_dev = device_create(dgrp_class, NULL, |
| MKDEV(0, max_majors + 2), NULL, "nodes"); |
| if (IS_ERR(dgrp_class_nodes_dev)) { |
| ret = PTR_ERR(dgrp_class_nodes_dev); |
| goto err_group; |
| } |
| |
| return 0; |
| err_group: |
| sysfs_remove_group(&dgrp_class_global_settings_dev->kobj, |
| &dgrp_global_settings_attribute_group); |
| err_dev1: |
| device_destroy(dgrp_class, MKDEV(0, max_majors + 1)); |
| err_file: |
| class_remove_file(dgrp_class, &class_attr_driver_version); |
| err_class: |
| class_destroy(dgrp_class); |
| return ret; |
| } |
| |
| |
| void dgrp_remove_class_sysfs_files(void) |
| { |
| struct nd_struct *nd; |
| int max_majors = 1U << (32 - MINORBITS); |
| |
| list_for_each_entry(nd, &nd_struct_list, list) |
| dgrp_remove_node_class_sysfs_files(nd); |
| |
| sysfs_remove_group(&dgrp_class_global_settings_dev->kobj, |
| &dgrp_global_settings_attribute_group); |
| |
| class_remove_file(dgrp_class, &class_attr_driver_version); |
| |
| device_destroy(dgrp_class, MKDEV(0, max_majors + 1)); |
| device_destroy(dgrp_class, MKDEV(0, max_majors + 2)); |
| class_destroy(dgrp_class); |
| } |
| |
| static ssize_t dgrp_node_state_show(struct device *c, |
| struct device_attribute *attr, char *buf) |
| { |
| struct nd_struct *nd; |
| |
| if (!c) |
| return 0; |
| nd = dev_get_drvdata(c); |
| if (!nd) |
| return 0; |
| |
| return snprintf(buf, PAGE_SIZE, "%s\n", ND_STATE_STR(nd->nd_state)); |
| } |
| |
| static DEVICE_ATTR(state, 0600, dgrp_node_state_show, NULL); |
| |
| static ssize_t dgrp_node_description_show(struct device *c, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct nd_struct *nd; |
| |
| if (!c) |
| return 0; |
| nd = dev_get_drvdata(c); |
| if (!nd) |
| return 0; |
| |
| if (nd->nd_state == NS_READY) |
| return snprintf(buf, PAGE_SIZE, "%s\n", nd->nd_ps_desc); |
| return 0; |
| } |
| static DEVICE_ATTR(description_info, 0600, dgrp_node_description_show, NULL); |
| |
| static ssize_t dgrp_node_hw_version_show(struct device *c, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct nd_struct *nd; |
| |
| if (!c) |
| return 0; |
| nd = dev_get_drvdata(c); |
| if (!nd) |
| return 0; |
| |
| if (nd->nd_state == NS_READY) |
| return snprintf(buf, PAGE_SIZE, "%d.%d\n", |
| (nd->nd_hw_ver >> 8) & 0xff, |
| nd->nd_hw_ver & 0xff); |
| |
| return 0; |
| } |
| static DEVICE_ATTR(hw_version_info, 0600, dgrp_node_hw_version_show, NULL); |
| |
| static ssize_t dgrp_node_hw_id_show(struct device *c, |
| struct device_attribute *attr, char *buf) |
| { |
| struct nd_struct *nd; |
| |
| if (!c) |
| return 0; |
| nd = dev_get_drvdata(c); |
| if (!nd) |
| return 0; |
| |
| |
| if (nd->nd_state == NS_READY) |
| return snprintf(buf, PAGE_SIZE, "%d\n", nd->nd_hw_id); |
| return 0; |
| } |
| static DEVICE_ATTR(hw_id_info, 0600, dgrp_node_hw_id_show, NULL); |
| |
| static ssize_t dgrp_node_sw_version_show(struct device *c, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| struct nd_struct *nd; |
| |
| if (!c) |
| return 0; |
| |
| nd = dev_get_drvdata(c); |
| if (!nd) |
| return 0; |
| |
| if (nd->nd_state == NS_READY) |
| return snprintf(buf, PAGE_SIZE, "%d.%d\n", |
| (nd->nd_sw_ver >> 8) & 0xff, |
| nd->nd_sw_ver & 0xff); |
| |
| return 0; |
| } |
| static DEVICE_ATTR(sw_version_info, 0600, dgrp_node_sw_version_show, NULL); |
| |
| |
| static struct attribute *dgrp_sysfs_node_entries[] = { |
| &dev_attr_state.attr, |
| &dev_attr_description_info.attr, |
| &dev_attr_hw_version_info.attr, |
| &dev_attr_hw_id_info.attr, |
| &dev_attr_sw_version_info.attr, |
| NULL |
| }; |
| |
| |
| static struct attribute_group dgrp_node_attribute_group = { |
| .name = NULL, |
| .attrs = dgrp_sysfs_node_entries, |
| }; |
| |
| |
| void dgrp_create_node_class_sysfs_files(struct nd_struct *nd) |
| { |
| int ret; |
| char name[10]; |
| |
| if (nd->nd_ID) |
| ID_TO_CHAR(nd->nd_ID, name); |
| else |
| sprintf(name, "node%ld", nd->nd_major); |
| |
| nd->nd_class_dev = device_create(dgrp_class, dgrp_class_nodes_dev, |
| MKDEV(0, nd->nd_major), NULL, "%s", name); |
| |
| ret = sysfs_create_group(&nd->nd_class_dev->kobj, |
| &dgrp_node_attribute_group); |
| |
| if (ret) { |
| pr_alert("%s: failed to create sysfs node device attributes.\n", |
| __func__); |
| sysfs_remove_group(&nd->nd_class_dev->kobj, |
| &dgrp_node_attribute_group); |
| return; |
| } |
| |
| dev_set_drvdata(nd->nd_class_dev, nd); |
| |
| } |
| |
| |
| void dgrp_remove_node_class_sysfs_files(struct nd_struct *nd) |
| { |
| if (nd->nd_class_dev) { |
| sysfs_remove_group(&nd->nd_class_dev->kobj, |
| &dgrp_node_attribute_group); |
| |
| device_destroy(dgrp_class, MKDEV(0, nd->nd_major)); |
| nd->nd_class_dev = NULL; |
| } |
| } |
| |
| |
| |
| static ssize_t dgrp_tty_state_show(struct device *d, |
| struct device_attribute *attr, char *buf) |
| { |
| struct un_struct *un; |
| |
| if (!d) |
| return 0; |
| un = dev_get_drvdata(d); |
| if (!un) |
| return 0; |
| |
| return snprintf(buf, PAGE_SIZE, "%s\n", |
| un->un_open_count ? "Open" : "Closed"); |
| } |
| static DEVICE_ATTR(state_info, 0600, dgrp_tty_state_show, NULL); |
| |
| static ssize_t dgrp_tty_baud_show(struct device *d, |
| struct device_attribute *attr, char *buf) |
| { |
| struct ch_struct *ch; |
| struct un_struct *un; |
| |
| if (!d) |
| return 0; |
| un = dev_get_drvdata(d); |
| if (!un) |
| return 0; |
| ch = un->un_ch; |
| if (!ch) |
| return 0; |
| return snprintf(buf, PAGE_SIZE, "%d\n", |
| un->un_open_count ? (PORTSERVER_DIVIDEND / ch->ch_s_brate) : 0); |
| } |
| static DEVICE_ATTR(baud_info, 0400, dgrp_tty_baud_show, NULL); |
| |
| |
| static ssize_t dgrp_tty_msignals_show(struct device *d, |
| struct device_attribute *attr, char *buf) |
| { |
| struct ch_struct *ch; |
| struct un_struct *un; |
| |
| if (!d) |
| return 0; |
| un = dev_get_drvdata(d); |
| if (!un) |
| return 0; |
| ch = un->un_ch; |
| if (!ch) |
| return 0; |
| |
| if (ch->ch_open_count) { |
| return snprintf(buf, PAGE_SIZE, "%s %s %s %s %s %s\n", |
| (ch->ch_s_mlast & DM_RTS) ? "RTS" : "", |
| (ch->ch_s_mlast & DM_CTS) ? "CTS" : "", |
| (ch->ch_s_mlast & DM_DTR) ? "DTR" : "", |
| (ch->ch_s_mlast & DM_DSR) ? "DSR" : "", |
| (ch->ch_s_mlast & DM_CD) ? "DCD" : "", |
| (ch->ch_s_mlast & DM_RI) ? "RI" : ""); |
| } |
| return 0; |
| } |
| static DEVICE_ATTR(msignals_info, 0400, dgrp_tty_msignals_show, NULL); |
| |
| |
| static ssize_t dgrp_tty_iflag_show(struct device *d, |
| struct device_attribute *attr, char *buf) |
| { |
| struct ch_struct *ch; |
| struct un_struct *un; |
| |
| if (!d) |
| return 0; |
| un = dev_get_drvdata(d); |
| if (!un) |
| return 0; |
| ch = un->un_ch; |
| if (!ch) |
| return 0; |
| return snprintf(buf, PAGE_SIZE, "%x\n", ch->ch_s_iflag); |
| } |
| static DEVICE_ATTR(iflag_info, 0600, dgrp_tty_iflag_show, NULL); |
| |
| |
| static ssize_t dgrp_tty_cflag_show(struct device *d, |
| struct device_attribute *attr, char *buf) |
| { |
| struct ch_struct *ch; |
| struct un_struct *un; |
| |
| if (!d) |
| return 0; |
| un = dev_get_drvdata(d); |
| if (!un) |
| return 0; |
| ch = un->un_ch; |
| if (!ch) |
| return 0; |
| return snprintf(buf, PAGE_SIZE, "%x\n", ch->ch_s_cflag); |
| } |
| static DEVICE_ATTR(cflag_info, 0600, dgrp_tty_cflag_show, NULL); |
| |
| |
| static ssize_t dgrp_tty_oflag_show(struct device *d, |
| struct device_attribute *attr, char *buf) |
| { |
| struct ch_struct *ch; |
| struct un_struct *un; |
| |
| if (!d) |
| return 0; |
| un = dev_get_drvdata(d); |
| if (!un) |
| return 0; |
| ch = un->un_ch; |
| if (!ch) |
| return 0; |
| return snprintf(buf, PAGE_SIZE, "%x\n", ch->ch_s_oflag); |
| } |
| static DEVICE_ATTR(oflag_info, 0600, dgrp_tty_oflag_show, NULL); |
| |
| |
| static ssize_t dgrp_tty_digi_flag_show(struct device *d, |
| struct device_attribute *attr, char *buf) |
| { |
| struct ch_struct *ch; |
| struct un_struct *un; |
| |
| if (!d) |
| return 0; |
| un = dev_get_drvdata(d); |
| if (!un) |
| return 0; |
| ch = un->un_ch; |
| if (!ch) |
| return 0; |
| return snprintf(buf, PAGE_SIZE, "%x\n", ch->ch_digi.digi_flags); |
| } |
| static DEVICE_ATTR(digi_flag_info, 0600, dgrp_tty_digi_flag_show, NULL); |
| |
| |
| static ssize_t dgrp_tty_rxcount_show(struct device *d, |
| struct device_attribute *attr, char *buf) |
| { |
| struct ch_struct *ch; |
| struct un_struct *un; |
| |
| if (!d) |
| return 0; |
| un = dev_get_drvdata(d); |
| if (!un) |
| return 0; |
| ch = un->un_ch; |
| if (!ch) |
| return 0; |
| return snprintf(buf, PAGE_SIZE, "%d\n", ch->ch_rxcount); |
| } |
| static DEVICE_ATTR(rxcount_info, 0600, dgrp_tty_rxcount_show, NULL); |
| |
| |
| static ssize_t dgrp_tty_txcount_show(struct device *d, |
| struct device_attribute *attr, char *buf) |
| { |
| struct ch_struct *ch; |
| struct un_struct *un; |
| |
| if (!d) |
| return 0; |
| un = dev_get_drvdata(d); |
| if (!un) |
| return 0; |
| ch = un->un_ch; |
| if (!ch) |
| return 0; |
| return snprintf(buf, PAGE_SIZE, "%d\n", ch->ch_txcount); |
| } |
| static DEVICE_ATTR(txcount_info, 0600, dgrp_tty_txcount_show, NULL); |
| |
| |
| static ssize_t dgrp_tty_name_show(struct device *d, |
| struct device_attribute *attr, char *buf) |
| { |
| struct nd_struct *nd; |
| struct ch_struct *ch; |
| struct un_struct *un; |
| char name[10]; |
| |
| if (!d) |
| return 0; |
| un = dev_get_drvdata(d); |
| if (!un) |
| return 0; |
| ch = un->un_ch; |
| if (!ch) |
| return 0; |
| nd = ch->ch_nd; |
| if (!nd) |
| return 0; |
| |
| ID_TO_CHAR(nd->nd_ID, name); |
| |
| return snprintf(buf, PAGE_SIZE, "%s%s%02d\n", |
| un->un_type == SERIAL_TYPE_XPRINT ? "pr" : "tty", |
| name, ch->ch_portnum); |
| } |
| static DEVICE_ATTR(custom_name, 0600, dgrp_tty_name_show, NULL); |
| |
| |
| static struct attribute *dgrp_sysfs_tty_entries[] = { |
| &dev_attr_state_info.attr, |
| &dev_attr_baud_info.attr, |
| &dev_attr_msignals_info.attr, |
| &dev_attr_iflag_info.attr, |
| &dev_attr_cflag_info.attr, |
| &dev_attr_oflag_info.attr, |
| &dev_attr_digi_flag_info.attr, |
| &dev_attr_rxcount_info.attr, |
| &dev_attr_txcount_info.attr, |
| &dev_attr_custom_name.attr, |
| NULL |
| }; |
| |
| |
| static struct attribute_group dgrp_tty_attribute_group = { |
| .name = NULL, |
| .attrs = dgrp_sysfs_tty_entries, |
| }; |
| |
| |
| void dgrp_create_tty_sysfs(struct un_struct *un, struct device *c) |
| { |
| int ret; |
| |
| ret = sysfs_create_group(&c->kobj, &dgrp_tty_attribute_group); |
| if (ret) { |
| pr_alert("%s: failed to create sysfs tty device attributes.\n", |
| __func__); |
| sysfs_remove_group(&c->kobj, &dgrp_tty_attribute_group); |
| return; |
| } |
| |
| dev_set_drvdata(c, un); |
| |
| } |
| |
| |
| void dgrp_remove_tty_sysfs(struct device *c) |
| { |
| sysfs_remove_group(&c->kobj, &dgrp_tty_attribute_group); |
| } |