Commit 7c1b0372 authored by Martin Schwidefsky's avatar Martin Schwidefsky Committed by Linus Torvalds

[PATCH] s390: ccwgroups.

ccwgroup bus driver.

The ccwgroup driver contains 'ccw_group' devices, which needed because of
strange hardware properties: Some device drivers need two or more
ccw_devices to make up a logical device and the user has to configure
which ones belong together. We have not found a way to represent this
correctly using the linux driver model, the current implementation
is about as good as it gets.

Authors: Arnd Bergmann <arndb@de.ibm.com>,
parent fe11051b
...@@ -143,6 +143,7 @@ CONFIG_HOTPLUG=y ...@@ -143,6 +143,7 @@ CONFIG_HOTPLUG=y
CONFIG_LCS=m CONFIG_LCS=m
CONFIG_CTC=m CONFIG_CTC=m
CONFIG_IUCV=m CONFIG_IUCV=m
CONFIG_CCWGROUP=m
# #
# Networking options # Networking options
......
...@@ -201,6 +201,7 @@ CONFIG_HOTPLUG=y ...@@ -201,6 +201,7 @@ CONFIG_HOTPLUG=y
CONFIG_LCS=m CONFIG_LCS=m
CONFIG_CTC=m CONFIG_CTC=m
CONFIG_IUCV=m CONFIG_IUCV=m
CONFIG_CCWGROUP=m
# #
# Networking options # Networking options
......
...@@ -571,5 +571,10 @@ config IUCV ...@@ -571,5 +571,10 @@ config IUCV
vehicle networking under VM or VIF. This option is also available vehicle networking under VM or VIF. This option is also available
as a module which will be called iucv.o. If unsure, say "Y". as a module which will be called iucv.o. If unsure, say "Y".
config CCWGROUP
tristate
depends on LCS || CTC
default m if LCS!=y && CTC!=y
default y if LCS=y || CTC=y
endmenu endmenu
/*
* drivers/s390/cio/ccwgroup.c
* bus driver for ccwgroup
* $Revision: 1.5 $
*
* Copyright (C) 2002 IBM Deutschland Entwicklung GmbH,
* IBM Corporation
* Author(s): Arnd Bergmann (arndb@de.ibm.com)
*/
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/ctype.h>
#include <asm/semaphore.h>
#include <asm/ccwdev.h>
#include <asm/ccwgroup.h>
/* In Linux 2.4, we had a channel device layer called "chandev"
* that did all sorts of obscure stuff for networking devices.
* This is another driver that serves as a replacement for just
* one of its functions, namely the translation of single subchannels
* to devices that use multiple subchannels.
*/
/* a device matches a driver if all its slave devices match the same
* entry of the driver */
static int
ccwgroup_bus_match (struct device * dev, struct device_driver * drv)
{
struct ccwgroup_device *gdev;
struct ccwgroup_driver *gdrv;
gdev = container_of(dev, struct ccwgroup_device, dev);
gdrv = container_of(drv, struct ccwgroup_driver, driver);
if (gdev->creator_id == gdrv->driver_id)
return 1;
return 0;
}
static int
ccwgroup_hotplug (struct device *dev, char **envp, int num_envp, char *buffer,
int buffer_size)
{
/* TODO */
return 0;
}
static struct bus_type ccwgroup_bus_type = {
.name = "ccwgroup",
.match = ccwgroup_bus_match,
.hotplug = ccwgroup_hotplug,
};
static void
ccwgroup_release (struct device *dev)
{
struct ccwgroup_device *gdev;
int i;
gdev = to_ccwgroupdev(dev);
for (i = 0; i < gdev->count; i++)
put_device(&gdev->cdev[i]->dev);
kfree(gdev);
}
/*
* try to add a new ccwgroup device for one driver
* argc and argv[] are a list of bus_id's of devices
* belonging to the driver.
*/
int
ccwgroup_create(struct device *root,
unsigned int creator_id,
struct ccw_driver *cdrv,
int argc, char *argv[])
{
struct ccwgroup_device *gdev;
int i;
if (argc > 256) /* disallow dumb users */
return -EINVAL;
gdev = kmalloc(sizeof(*gdev) + argc*sizeof(gdev->cdev[0]), GFP_KERNEL);
if (!gdev)
return -ENOMEM;
for (i = 0; i < argc; i++) {
gdev->cdev[i] = get_ccwdev_by_busid(cdrv, argv[i]);
/* all devices have to be of the same type in
* order to be grouped */
if (!gdev->cdev[i]
|| gdev->cdev[i]->id.driver_info !=
gdev->cdev[0]->id.driver_info)
goto error;
}
*gdev = (struct ccwgroup_device) {
.creator_id = creator_id,
.count = argc,
.dev = {
.bus = &ccwgroup_bus_type,
.parent = root,
.release = &ccwgroup_release,
},
};
snprintf (gdev->dev.bus_id, BUS_ID_SIZE, "%s",
gdev->cdev[0]->dev.bus_id);
/* TODO: make symlinks for sysfs */
return device_register(&gdev->dev);
error:
for (i = 0; i < argc; i++)
if (gdev->cdev[i])
put_device(&gdev->cdev[i]->dev);
kfree(gdev);
return -EINVAL;
}
static int __init
init_ccwgroup (void)
{
return bus_register (&ccwgroup_bus_type);
}
static void __exit
cleanup_ccwgroup (void)
{
bus_unregister (&ccwgroup_bus_type);
}
module_init(init_ccwgroup);
module_exit(cleanup_ccwgroup);
/************************** driver stuff ******************************/
static int
ccwgroup_set_online(struct ccwgroup_device *gdev)
{
struct ccwgroup_driver *gdrv;
int ret;
if (gdev->state == CCWGROUP_ONLINE)
return 0;
if (!gdev->dev.driver)
return -EINVAL;
gdrv = to_ccwgroupdrv (gdev->dev.driver);
if ((ret = gdrv->set_online(gdev)))
return ret;
gdev->state = CCWGROUP_ONLINE;
return 0;
}
static int
ccwgroup_set_offline(struct ccwgroup_device *gdev)
{
struct ccwgroup_driver *gdrv;
int ret;
if (gdev->state == CCWGROUP_OFFLINE)
return 0;
if (!gdev->dev.driver)
return -EINVAL;
gdrv = to_ccwgroupdrv (gdev->dev.driver);
if ((ret = gdrv->set_offline(gdev)))
return ret;
gdev->state = CCWGROUP_OFFLINE;
return 0;
}
static ssize_t
ccwgroup_online_store (struct device *dev, const char *buf, size_t count,
loff_t off)
{
struct ccwgroup_device *gdev;
unsigned int value;
if (off)
return 0;
gdev = to_ccwgroupdev(dev);
if (!dev->driver)
return count;
value = simple_strtoul(buf, 0, 0);
if (value)
ccwgroup_set_online(gdev);
else
ccwgroup_set_offline(gdev);
return count;
}
static ssize_t
ccwgroup_online_show (struct device *dev, char *buf, size_t count, loff_t off)
{
int online;
if (off)
return 0;
online = (to_ccwgroupdev(dev)->state == CCWGROUP_ONLINE);
return snprintf(buf, count, online ? "1\n" : "0\n");
}
static DEVICE_ATTR(online, 0644, ccwgroup_online_show, ccwgroup_online_store);
static int
ccwgroup_probe (struct device *dev)
{
struct ccwgroup_device *gdev;
struct ccwgroup_driver *gdrv;
int ret;
gdev = to_ccwgroupdev(dev);
gdrv = to_ccwgroupdrv(dev->driver);
if ((ret = device_create_file(dev, &dev_attr_online)))
return ret;
pr_debug("%s: device %s\n", __func__, gdev->dev.name);
ret = gdrv->probe ? gdrv->probe(gdev) : -ENODEV;
if (ret)
device_remove_file(dev, &dev_attr_online);
return ret;
}
static int
ccwgroup_remove (struct device *dev)
{
struct ccwgroup_device *gdev;
struct ccwgroup_driver *gdrv;
int ret;
gdev = to_ccwgroupdev(dev);
gdrv = to_ccwgroupdrv(dev->driver);
pr_debug("%s: device %s\n", __func__, gdev->dev.name);
device_remove_file(dev, &dev_attr_online);
ccwgroup_set_offline(gdev);
ret = (gdrv && gdrv->remove) ? gdrv->remove(gdev) : 0;
return ret;
}
int
ccwgroup_driver_register (struct ccwgroup_driver *cdriver)
{
/* register our new driver with the core */
cdriver->driver = (struct device_driver) {
.bus = &ccwgroup_bus_type,
.name = cdriver->name,
.probe = ccwgroup_probe,
.remove = ccwgroup_remove,
};
return driver_register(&cdriver->driver);
}
void
ccwgroup_driver_unregister (struct ccwgroup_driver *cdriver)
{
driver_unregister(&cdriver->driver);
}
int
ccwgroup_probe_ccwdev(struct ccw_device *cdev)
{
return 0;
}
int
ccwgroup_remove_ccwdev(struct ccw_device *cdev)
{
return 0;
}
MODULE_LICENSE("GPL");
EXPORT_SYMBOL(ccwgroup_driver_register);
EXPORT_SYMBOL(ccwgroup_driver_unregister);
EXPORT_SYMBOL(ccwgroup_create);
EXPORT_SYMBOL(ccwgroup_probe_ccwdev);
EXPORT_SYMBOL(ccwgroup_remove_ccwdev);
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment