/*
* Driver for s390 chsc subchannels
*
* Copyright IBM Corp. 2008, 2011
*
* Author(s): Cornelia Huck <cornelia.huck@de.ibm.com>
*
*/
#include <linux/slab.h>
#include <linux/compat.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/uaccess.h>
#include <linux/miscdevice.h>
#include <linux/kernel_stat.h>
#include <asm/compat.h>
#include <asm/cio.h>
#include <asm/chsc.h>
#include <asm/isc.h>
#include "cio.h"
#include "cio_debug.h"
#include "css.h"
#include "chsc_sch.h"
#include "ioasm.h"
static debug_info_t *chsc_debug_msg_id;
static debug_info_t *chsc_debug_log_id;
static struct chsc_request *on_close_request;
static struct chsc_async_area *on_close_chsc_area;
static DEFINE_MUTEX(on_close_mutex);
#define CHSC_MSG(imp, args...) do { \
debug_sprintf_event(chsc_debug_msg_id, imp , ##args); \
} while (0)
#define CHSC_LOG(imp, txt) do { \
debug_text_event(chsc_debug_log_id, imp , txt); \
} while (0)
static void CHSC_LOG_HEX(int level, void *data, int length)
{
while (length > 0) {
debug_event(chsc_debug_log_id, level, data, length);
length -= chsc_debug_log_id->buf_size;
data += chsc_debug_log_id->buf_size;
}
}
MODULE_AUTHOR("IBM Corporation");
MODULE_DESCRIPTION("driver for s390 chsc subchannels");
MODULE_LICENSE("GPL");
static void chsc_subchannel_irq(struct subchannel *sch)
{
struct chsc_private *private = dev_get_drvdata(&sch->dev);
struct chsc_request *request = private->request;
struct irb *irb = (struct irb *)&S390_lowcore.irb;
CHSC_LOG(4, "irb");
CHSC_LOG_HEX(4, irb, sizeof(*irb));
inc_irq_stat(IRQIO_CSC);
/* Copy irb to provided request and set done. */
if (!request) {
CHSC_MSG(0, "Interrupt on sch 0.%x.%04x with no request\n",
sch->schid.ssid, sch->schid.sch_no);
return;
}
private->request = NULL;
memcpy(&request->irb, irb, sizeof(*irb));
cio_update_schib(sch);
complete(&request->completion);
put_device(&sch->dev);
}
static int chsc_subchannel_probe(struct subchannel *sch)
{
struct chsc_private *private;
int ret;
CHSC_MSG(6, "Detected chsc subchannel 0.%x.%04x\n",
sch->schid.ssid, sch->schid.sch_no);
sch->isc = CHSC_SCH_ISC;
private = kzalloc(sizeof(*private), GFP_KERNEL);
if (!private)
return -ENOMEM;
dev_set_drvdata(&sch->dev, private);
ret = cio_enable_subchannel(sch, (u32)(unsigned long)sch);
if (ret) {
CHSC_MSG(0, "Failed to enable 0.%x.%04x: %d\n",
sch->schid.ssid, sch->schid.sch_no, ret);
dev_set_drvdata(&sch->dev, NULL);
kfree(private);
} else {
if (dev_get_uevent_suppress(&sch->dev)) {
dev_set_uevent_suppress(&sch->dev, 0);
kobject_uevent(&sch->dev.kobj, KOBJ_ADD);
}
}
return ret;
}
static int chsc_subchannel_remove(struct subchannel *sch)
{
struct chsc_private *private;
cio_disable_subchannel(sch);
private = dev_get_drvdata(&sch->dev);
dev_set_drvdata(&sch->dev, NULL);
if (private->request) {
complete(&private->request->completion);
put_device(&sch->dev);
}
kfree(private);
return 0;
}
static void chsc_subchannel_shutdown(struct subchannel *sch)
{
cio_disable_subchannel(sch);
}
static int chsc_subchannel_prepare(struct subchannel *sch)
{
int cc;
struct schib schib;
/*
* Don't allow suspend while the subchannel is not idle
* since we don't have a way to clear the subchannel and
* cannot disable it with a request running.
*/
cc = stsch_err(sch->schid, &schib);
if (!cc && scsw_stctl(&schib.scsw))
return -EAGAIN;
return 0;
}
static int chsc_subchannel_freeze(struct subchannel *sch)
{
return cio_disable_subchannel(sch);
}