![Tejun Heo](/assets/img/avatar_default.png)
sysfs is now completely out of driver/module lifetime game. After deletion, a sysfs node doesn't access anything outside sysfs proper, so there's no reason to hold onto the attribute owners. Note that often the wrong modules were accounted for as owners leading to accessing removed modules. This patch kills now unnecessary attribute->owner. Note that with this change, userland holding a sysfs node does not prevent the backing module from being unloaded. For more info regarding lifetime rule cleanup, please read the following message. http://article.gmane.org/gmane.linux.kernel/510293 (tweaked by Greg to not delete the field just yet, to make it easier to merge things properly.) Signed-off-by: Tejun Heo <htejun@gmail.com> Cc: Cornelia Huck <cornelia.huck@de.ibm.com> Cc: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
1859 lines
45 KiB
C
1859 lines
45 KiB
C
/*
|
|
*
|
|
* linux/drivers/s390/net/qeth_sys.c
|
|
*
|
|
* Linux on zSeries OSA Express and HiperSockets support
|
|
* This file contains code related to sysfs.
|
|
*
|
|
* Copyright 2000,2003 IBM Corporation
|
|
*
|
|
* Author(s): Thomas Spatzier <tspat@de.ibm.com>
|
|
* Frank Pavlic <fpavlic@de.ibm.com>
|
|
*
|
|
*/
|
|
#include <linux/list.h>
|
|
#include <linux/rwsem.h>
|
|
|
|
#include <asm/ebcdic.h>
|
|
|
|
#include "qeth.h"
|
|
#include "qeth_mpc.h"
|
|
#include "qeth_fs.h"
|
|
|
|
/*****************************************************************************/
|
|
/* */
|
|
/* /sys-fs stuff UNDER DEVELOPMENT !!! */
|
|
/* */
|
|
/*****************************************************************************/
|
|
//low/high watermark
|
|
|
|
static ssize_t
|
|
qeth_dev_state_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
switch (card->state) {
|
|
case CARD_STATE_DOWN:
|
|
return sprintf(buf, "DOWN\n");
|
|
case CARD_STATE_HARDSETUP:
|
|
return sprintf(buf, "HARDSETUP\n");
|
|
case CARD_STATE_SOFTSETUP:
|
|
return sprintf(buf, "SOFTSETUP\n");
|
|
case CARD_STATE_UP:
|
|
if (card->lan_online)
|
|
return sprintf(buf, "UP (LAN ONLINE)\n");
|
|
else
|
|
return sprintf(buf, "UP (LAN OFFLINE)\n");
|
|
case CARD_STATE_RECOVER:
|
|
return sprintf(buf, "RECOVER\n");
|
|
default:
|
|
return sprintf(buf, "UNKNOWN\n");
|
|
}
|
|
}
|
|
|
|
static DEVICE_ATTR(state, 0444, qeth_dev_state_show, NULL);
|
|
|
|
static ssize_t
|
|
qeth_dev_chpid_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
return sprintf(buf, "%02X\n", card->info.chpid);
|
|
}
|
|
|
|
static DEVICE_ATTR(chpid, 0444, qeth_dev_chpid_show, NULL);
|
|
|
|
static ssize_t
|
|
qeth_dev_if_name_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
if (!card)
|
|
return -EINVAL;
|
|
return sprintf(buf, "%s\n", QETH_CARD_IFNAME(card));
|
|
}
|
|
|
|
static DEVICE_ATTR(if_name, 0444, qeth_dev_if_name_show, NULL);
|
|
|
|
static ssize_t
|
|
qeth_dev_card_type_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
return sprintf(buf, "%s\n", qeth_get_cardname_short(card));
|
|
}
|
|
|
|
static DEVICE_ATTR(card_type, 0444, qeth_dev_card_type_show, NULL);
|
|
|
|
static ssize_t
|
|
qeth_dev_portno_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
return sprintf(buf, "%i\n", card->info.portno);
|
|
}
|
|
|
|
static ssize_t
|
|
qeth_dev_portno_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
char *tmp;
|
|
unsigned int portno;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
if ((card->state != CARD_STATE_DOWN) &&
|
|
(card->state != CARD_STATE_RECOVER))
|
|
return -EPERM;
|
|
|
|
portno = simple_strtoul(buf, &tmp, 16);
|
|
if (portno > MAX_PORTNO){
|
|
PRINT_WARN("portno 0x%X is out of range\n", portno);
|
|
return -EINVAL;
|
|
}
|
|
|
|
card->info.portno = portno;
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(portno, 0644, qeth_dev_portno_show, qeth_dev_portno_store);
|
|
|
|
static ssize_t
|
|
qeth_dev_portname_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
char portname[9] = {0, };
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
if (card->info.portname_required) {
|
|
memcpy(portname, card->info.portname + 1, 8);
|
|
EBCASC(portname, 8);
|
|
return sprintf(buf, "%s\n", portname);
|
|
} else
|
|
return sprintf(buf, "no portname required\n");
|
|
}
|
|
|
|
static ssize_t
|
|
qeth_dev_portname_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
char *tmp;
|
|
int i;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
if ((card->state != CARD_STATE_DOWN) &&
|
|
(card->state != CARD_STATE_RECOVER))
|
|
return -EPERM;
|
|
|
|
tmp = strsep((char **) &buf, "\n");
|
|
if ((strlen(tmp) > 8) || (strlen(tmp) == 0))
|
|
return -EINVAL;
|
|
|
|
card->info.portname[0] = strlen(tmp);
|
|
/* for beauty reasons */
|
|
for (i = 1; i < 9; i++)
|
|
card->info.portname[i] = ' ';
|
|
strcpy(card->info.portname + 1, tmp);
|
|
ASCEBC(card->info.portname + 1, 8);
|
|
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(portname, 0644, qeth_dev_portname_show,
|
|
qeth_dev_portname_store);
|
|
|
|
static ssize_t
|
|
qeth_dev_checksum_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
return sprintf(buf, "%s checksumming\n", qeth_get_checksum_str(card));
|
|
}
|
|
|
|
static ssize_t
|
|
qeth_dev_checksum_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
char *tmp;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
if ((card->state != CARD_STATE_DOWN) &&
|
|
(card->state != CARD_STATE_RECOVER))
|
|
return -EPERM;
|
|
|
|
tmp = strsep((char **) &buf, "\n");
|
|
if (!strcmp(tmp, "sw_checksumming"))
|
|
card->options.checksum_type = SW_CHECKSUMMING;
|
|
else if (!strcmp(tmp, "hw_checksumming"))
|
|
card->options.checksum_type = HW_CHECKSUMMING;
|
|
else if (!strcmp(tmp, "no_checksumming"))
|
|
card->options.checksum_type = NO_CHECKSUMMING;
|
|
else {
|
|
PRINT_WARN("Unknown checksumming type '%s'\n", tmp);
|
|
return -EINVAL;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(checksumming, 0644, qeth_dev_checksum_show,
|
|
qeth_dev_checksum_store);
|
|
|
|
static ssize_t
|
|
qeth_dev_prioqing_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
switch (card->qdio.do_prio_queueing) {
|
|
case QETH_PRIO_Q_ING_PREC:
|
|
return sprintf(buf, "%s\n", "by precedence");
|
|
case QETH_PRIO_Q_ING_TOS:
|
|
return sprintf(buf, "%s\n", "by type of service");
|
|
default:
|
|
return sprintf(buf, "always queue %i\n",
|
|
card->qdio.default_out_queue);
|
|
}
|
|
}
|
|
|
|
static ssize_t
|
|
qeth_dev_prioqing_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
char *tmp;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
if ((card->state != CARD_STATE_DOWN) &&
|
|
(card->state != CARD_STATE_RECOVER))
|
|
return -EPERM;
|
|
|
|
/* check if 1920 devices are supported ,
|
|
* if though we have to permit priority queueing
|
|
*/
|
|
if (card->qdio.no_out_queues == 1) {
|
|
PRINT_WARN("Priority queueing disabled due "
|
|
"to hardware limitations!\n");
|
|
card->qdio.do_prio_queueing = QETH_PRIOQ_DEFAULT;
|
|
return -EPERM;
|
|
}
|
|
|
|
tmp = strsep((char **) &buf, "\n");
|
|
if (!strcmp(tmp, "prio_queueing_prec"))
|
|
card->qdio.do_prio_queueing = QETH_PRIO_Q_ING_PREC;
|
|
else if (!strcmp(tmp, "prio_queueing_tos"))
|
|
card->qdio.do_prio_queueing = QETH_PRIO_Q_ING_TOS;
|
|
else if (!strcmp(tmp, "no_prio_queueing:0")) {
|
|
card->qdio.do_prio_queueing = QETH_NO_PRIO_QUEUEING;
|
|
card->qdio.default_out_queue = 0;
|
|
} else if (!strcmp(tmp, "no_prio_queueing:1")) {
|
|
card->qdio.do_prio_queueing = QETH_NO_PRIO_QUEUEING;
|
|
card->qdio.default_out_queue = 1;
|
|
} else if (!strcmp(tmp, "no_prio_queueing:2")) {
|
|
card->qdio.do_prio_queueing = QETH_NO_PRIO_QUEUEING;
|
|
card->qdio.default_out_queue = 2;
|
|
} else if (!strcmp(tmp, "no_prio_queueing:3")) {
|
|
card->qdio.do_prio_queueing = QETH_NO_PRIO_QUEUEING;
|
|
card->qdio.default_out_queue = 3;
|
|
} else if (!strcmp(tmp, "no_prio_queueing")) {
|
|
card->qdio.do_prio_queueing = QETH_NO_PRIO_QUEUEING;
|
|
card->qdio.default_out_queue = QETH_DEFAULT_QUEUE;
|
|
} else {
|
|
PRINT_WARN("Unknown queueing type '%s'\n", tmp);
|
|
return -EINVAL;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(priority_queueing, 0644, qeth_dev_prioqing_show,
|
|
qeth_dev_prioqing_store);
|
|
|
|
static ssize_t
|
|
qeth_dev_bufcnt_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
return sprintf(buf, "%i\n", card->qdio.in_buf_pool.buf_count);
|
|
}
|
|
|
|
static ssize_t
|
|
qeth_dev_bufcnt_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
char *tmp;
|
|
int cnt, old_cnt;
|
|
int rc;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
if ((card->state != CARD_STATE_DOWN) &&
|
|
(card->state != CARD_STATE_RECOVER))
|
|
return -EPERM;
|
|
|
|
old_cnt = card->qdio.in_buf_pool.buf_count;
|
|
cnt = simple_strtoul(buf, &tmp, 10);
|
|
cnt = (cnt < QETH_IN_BUF_COUNT_MIN) ? QETH_IN_BUF_COUNT_MIN :
|
|
((cnt > QETH_IN_BUF_COUNT_MAX) ? QETH_IN_BUF_COUNT_MAX : cnt);
|
|
if (old_cnt != cnt) {
|
|
if ((rc = qeth_realloc_buffer_pool(card, cnt)))
|
|
PRINT_WARN("Error (%d) while setting "
|
|
"buffer count.\n", rc);
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(buffer_count, 0644, qeth_dev_bufcnt_show,
|
|
qeth_dev_bufcnt_store);
|
|
|
|
static ssize_t
|
|
qeth_dev_route_show(struct qeth_card *card, struct qeth_routing_info *route,
|
|
char *buf)
|
|
{
|
|
switch (route->type) {
|
|
case PRIMARY_ROUTER:
|
|
return sprintf(buf, "%s\n", "primary router");
|
|
case SECONDARY_ROUTER:
|
|
return sprintf(buf, "%s\n", "secondary router");
|
|
case MULTICAST_ROUTER:
|
|
if (card->info.broadcast_capable == QETH_BROADCAST_WITHOUT_ECHO)
|
|
return sprintf(buf, "%s\n", "multicast router+");
|
|
else
|
|
return sprintf(buf, "%s\n", "multicast router");
|
|
case PRIMARY_CONNECTOR:
|
|
if (card->info.broadcast_capable == QETH_BROADCAST_WITHOUT_ECHO)
|
|
return sprintf(buf, "%s\n", "primary connector+");
|
|
else
|
|
return sprintf(buf, "%s\n", "primary connector");
|
|
case SECONDARY_CONNECTOR:
|
|
if (card->info.broadcast_capable == QETH_BROADCAST_WITHOUT_ECHO)
|
|
return sprintf(buf, "%s\n", "secondary connector+");
|
|
else
|
|
return sprintf(buf, "%s\n", "secondary connector");
|
|
default:
|
|
return sprintf(buf, "%s\n", "no");
|
|
}
|
|
}
|
|
|
|
static ssize_t
|
|
qeth_dev_route4_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
return qeth_dev_route_show(card, &card->options.route4, buf);
|
|
}
|
|
|
|
static ssize_t
|
|
qeth_dev_route_store(struct qeth_card *card, struct qeth_routing_info *route,
|
|
enum qeth_prot_versions prot, const char *buf, size_t count)
|
|
{
|
|
enum qeth_routing_types old_route_type = route->type;
|
|
char *tmp;
|
|
int rc;
|
|
|
|
tmp = strsep((char **) &buf, "\n");
|
|
|
|
if (!strcmp(tmp, "no_router")){
|
|
route->type = NO_ROUTER;
|
|
} else if (!strcmp(tmp, "primary_connector")) {
|
|
route->type = PRIMARY_CONNECTOR;
|
|
} else if (!strcmp(tmp, "secondary_connector")) {
|
|
route->type = SECONDARY_CONNECTOR;
|
|
} else if (!strcmp(tmp, "primary_router")) {
|
|
route->type = PRIMARY_ROUTER;
|
|
} else if (!strcmp(tmp, "secondary_router")) {
|
|
route->type = SECONDARY_ROUTER;
|
|
} else if (!strcmp(tmp, "multicast_router")) {
|
|
route->type = MULTICAST_ROUTER;
|
|
} else {
|
|
PRINT_WARN("Invalid routing type '%s'.\n", tmp);
|
|
return -EINVAL;
|
|
}
|
|
if (((card->state == CARD_STATE_SOFTSETUP) ||
|
|
(card->state == CARD_STATE_UP)) &&
|
|
(old_route_type != route->type)){
|
|
if (prot == QETH_PROT_IPV4)
|
|
rc = qeth_setrouting_v4(card);
|
|
else if (prot == QETH_PROT_IPV6)
|
|
rc = qeth_setrouting_v6(card);
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static ssize_t
|
|
qeth_dev_route4_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
return qeth_dev_route_store(card, &card->options.route4,
|
|
QETH_PROT_IPV4, buf, count);
|
|
}
|
|
|
|
static DEVICE_ATTR(route4, 0644, qeth_dev_route4_show, qeth_dev_route4_store);
|
|
|
|
#ifdef CONFIG_QETH_IPV6
|
|
static ssize_t
|
|
qeth_dev_route6_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
if (!qeth_is_supported(card, IPA_IPV6))
|
|
return sprintf(buf, "%s\n", "n/a");
|
|
|
|
return qeth_dev_route_show(card, &card->options.route6, buf);
|
|
}
|
|
|
|
static ssize_t
|
|
qeth_dev_route6_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
if (!qeth_is_supported(card, IPA_IPV6)){
|
|
PRINT_WARN("IPv6 not supported for interface %s.\n"
|
|
"Routing status no changed.\n",
|
|
QETH_CARD_IFNAME(card));
|
|
return -ENOTSUPP;
|
|
}
|
|
|
|
return qeth_dev_route_store(card, &card->options.route6,
|
|
QETH_PROT_IPV6, buf, count);
|
|
}
|
|
|
|
static DEVICE_ATTR(route6, 0644, qeth_dev_route6_show, qeth_dev_route6_store);
|
|
#endif
|
|
|
|
static ssize_t
|
|
qeth_dev_add_hhlen_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
return sprintf(buf, "%i\n", card->options.add_hhlen);
|
|
}
|
|
|
|
static ssize_t
|
|
qeth_dev_add_hhlen_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
char *tmp;
|
|
int i;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
if ((card->state != CARD_STATE_DOWN) &&
|
|
(card->state != CARD_STATE_RECOVER))
|
|
return -EPERM;
|
|
|
|
i = simple_strtoul(buf, &tmp, 10);
|
|
if ((i < 0) || (i > MAX_ADD_HHLEN)) {
|
|
PRINT_WARN("add_hhlen out of range\n");
|
|
return -EINVAL;
|
|
}
|
|
card->options.add_hhlen = i;
|
|
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(add_hhlen, 0644, qeth_dev_add_hhlen_show,
|
|
qeth_dev_add_hhlen_store);
|
|
|
|
static ssize_t
|
|
qeth_dev_fake_ll_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
return sprintf(buf, "%i\n", card->options.fake_ll? 1:0);
|
|
}
|
|
|
|
static ssize_t
|
|
qeth_dev_fake_ll_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
char *tmp;
|
|
int i;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
if ((card->state != CARD_STATE_DOWN) &&
|
|
(card->state != CARD_STATE_RECOVER))
|
|
return -EPERM;
|
|
|
|
i = simple_strtoul(buf, &tmp, 16);
|
|
if ((i != 0) && (i != 1)) {
|
|
PRINT_WARN("fake_ll: write 0 or 1 to this file!\n");
|
|
return -EINVAL;
|
|
}
|
|
card->options.fake_ll = i;
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(fake_ll, 0644, qeth_dev_fake_ll_show,
|
|
qeth_dev_fake_ll_store);
|
|
|
|
static ssize_t
|
|
qeth_dev_fake_broadcast_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
return sprintf(buf, "%i\n", card->options.fake_broadcast? 1:0);
|
|
}
|
|
|
|
static ssize_t
|
|
qeth_dev_fake_broadcast_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
char *tmp;
|
|
int i;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
if ((card->state != CARD_STATE_DOWN) &&
|
|
(card->state != CARD_STATE_RECOVER))
|
|
return -EPERM;
|
|
|
|
i = simple_strtoul(buf, &tmp, 16);
|
|
if ((i == 0) || (i == 1))
|
|
card->options.fake_broadcast = i;
|
|
else {
|
|
PRINT_WARN("fake_broadcast: write 0 or 1 to this file!\n");
|
|
return -EINVAL;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(fake_broadcast, 0644, qeth_dev_fake_broadcast_show,
|
|
qeth_dev_fake_broadcast_store);
|
|
|
|
static ssize_t
|
|
qeth_dev_recover_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
char *tmp;
|
|
int i;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
if (card->state != CARD_STATE_UP)
|
|
return -EPERM;
|
|
|
|
i = simple_strtoul(buf, &tmp, 16);
|
|
if (i == 1)
|
|
qeth_schedule_recovery(card);
|
|
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(recover, 0200, NULL, qeth_dev_recover_store);
|
|
|
|
static ssize_t
|
|
qeth_dev_broadcast_mode_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
if (!((card->info.link_type == QETH_LINK_TYPE_HSTR) ||
|
|
(card->info.link_type == QETH_LINK_TYPE_LANE_TR)))
|
|
return sprintf(buf, "n/a\n");
|
|
|
|
return sprintf(buf, "%s\n", (card->options.broadcast_mode ==
|
|
QETH_TR_BROADCAST_ALLRINGS)?
|
|
"all rings":"local");
|
|
}
|
|
|
|
static ssize_t
|
|
qeth_dev_broadcast_mode_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
char *tmp;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
if ((card->state != CARD_STATE_DOWN) &&
|
|
(card->state != CARD_STATE_RECOVER))
|
|
return -EPERM;
|
|
|
|
if (!((card->info.link_type == QETH_LINK_TYPE_HSTR) ||
|
|
(card->info.link_type == QETH_LINK_TYPE_LANE_TR))){
|
|
PRINT_WARN("Device is not a tokenring device!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
tmp = strsep((char **) &buf, "\n");
|
|
|
|
if (!strcmp(tmp, "local")){
|
|
card->options.broadcast_mode = QETH_TR_BROADCAST_LOCAL;
|
|
return count;
|
|
} else if (!strcmp(tmp, "all_rings")) {
|
|
card->options.broadcast_mode = QETH_TR_BROADCAST_ALLRINGS;
|
|
return count;
|
|
} else {
|
|
PRINT_WARN("broadcast_mode: invalid mode %s!\n",
|
|
tmp);
|
|
return -EINVAL;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(broadcast_mode, 0644, qeth_dev_broadcast_mode_show,
|
|
qeth_dev_broadcast_mode_store);
|
|
|
|
static ssize_t
|
|
qeth_dev_canonical_macaddr_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
if (!((card->info.link_type == QETH_LINK_TYPE_HSTR) ||
|
|
(card->info.link_type == QETH_LINK_TYPE_LANE_TR)))
|
|
return sprintf(buf, "n/a\n");
|
|
|
|
return sprintf(buf, "%i\n", (card->options.macaddr_mode ==
|
|
QETH_TR_MACADDR_CANONICAL)? 1:0);
|
|
}
|
|
|
|
static ssize_t
|
|
qeth_dev_canonical_macaddr_store(struct device *dev, struct device_attribute *attr, const char *buf,
|
|
size_t count)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
char *tmp;
|
|
int i;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
if ((card->state != CARD_STATE_DOWN) &&
|
|
(card->state != CARD_STATE_RECOVER))
|
|
return -EPERM;
|
|
|
|
if (!((card->info.link_type == QETH_LINK_TYPE_HSTR) ||
|
|
(card->info.link_type == QETH_LINK_TYPE_LANE_TR))){
|
|
PRINT_WARN("Device is not a tokenring device!\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
i = simple_strtoul(buf, &tmp, 16);
|
|
if ((i == 0) || (i == 1))
|
|
card->options.macaddr_mode = i?
|
|
QETH_TR_MACADDR_CANONICAL :
|
|
QETH_TR_MACADDR_NONCANONICAL;
|
|
else {
|
|
PRINT_WARN("canonical_macaddr: write 0 or 1 to this file!\n");
|
|
return -EINVAL;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(canonical_macaddr, 0644, qeth_dev_canonical_macaddr_show,
|
|
qeth_dev_canonical_macaddr_store);
|
|
|
|
static ssize_t
|
|
qeth_dev_layer2_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
return sprintf(buf, "%i\n", card->options.layer2 ? 1:0);
|
|
}
|
|
|
|
static ssize_t
|
|
qeth_dev_layer2_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
char *tmp;
|
|
int i;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
if (card->info.type == QETH_CARD_TYPE_IQD) {
|
|
PRINT_WARN("Layer2 on Hipersockets is not supported! \n");
|
|
return -EPERM;
|
|
}
|
|
|
|
if (((card->state != CARD_STATE_DOWN) &&
|
|
(card->state != CARD_STATE_RECOVER)))
|
|
return -EPERM;
|
|
|
|
i = simple_strtoul(buf, &tmp, 16);
|
|
if ((i == 0) || (i == 1))
|
|
card->options.layer2 = i;
|
|
else {
|
|
PRINT_WARN("layer2: write 0 or 1 to this file!\n");
|
|
return -EINVAL;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(layer2, 0644, qeth_dev_layer2_show,
|
|
qeth_dev_layer2_store);
|
|
|
|
static ssize_t
|
|
qeth_dev_performance_stats_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
return sprintf(buf, "%i\n", card->options.performance_stats ? 1:0);
|
|
}
|
|
|
|
static ssize_t
|
|
qeth_dev_performance_stats_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
char *tmp;
|
|
int i;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
i = simple_strtoul(buf, &tmp, 16);
|
|
if ((i == 0) || (i == 1)) {
|
|
if (i == card->options.performance_stats)
|
|
return count;
|
|
card->options.performance_stats = i;
|
|
if (i == 0)
|
|
memset(&card->perf_stats, 0,
|
|
sizeof(struct qeth_perf_stats));
|
|
card->perf_stats.initial_rx_packets = card->stats.rx_packets;
|
|
card->perf_stats.initial_tx_packets = card->stats.tx_packets;
|
|
} else {
|
|
PRINT_WARN("performance_stats: write 0 or 1 to this file!\n");
|
|
return -EINVAL;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(performance_stats, 0644, qeth_dev_performance_stats_show,
|
|
qeth_dev_performance_stats_store);
|
|
|
|
static ssize_t
|
|
qeth_dev_large_send_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
switch (card->options.large_send) {
|
|
case QETH_LARGE_SEND_NO:
|
|
return sprintf(buf, "%s\n", "no");
|
|
case QETH_LARGE_SEND_EDDP:
|
|
return sprintf(buf, "%s\n", "EDDP");
|
|
case QETH_LARGE_SEND_TSO:
|
|
return sprintf(buf, "%s\n", "TSO");
|
|
default:
|
|
return sprintf(buf, "%s\n", "N/A");
|
|
}
|
|
}
|
|
|
|
static ssize_t
|
|
qeth_dev_large_send_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
enum qeth_large_send_types type;
|
|
int rc = 0;
|
|
char *tmp;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
tmp = strsep((char **) &buf, "\n");
|
|
if (!strcmp(tmp, "no")){
|
|
type = QETH_LARGE_SEND_NO;
|
|
} else if (!strcmp(tmp, "EDDP")) {
|
|
type = QETH_LARGE_SEND_EDDP;
|
|
} else if (!strcmp(tmp, "TSO")) {
|
|
type = QETH_LARGE_SEND_TSO;
|
|
} else {
|
|
PRINT_WARN("large_send: invalid mode %s!\n", tmp);
|
|
return -EINVAL;
|
|
}
|
|
if (card->options.large_send == type)
|
|
return count;
|
|
if ((rc = qeth_set_large_send(card, type)))
|
|
return rc;
|
|
return count;
|
|
}
|
|
|
|
static DEVICE_ATTR(large_send, 0644, qeth_dev_large_send_show,
|
|
qeth_dev_large_send_store);
|
|
|
|
static ssize_t
|
|
qeth_dev_blkt_show(char *buf, struct qeth_card *card, int value )
|
|
{
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
return sprintf(buf, "%i\n", value);
|
|
}
|
|
|
|
static ssize_t
|
|
qeth_dev_blkt_store(struct qeth_card *card, const char *buf, size_t count,
|
|
int *value, int max_value)
|
|
{
|
|
char *tmp;
|
|
int i;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
if ((card->state != CARD_STATE_DOWN) &&
|
|
(card->state != CARD_STATE_RECOVER))
|
|
return -EPERM;
|
|
|
|
i = simple_strtoul(buf, &tmp, 10);
|
|
if (i <= max_value) {
|
|
*value = i;
|
|
} else {
|
|
PRINT_WARN("blkt total time: write values between"
|
|
" 0 and %d to this file!\n", max_value);
|
|
return -EINVAL;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static ssize_t
|
|
qeth_dev_blkt_total_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
return qeth_dev_blkt_show(buf, card, card->info.blkt.time_total);
|
|
}
|
|
|
|
|
|
static ssize_t
|
|
qeth_dev_blkt_total_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
return qeth_dev_blkt_store(card, buf, count,
|
|
&card->info.blkt.time_total,1000);
|
|
}
|
|
|
|
|
|
|
|
static DEVICE_ATTR(total, 0644, qeth_dev_blkt_total_show,
|
|
qeth_dev_blkt_total_store);
|
|
|
|
static ssize_t
|
|
qeth_dev_blkt_inter_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
return qeth_dev_blkt_show(buf, card, card->info.blkt.inter_packet);
|
|
}
|
|
|
|
|
|
static ssize_t
|
|
qeth_dev_blkt_inter_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
return qeth_dev_blkt_store(card, buf, count,
|
|
&card->info.blkt.inter_packet,100);
|
|
}
|
|
|
|
static DEVICE_ATTR(inter, 0644, qeth_dev_blkt_inter_show,
|
|
qeth_dev_blkt_inter_store);
|
|
|
|
static ssize_t
|
|
qeth_dev_blkt_inter_jumbo_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
return qeth_dev_blkt_show(buf, card,
|
|
card->info.blkt.inter_packet_jumbo);
|
|
}
|
|
|
|
|
|
static ssize_t
|
|
qeth_dev_blkt_inter_jumbo_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
return qeth_dev_blkt_store(card, buf, count,
|
|
&card->info.blkt.inter_packet_jumbo,100);
|
|
}
|
|
|
|
static DEVICE_ATTR(inter_jumbo, 0644, qeth_dev_blkt_inter_jumbo_show,
|
|
qeth_dev_blkt_inter_jumbo_store);
|
|
|
|
static struct device_attribute * qeth_blkt_device_attrs[] = {
|
|
&dev_attr_total,
|
|
&dev_attr_inter,
|
|
&dev_attr_inter_jumbo,
|
|
NULL,
|
|
};
|
|
|
|
static struct attribute_group qeth_device_blkt_group = {
|
|
.name = "blkt",
|
|
.attrs = (struct attribute **)qeth_blkt_device_attrs,
|
|
};
|
|
|
|
static struct device_attribute * qeth_device_attrs[] = {
|
|
&dev_attr_state,
|
|
&dev_attr_chpid,
|
|
&dev_attr_if_name,
|
|
&dev_attr_card_type,
|
|
&dev_attr_portno,
|
|
&dev_attr_portname,
|
|
&dev_attr_checksumming,
|
|
&dev_attr_priority_queueing,
|
|
&dev_attr_buffer_count,
|
|
&dev_attr_route4,
|
|
#ifdef CONFIG_QETH_IPV6
|
|
&dev_attr_route6,
|
|
#endif
|
|
&dev_attr_add_hhlen,
|
|
&dev_attr_fake_ll,
|
|
&dev_attr_fake_broadcast,
|
|
&dev_attr_recover,
|
|
&dev_attr_broadcast_mode,
|
|
&dev_attr_canonical_macaddr,
|
|
&dev_attr_layer2,
|
|
&dev_attr_large_send,
|
|
&dev_attr_performance_stats,
|
|
NULL,
|
|
};
|
|
|
|
static struct attribute_group qeth_device_attr_group = {
|
|
.attrs = (struct attribute **)qeth_device_attrs,
|
|
};
|
|
|
|
static struct device_attribute * qeth_osn_device_attrs[] = {
|
|
&dev_attr_state,
|
|
&dev_attr_chpid,
|
|
&dev_attr_if_name,
|
|
&dev_attr_card_type,
|
|
&dev_attr_buffer_count,
|
|
&dev_attr_recover,
|
|
NULL,
|
|
};
|
|
|
|
static struct attribute_group qeth_osn_device_attr_group = {
|
|
.attrs = (struct attribute **)qeth_osn_device_attrs,
|
|
};
|
|
|
|
#define QETH_DEVICE_ATTR(_id,_name,_mode,_show,_store) \
|
|
struct device_attribute dev_attr_##_id = { \
|
|
.attr = {.name=__stringify(_name), .mode=_mode, },\
|
|
.show = _show, \
|
|
.store = _store, \
|
|
};
|
|
|
|
static int
|
|
qeth_check_layer2(struct qeth_card *card)
|
|
{
|
|
if (card->options.layer2)
|
|
return -EPERM;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static ssize_t
|
|
qeth_dev_ipato_enable_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
if (qeth_check_layer2(card))
|
|
return -EPERM;
|
|
return sprintf(buf, "%i\n", card->ipato.enabled? 1:0);
|
|
}
|
|
|
|
static ssize_t
|
|
qeth_dev_ipato_enable_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
char *tmp;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
if ((card->state != CARD_STATE_DOWN) &&
|
|
(card->state != CARD_STATE_RECOVER))
|
|
return -EPERM;
|
|
|
|
if (qeth_check_layer2(card))
|
|
return -EPERM;
|
|
|
|
tmp = strsep((char **) &buf, "\n");
|
|
if (!strcmp(tmp, "toggle")){
|
|
card->ipato.enabled = (card->ipato.enabled)? 0 : 1;
|
|
} else if (!strcmp(tmp, "1")){
|
|
card->ipato.enabled = 1;
|
|
} else if (!strcmp(tmp, "0")){
|
|
card->ipato.enabled = 0;
|
|
} else {
|
|
PRINT_WARN("ipato_enable: write 0, 1 or 'toggle' to "
|
|
"this file\n");
|
|
return -EINVAL;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static QETH_DEVICE_ATTR(ipato_enable, enable, 0644,
|
|
qeth_dev_ipato_enable_show,
|
|
qeth_dev_ipato_enable_store);
|
|
|
|
static ssize_t
|
|
qeth_dev_ipato_invert4_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
if (qeth_check_layer2(card))
|
|
return -EPERM;
|
|
|
|
return sprintf(buf, "%i\n", card->ipato.invert4? 1:0);
|
|
}
|
|
|
|
static ssize_t
|
|
qeth_dev_ipato_invert4_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
char *tmp;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
if (qeth_check_layer2(card))
|
|
return -EPERM;
|
|
|
|
tmp = strsep((char **) &buf, "\n");
|
|
if (!strcmp(tmp, "toggle")){
|
|
card->ipato.invert4 = (card->ipato.invert4)? 0 : 1;
|
|
} else if (!strcmp(tmp, "1")){
|
|
card->ipato.invert4 = 1;
|
|
} else if (!strcmp(tmp, "0")){
|
|
card->ipato.invert4 = 0;
|
|
} else {
|
|
PRINT_WARN("ipato_invert4: write 0, 1 or 'toggle' to "
|
|
"this file\n");
|
|
return -EINVAL;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static QETH_DEVICE_ATTR(ipato_invert4, invert4, 0644,
|
|
qeth_dev_ipato_invert4_show,
|
|
qeth_dev_ipato_invert4_store);
|
|
|
|
static ssize_t
|
|
qeth_dev_ipato_add_show(char *buf, struct qeth_card *card,
|
|
enum qeth_prot_versions proto)
|
|
{
|
|
struct qeth_ipato_entry *ipatoe;
|
|
unsigned long flags;
|
|
char addr_str[40];
|
|
int entry_len; /* length of 1 entry string, differs between v4 and v6 */
|
|
int i = 0;
|
|
|
|
if (qeth_check_layer2(card))
|
|
return -EPERM;
|
|
|
|
entry_len = (proto == QETH_PROT_IPV4)? 12 : 40;
|
|
/* add strlen for "/<mask>\n" */
|
|
entry_len += (proto == QETH_PROT_IPV4)? 5 : 6;
|
|
spin_lock_irqsave(&card->ip_lock, flags);
|
|
list_for_each_entry(ipatoe, &card->ipato.entries, entry){
|
|
if (ipatoe->proto != proto)
|
|
continue;
|
|
/* String must not be longer than PAGE_SIZE. So we check if
|
|
* string length gets near PAGE_SIZE. Then we can savely display
|
|
* the next IPv6 address (worst case, compared to IPv4) */
|
|
if ((PAGE_SIZE - i) <= entry_len)
|
|
break;
|
|
qeth_ipaddr_to_string(proto, ipatoe->addr, addr_str);
|
|
i += snprintf(buf + i, PAGE_SIZE - i,
|
|
"%s/%i\n", addr_str, ipatoe->mask_bits);
|
|
}
|
|
spin_unlock_irqrestore(&card->ip_lock, flags);
|
|
i += snprintf(buf + i, PAGE_SIZE - i, "\n");
|
|
|
|
return i;
|
|
}
|
|
|
|
static ssize_t
|
|
qeth_dev_ipato_add4_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
return qeth_dev_ipato_add_show(buf, card, QETH_PROT_IPV4);
|
|
}
|
|
|
|
static int
|
|
qeth_parse_ipatoe(const char* buf, enum qeth_prot_versions proto,
|
|
u8 *addr, int *mask_bits)
|
|
{
|
|
const char *start, *end;
|
|
char *tmp;
|
|
char buffer[40] = {0, };
|
|
|
|
start = buf;
|
|
/* get address string */
|
|
end = strchr(start, '/');
|
|
if (!end || (end - start >= 40)){
|
|
PRINT_WARN("Invalid format for ipato_addx/delx. "
|
|
"Use <ip addr>/<mask bits>\n");
|
|
return -EINVAL;
|
|
}
|
|
strncpy(buffer, start, end - start);
|
|
if (qeth_string_to_ipaddr(buffer, proto, addr)){
|
|
PRINT_WARN("Invalid IP address format!\n");
|
|
return -EINVAL;
|
|
}
|
|
start = end + 1;
|
|
*mask_bits = simple_strtoul(start, &tmp, 10);
|
|
if (!strlen(start) ||
|
|
(tmp == start) ||
|
|
(*mask_bits > ((proto == QETH_PROT_IPV4) ? 32 : 128))) {
|
|
PRINT_WARN("Invalid mask bits for ipato_addx/delx !\n");
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t
|
|
qeth_dev_ipato_add_store(const char *buf, size_t count,
|
|
struct qeth_card *card, enum qeth_prot_versions proto)
|
|
{
|
|
struct qeth_ipato_entry *ipatoe;
|
|
u8 addr[16];
|
|
int mask_bits;
|
|
int rc;
|
|
|
|
if (qeth_check_layer2(card))
|
|
return -EPERM;
|
|
if ((rc = qeth_parse_ipatoe(buf, proto, addr, &mask_bits)))
|
|
return rc;
|
|
|
|
if (!(ipatoe = kzalloc(sizeof(struct qeth_ipato_entry), GFP_KERNEL))){
|
|
PRINT_WARN("No memory to allocate ipato entry\n");
|
|
return -ENOMEM;
|
|
}
|
|
ipatoe->proto = proto;
|
|
memcpy(ipatoe->addr, addr, (proto == QETH_PROT_IPV4)? 4:16);
|
|
ipatoe->mask_bits = mask_bits;
|
|
|
|
if ((rc = qeth_add_ipato_entry(card, ipatoe))){
|
|
kfree(ipatoe);
|
|
return rc;
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t
|
|
qeth_dev_ipato_add4_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
return qeth_dev_ipato_add_store(buf, count, card, QETH_PROT_IPV4);
|
|
}
|
|
|
|
static QETH_DEVICE_ATTR(ipato_add4, add4, 0644,
|
|
qeth_dev_ipato_add4_show,
|
|
qeth_dev_ipato_add4_store);
|
|
|
|
static ssize_t
|
|
qeth_dev_ipato_del_store(const char *buf, size_t count,
|
|
struct qeth_card *card, enum qeth_prot_versions proto)
|
|
{
|
|
u8 addr[16];
|
|
int mask_bits;
|
|
int rc;
|
|
|
|
if (qeth_check_layer2(card))
|
|
return -EPERM;
|
|
if ((rc = qeth_parse_ipatoe(buf, proto, addr, &mask_bits)))
|
|
return rc;
|
|
|
|
qeth_del_ipato_entry(card, proto, addr, mask_bits);
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t
|
|
qeth_dev_ipato_del4_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
return qeth_dev_ipato_del_store(buf, count, card, QETH_PROT_IPV4);
|
|
}
|
|
|
|
static QETH_DEVICE_ATTR(ipato_del4, del4, 0200, NULL,
|
|
qeth_dev_ipato_del4_store);
|
|
|
|
#ifdef CONFIG_QETH_IPV6
|
|
static ssize_t
|
|
qeth_dev_ipato_invert6_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
if (qeth_check_layer2(card))
|
|
return -EPERM;
|
|
|
|
return sprintf(buf, "%i\n", card->ipato.invert6? 1:0);
|
|
}
|
|
|
|
static ssize_t
|
|
qeth_dev_ipato_invert6_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
char *tmp;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
if (qeth_check_layer2(card))
|
|
return -EPERM;
|
|
|
|
tmp = strsep((char **) &buf, "\n");
|
|
if (!strcmp(tmp, "toggle")){
|
|
card->ipato.invert6 = (card->ipato.invert6)? 0 : 1;
|
|
} else if (!strcmp(tmp, "1")){
|
|
card->ipato.invert6 = 1;
|
|
} else if (!strcmp(tmp, "0")){
|
|
card->ipato.invert6 = 0;
|
|
} else {
|
|
PRINT_WARN("ipato_invert6: write 0, 1 or 'toggle' to "
|
|
"this file\n");
|
|
return -EINVAL;
|
|
}
|
|
return count;
|
|
}
|
|
|
|
static QETH_DEVICE_ATTR(ipato_invert6, invert6, 0644,
|
|
qeth_dev_ipato_invert6_show,
|
|
qeth_dev_ipato_invert6_store);
|
|
|
|
|
|
static ssize_t
|
|
qeth_dev_ipato_add6_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
return qeth_dev_ipato_add_show(buf, card, QETH_PROT_IPV6);
|
|
}
|
|
|
|
static ssize_t
|
|
qeth_dev_ipato_add6_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
return qeth_dev_ipato_add_store(buf, count, card, QETH_PROT_IPV6);
|
|
}
|
|
|
|
static QETH_DEVICE_ATTR(ipato_add6, add6, 0644,
|
|
qeth_dev_ipato_add6_show,
|
|
qeth_dev_ipato_add6_store);
|
|
|
|
static ssize_t
|
|
qeth_dev_ipato_del6_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
return qeth_dev_ipato_del_store(buf, count, card, QETH_PROT_IPV6);
|
|
}
|
|
|
|
static QETH_DEVICE_ATTR(ipato_del6, del6, 0200, NULL,
|
|
qeth_dev_ipato_del6_store);
|
|
#endif /* CONFIG_QETH_IPV6 */
|
|
|
|
static struct device_attribute * qeth_ipato_device_attrs[] = {
|
|
&dev_attr_ipato_enable,
|
|
&dev_attr_ipato_invert4,
|
|
&dev_attr_ipato_add4,
|
|
&dev_attr_ipato_del4,
|
|
#ifdef CONFIG_QETH_IPV6
|
|
&dev_attr_ipato_invert6,
|
|
&dev_attr_ipato_add6,
|
|
&dev_attr_ipato_del6,
|
|
#endif
|
|
NULL,
|
|
};
|
|
|
|
static struct attribute_group qeth_device_ipato_group = {
|
|
.name = "ipa_takeover",
|
|
.attrs = (struct attribute **)qeth_ipato_device_attrs,
|
|
};
|
|
|
|
static ssize_t
|
|
qeth_dev_vipa_add_show(char *buf, struct qeth_card *card,
|
|
enum qeth_prot_versions proto)
|
|
{
|
|
struct qeth_ipaddr *ipaddr;
|
|
char addr_str[40];
|
|
int entry_len; /* length of 1 entry string, differs between v4 and v6 */
|
|
unsigned long flags;
|
|
int i = 0;
|
|
|
|
if (qeth_check_layer2(card))
|
|
return -EPERM;
|
|
|
|
entry_len = (proto == QETH_PROT_IPV4)? 12 : 40;
|
|
entry_len += 2; /* \n + terminator */
|
|
spin_lock_irqsave(&card->ip_lock, flags);
|
|
list_for_each_entry(ipaddr, &card->ip_list, entry){
|
|
if (ipaddr->proto != proto)
|
|
continue;
|
|
if (ipaddr->type != QETH_IP_TYPE_VIPA)
|
|
continue;
|
|
/* String must not be longer than PAGE_SIZE. So we check if
|
|
* string length gets near PAGE_SIZE. Then we can savely display
|
|
* the next IPv6 address (worst case, compared to IPv4) */
|
|
if ((PAGE_SIZE - i) <= entry_len)
|
|
break;
|
|
qeth_ipaddr_to_string(proto, (const u8 *)&ipaddr->u, addr_str);
|
|
i += snprintf(buf + i, PAGE_SIZE - i, "%s\n", addr_str);
|
|
}
|
|
spin_unlock_irqrestore(&card->ip_lock, flags);
|
|
i += snprintf(buf + i, PAGE_SIZE - i, "\n");
|
|
|
|
return i;
|
|
}
|
|
|
|
static ssize_t
|
|
qeth_dev_vipa_add4_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
return qeth_dev_vipa_add_show(buf, card, QETH_PROT_IPV4);
|
|
}
|
|
|
|
static int
|
|
qeth_parse_vipae(const char* buf, enum qeth_prot_versions proto,
|
|
u8 *addr)
|
|
{
|
|
if (qeth_string_to_ipaddr(buf, proto, addr)){
|
|
PRINT_WARN("Invalid IP address format!\n");
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t
|
|
qeth_dev_vipa_add_store(const char *buf, size_t count,
|
|
struct qeth_card *card, enum qeth_prot_versions proto)
|
|
{
|
|
u8 addr[16] = {0, };
|
|
int rc;
|
|
|
|
if (qeth_check_layer2(card))
|
|
return -EPERM;
|
|
if ((rc = qeth_parse_vipae(buf, proto, addr)))
|
|
return rc;
|
|
|
|
if ((rc = qeth_add_vipa(card, proto, addr)))
|
|
return rc;
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t
|
|
qeth_dev_vipa_add4_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
return qeth_dev_vipa_add_store(buf, count, card, QETH_PROT_IPV4);
|
|
}
|
|
|
|
static QETH_DEVICE_ATTR(vipa_add4, add4, 0644,
|
|
qeth_dev_vipa_add4_show,
|
|
qeth_dev_vipa_add4_store);
|
|
|
|
static ssize_t
|
|
qeth_dev_vipa_del_store(const char *buf, size_t count,
|
|
struct qeth_card *card, enum qeth_prot_versions proto)
|
|
{
|
|
u8 addr[16];
|
|
int rc;
|
|
|
|
if (qeth_check_layer2(card))
|
|
return -EPERM;
|
|
if ((rc = qeth_parse_vipae(buf, proto, addr)))
|
|
return rc;
|
|
|
|
qeth_del_vipa(card, proto, addr);
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t
|
|
qeth_dev_vipa_del4_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
return qeth_dev_vipa_del_store(buf, count, card, QETH_PROT_IPV4);
|
|
}
|
|
|
|
static QETH_DEVICE_ATTR(vipa_del4, del4, 0200, NULL,
|
|
qeth_dev_vipa_del4_store);
|
|
|
|
#ifdef CONFIG_QETH_IPV6
|
|
static ssize_t
|
|
qeth_dev_vipa_add6_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
return qeth_dev_vipa_add_show(buf, card, QETH_PROT_IPV6);
|
|
}
|
|
|
|
static ssize_t
|
|
qeth_dev_vipa_add6_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
return qeth_dev_vipa_add_store(buf, count, card, QETH_PROT_IPV6);
|
|
}
|
|
|
|
static QETH_DEVICE_ATTR(vipa_add6, add6, 0644,
|
|
qeth_dev_vipa_add6_show,
|
|
qeth_dev_vipa_add6_store);
|
|
|
|
static ssize_t
|
|
qeth_dev_vipa_del6_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
if (qeth_check_layer2(card))
|
|
return -EPERM;
|
|
|
|
return qeth_dev_vipa_del_store(buf, count, card, QETH_PROT_IPV6);
|
|
}
|
|
|
|
static QETH_DEVICE_ATTR(vipa_del6, del6, 0200, NULL,
|
|
qeth_dev_vipa_del6_store);
|
|
#endif /* CONFIG_QETH_IPV6 */
|
|
|
|
static struct device_attribute * qeth_vipa_device_attrs[] = {
|
|
&dev_attr_vipa_add4,
|
|
&dev_attr_vipa_del4,
|
|
#ifdef CONFIG_QETH_IPV6
|
|
&dev_attr_vipa_add6,
|
|
&dev_attr_vipa_del6,
|
|
#endif
|
|
NULL,
|
|
};
|
|
|
|
static struct attribute_group qeth_device_vipa_group = {
|
|
.name = "vipa",
|
|
.attrs = (struct attribute **)qeth_vipa_device_attrs,
|
|
};
|
|
|
|
static ssize_t
|
|
qeth_dev_rxip_add_show(char *buf, struct qeth_card *card,
|
|
enum qeth_prot_versions proto)
|
|
{
|
|
struct qeth_ipaddr *ipaddr;
|
|
char addr_str[40];
|
|
int entry_len; /* length of 1 entry string, differs between v4 and v6 */
|
|
unsigned long flags;
|
|
int i = 0;
|
|
|
|
if (qeth_check_layer2(card))
|
|
return -EPERM;
|
|
|
|
entry_len = (proto == QETH_PROT_IPV4)? 12 : 40;
|
|
entry_len += 2; /* \n + terminator */
|
|
spin_lock_irqsave(&card->ip_lock, flags);
|
|
list_for_each_entry(ipaddr, &card->ip_list, entry){
|
|
if (ipaddr->proto != proto)
|
|
continue;
|
|
if (ipaddr->type != QETH_IP_TYPE_RXIP)
|
|
continue;
|
|
/* String must not be longer than PAGE_SIZE. So we check if
|
|
* string length gets near PAGE_SIZE. Then we can savely display
|
|
* the next IPv6 address (worst case, compared to IPv4) */
|
|
if ((PAGE_SIZE - i) <= entry_len)
|
|
break;
|
|
qeth_ipaddr_to_string(proto, (const u8 *)&ipaddr->u, addr_str);
|
|
i += snprintf(buf + i, PAGE_SIZE - i, "%s\n", addr_str);
|
|
}
|
|
spin_unlock_irqrestore(&card->ip_lock, flags);
|
|
i += snprintf(buf + i, PAGE_SIZE - i, "\n");
|
|
|
|
return i;
|
|
}
|
|
|
|
static ssize_t
|
|
qeth_dev_rxip_add4_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
return qeth_dev_rxip_add_show(buf, card, QETH_PROT_IPV4);
|
|
}
|
|
|
|
static int
|
|
qeth_parse_rxipe(const char* buf, enum qeth_prot_versions proto,
|
|
u8 *addr)
|
|
{
|
|
if (qeth_string_to_ipaddr(buf, proto, addr)){
|
|
PRINT_WARN("Invalid IP address format!\n");
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t
|
|
qeth_dev_rxip_add_store(const char *buf, size_t count,
|
|
struct qeth_card *card, enum qeth_prot_versions proto)
|
|
{
|
|
u8 addr[16] = {0, };
|
|
int rc;
|
|
|
|
if (qeth_check_layer2(card))
|
|
return -EPERM;
|
|
if ((rc = qeth_parse_rxipe(buf, proto, addr)))
|
|
return rc;
|
|
|
|
if ((rc = qeth_add_rxip(card, proto, addr)))
|
|
return rc;
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t
|
|
qeth_dev_rxip_add4_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
return qeth_dev_rxip_add_store(buf, count, card, QETH_PROT_IPV4);
|
|
}
|
|
|
|
static QETH_DEVICE_ATTR(rxip_add4, add4, 0644,
|
|
qeth_dev_rxip_add4_show,
|
|
qeth_dev_rxip_add4_store);
|
|
|
|
static ssize_t
|
|
qeth_dev_rxip_del_store(const char *buf, size_t count,
|
|
struct qeth_card *card, enum qeth_prot_versions proto)
|
|
{
|
|
u8 addr[16];
|
|
int rc;
|
|
|
|
if (qeth_check_layer2(card))
|
|
return -EPERM;
|
|
if ((rc = qeth_parse_rxipe(buf, proto, addr)))
|
|
return rc;
|
|
|
|
qeth_del_rxip(card, proto, addr);
|
|
|
|
return count;
|
|
}
|
|
|
|
static ssize_t
|
|
qeth_dev_rxip_del4_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
return qeth_dev_rxip_del_store(buf, count, card, QETH_PROT_IPV4);
|
|
}
|
|
|
|
static QETH_DEVICE_ATTR(rxip_del4, del4, 0200, NULL,
|
|
qeth_dev_rxip_del4_store);
|
|
|
|
#ifdef CONFIG_QETH_IPV6
|
|
static ssize_t
|
|
qeth_dev_rxip_add6_show(struct device *dev, struct device_attribute *attr, char *buf)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
return qeth_dev_rxip_add_show(buf, card, QETH_PROT_IPV6);
|
|
}
|
|
|
|
static ssize_t
|
|
qeth_dev_rxip_add6_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
return qeth_dev_rxip_add_store(buf, count, card, QETH_PROT_IPV6);
|
|
}
|
|
|
|
static QETH_DEVICE_ATTR(rxip_add6, add6, 0644,
|
|
qeth_dev_rxip_add6_show,
|
|
qeth_dev_rxip_add6_store);
|
|
|
|
static ssize_t
|
|
qeth_dev_rxip_del6_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (!card)
|
|
return -EINVAL;
|
|
|
|
return qeth_dev_rxip_del_store(buf, count, card, QETH_PROT_IPV6);
|
|
}
|
|
|
|
static QETH_DEVICE_ATTR(rxip_del6, del6, 0200, NULL,
|
|
qeth_dev_rxip_del6_store);
|
|
#endif /* CONFIG_QETH_IPV6 */
|
|
|
|
static struct device_attribute * qeth_rxip_device_attrs[] = {
|
|
&dev_attr_rxip_add4,
|
|
&dev_attr_rxip_del4,
|
|
#ifdef CONFIG_QETH_IPV6
|
|
&dev_attr_rxip_add6,
|
|
&dev_attr_rxip_del6,
|
|
#endif
|
|
NULL,
|
|
};
|
|
|
|
static struct attribute_group qeth_device_rxip_group = {
|
|
.name = "rxip",
|
|
.attrs = (struct attribute **)qeth_rxip_device_attrs,
|
|
};
|
|
|
|
int
|
|
qeth_create_device_attributes(struct device *dev)
|
|
{
|
|
int ret;
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (card->info.type == QETH_CARD_TYPE_OSN)
|
|
return sysfs_create_group(&dev->kobj,
|
|
&qeth_osn_device_attr_group);
|
|
|
|
if ((ret = sysfs_create_group(&dev->kobj, &qeth_device_attr_group)))
|
|
return ret;
|
|
if ((ret = sysfs_create_group(&dev->kobj, &qeth_device_ipato_group))){
|
|
sysfs_remove_group(&dev->kobj, &qeth_device_attr_group);
|
|
return ret;
|
|
}
|
|
if ((ret = sysfs_create_group(&dev->kobj, &qeth_device_vipa_group))){
|
|
sysfs_remove_group(&dev->kobj, &qeth_device_attr_group);
|
|
sysfs_remove_group(&dev->kobj, &qeth_device_ipato_group);
|
|
return ret;
|
|
}
|
|
if ((ret = sysfs_create_group(&dev->kobj, &qeth_device_rxip_group))){
|
|
sysfs_remove_group(&dev->kobj, &qeth_device_attr_group);
|
|
sysfs_remove_group(&dev->kobj, &qeth_device_ipato_group);
|
|
sysfs_remove_group(&dev->kobj, &qeth_device_vipa_group);
|
|
return ret;
|
|
}
|
|
if ((ret = sysfs_create_group(&dev->kobj, &qeth_device_blkt_group))){
|
|
sysfs_remove_group(&dev->kobj, &qeth_device_attr_group);
|
|
sysfs_remove_group(&dev->kobj, &qeth_device_ipato_group);
|
|
sysfs_remove_group(&dev->kobj, &qeth_device_vipa_group);
|
|
sysfs_remove_group(&dev->kobj, &qeth_device_rxip_group);
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
qeth_remove_device_attributes(struct device *dev)
|
|
{
|
|
struct qeth_card *card = dev->driver_data;
|
|
|
|
if (card->info.type == QETH_CARD_TYPE_OSN)
|
|
return sysfs_remove_group(&dev->kobj,
|
|
&qeth_osn_device_attr_group);
|
|
|
|
sysfs_remove_group(&dev->kobj, &qeth_device_attr_group);
|
|
sysfs_remove_group(&dev->kobj, &qeth_device_ipato_group);
|
|
sysfs_remove_group(&dev->kobj, &qeth_device_vipa_group);
|
|
sysfs_remove_group(&dev->kobj, &qeth_device_rxip_group);
|
|
sysfs_remove_group(&dev->kobj, &qeth_device_blkt_group);
|
|
}
|
|
|
|
/**********************/
|
|
/* DRIVER ATTRIBUTES */
|
|
/**********************/
|
|
static ssize_t
|
|
qeth_driver_group_store(struct device_driver *ddrv, const char *buf,
|
|
size_t count)
|
|
{
|
|
const char *start, *end;
|
|
char bus_ids[3][BUS_ID_SIZE], *argv[3];
|
|
int i;
|
|
int err;
|
|
|
|
start = buf;
|
|
for (i = 0; i < 3; i++) {
|
|
static const char delim[] = { ',', ',', '\n' };
|
|
int len;
|
|
|
|
if (!(end = strchr(start, delim[i])))
|
|
return -EINVAL;
|
|
len = min_t(ptrdiff_t, BUS_ID_SIZE, end - start);
|
|
strncpy(bus_ids[i], start, len);
|
|
bus_ids[i][len] = '\0';
|
|
start = end + 1;
|
|
argv[i] = bus_ids[i];
|
|
}
|
|
err = ccwgroup_create(qeth_root_dev, qeth_ccwgroup_driver.driver_id,
|
|
&qeth_ccw_driver, 3, argv);
|
|
if (err)
|
|
return err;
|
|
else
|
|
return count;
|
|
}
|
|
|
|
|
|
static DRIVER_ATTR(group, 0200, NULL, qeth_driver_group_store);
|
|
|
|
static ssize_t
|
|
qeth_driver_notifier_register_store(struct device_driver *ddrv, const char *buf,
|
|
size_t count)
|
|
{
|
|
int rc;
|
|
int signum;
|
|
char *tmp, *tmp2;
|
|
|
|
tmp = strsep((char **) &buf, "\n");
|
|
if (!strncmp(tmp, "unregister", 10)){
|
|
if ((rc = qeth_notifier_unregister(current)))
|
|
return rc;
|
|
return count;
|
|
}
|
|
|
|
signum = simple_strtoul(tmp, &tmp2, 10);
|
|
if ((signum < 0) || (signum > 32)){
|
|
PRINT_WARN("Signal number %d is out of range\n", signum);
|
|
return -EINVAL;
|
|
}
|
|
if ((rc = qeth_notifier_register(current, signum)))
|
|
return rc;
|
|
|
|
return count;
|
|
}
|
|
|
|
static DRIVER_ATTR(notifier_register, 0200, NULL,
|
|
qeth_driver_notifier_register_store);
|
|
|
|
int
|
|
qeth_create_driver_attributes(void)
|
|
{
|
|
int rc;
|
|
|
|
if ((rc = driver_create_file(&qeth_ccwgroup_driver.driver,
|
|
&driver_attr_group)))
|
|
return rc;
|
|
return driver_create_file(&qeth_ccwgroup_driver.driver,
|
|
&driver_attr_notifier_register);
|
|
}
|
|
|
|
void
|
|
qeth_remove_driver_attributes(void)
|
|
{
|
|
driver_remove_file(&qeth_ccwgroup_driver.driver,
|
|
&driver_attr_group);
|
|
driver_remove_file(&qeth_ccwgroup_driver.driver,
|
|
&driver_attr_notifier_register);
|
|
}
|