Skip to content

Commit

Permalink
[S390] tape: deadlock on system work queue
Browse files Browse the repository at this point in the history
The 34xx and 3590 tape driver uses the system work queue to defer work
from the interrupt function to process context, e.g. a medium sense
after an unsolicited interrupt. The tape commands started by the work
handler need to be asynchronous, otherwise a deadlock on the system
work queue can occur.

Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
  • Loading branch information
Martin Schwidefsky authored and Martin Schwidefsky committed Mar 3, 2011
1 parent b652277 commit 0c2bd9b
Show file tree
Hide file tree
Showing 3 changed files with 116 additions and 34 deletions.
8 changes: 8 additions & 0 deletions drivers/s390/char/tape.h
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,14 @@ tape_do_io_free(struct tape_device *device, struct tape_request *request)
return rc;
}

static inline void
tape_do_io_async_free(struct tape_device *device, struct tape_request *request)
{
request->callback = (void *) tape_free_request;
request->callback_data = NULL;
tape_do_io_async(device, request);
}

extern int tape_oper_handler(int irq, int status);
extern void tape_noper_handler(int irq, int status);
extern int tape_open(struct tape_device *);
Expand Down
59 changes: 41 additions & 18 deletions drivers/s390/char/tape_34xx.c
Original file line number Diff line number Diff line change
Expand Up @@ -53,23 +53,11 @@ static void tape_34xx_delete_sbid_from(struct tape_device *, int);
* Medium sense for 34xx tapes. There is no 'real' medium sense call.
* So we just do a normal sense.
*/
static int
tape_34xx_medium_sense(struct tape_device *device)
static void __tape_34xx_medium_sense(struct tape_request *request)
{
struct tape_request *request;
unsigned char *sense;
int rc;

request = tape_alloc_request(1, 32);
if (IS_ERR(request)) {
DBF_EXCEPTION(6, "MSEN fail\n");
return PTR_ERR(request);
}

request->op = TO_MSEN;
tape_ccw_end(request->cpaddr, SENSE, 32, request->cpdata);
struct tape_device *device = request->device;
unsigned char *sense;

rc = tape_do_io_interruptible(device, request);
if (request->rc == 0) {
sense = request->cpdata;

Expand All @@ -88,15 +76,47 @@ tape_34xx_medium_sense(struct tape_device *device)
device->tape_generic_status |= GMT_WR_PROT(~0);
else
device->tape_generic_status &= ~GMT_WR_PROT(~0);
} else {
} else
DBF_EVENT(4, "tape_34xx: medium sense failed with rc=%d\n",
request->rc);
}
tape_free_request(request);
}

static int tape_34xx_medium_sense(struct tape_device *device)
{
struct tape_request *request;
int rc;

request = tape_alloc_request(1, 32);
if (IS_ERR(request)) {
DBF_EXCEPTION(6, "MSEN fail\n");
return PTR_ERR(request);
}

request->op = TO_MSEN;
tape_ccw_end(request->cpaddr, SENSE, 32, request->cpdata);
rc = tape_do_io_interruptible(device, request);
__tape_34xx_medium_sense(request);
return rc;
}

static void tape_34xx_medium_sense_async(struct tape_device *device)
{
struct tape_request *request;

request = tape_alloc_request(1, 32);
if (IS_ERR(request)) {
DBF_EXCEPTION(6, "MSEN fail\n");
return;
}

request->op = TO_MSEN;
tape_ccw_end(request->cpaddr, SENSE, 32, request->cpdata);
request->callback = (void *) __tape_34xx_medium_sense;
request->callback_data = NULL;
tape_do_io_async(device, request);
}

struct tape_34xx_work {
struct tape_device *device;
enum tape_op op;
Expand All @@ -109,6 +129,9 @@ struct tape_34xx_work {
* is inserted but cannot call tape_do_io* from an interrupt context.
* Maybe that's useful for other actions we want to start from the
* interrupt handler.
* Note: the work handler is called by the system work queue. The tape
* commands started by the handler need to be asynchrounous, otherwise
* a deadlock can occur e.g. in case of a deferred cc=1 (see __tape_do_irq).
*/
static void
tape_34xx_work_handler(struct work_struct *work)
Expand All @@ -119,7 +142,7 @@ tape_34xx_work_handler(struct work_struct *work)

switch(p->op) {
case TO_MSEN:
tape_34xx_medium_sense(device);
tape_34xx_medium_sense_async(device);
break;
default:
DBF_EVENT(3, "T34XX: internal error: unknown work\n");
Expand Down
83 changes: 67 additions & 16 deletions drivers/s390/char/tape_3590.c
Original file line number Diff line number Diff line change
Expand Up @@ -329,17 +329,17 @@ static int tape_3592_ioctl_kekl_set(struct tape_device *device,
/*
* Enable encryption
*/
static int tape_3592_enable_crypt(struct tape_device *device)
static struct tape_request *__tape_3592_enable_crypt(struct tape_device *device)
{
struct tape_request *request;
char *data;

DBF_EVENT(6, "tape_3592_enable_crypt\n");
if (!crypt_supported(device))
return -ENOSYS;
return ERR_PTR(-ENOSYS);
request = tape_alloc_request(2, 72);
if (IS_ERR(request))
return PTR_ERR(request);
return request;
data = request->cpdata;
memset(data,0,72);

Expand All @@ -354,23 +354,42 @@ static int tape_3592_enable_crypt(struct tape_device *device)
request->op = TO_CRYPT_ON;
tape_ccw_cc(request->cpaddr, MODE_SET_CB, 36, data);
tape_ccw_end(request->cpaddr + 1, MODE_SET_CB, 36, data + 36);
return request;
}

static int tape_3592_enable_crypt(struct tape_device *device)
{
struct tape_request *request;

request = __tape_3592_enable_crypt(device);
if (IS_ERR(request))
return PTR_ERR(request);
return tape_do_io_free(device, request);
}

static void tape_3592_enable_crypt_async(struct tape_device *device)
{
struct tape_request *request;

request = __tape_3592_enable_crypt(device);
if (!IS_ERR(request))
tape_do_io_async_free(device, request);
}

/*
* Disable encryption
*/
static int tape_3592_disable_crypt(struct tape_device *device)
static struct tape_request *__tape_3592_disable_crypt(struct tape_device *device)
{
struct tape_request *request;
char *data;

DBF_EVENT(6, "tape_3592_disable_crypt\n");
if (!crypt_supported(device))
return -ENOSYS;
return ERR_PTR(-ENOSYS);
request = tape_alloc_request(2, 72);
if (IS_ERR(request))
return PTR_ERR(request);
return request;
data = request->cpdata;
memset(data,0,72);

Expand All @@ -383,9 +402,28 @@ static int tape_3592_disable_crypt(struct tape_device *device)
tape_ccw_cc(request->cpaddr, MODE_SET_CB, 36, data);
tape_ccw_end(request->cpaddr + 1, MODE_SET_CB, 36, data + 36);

return request;
}

static int tape_3592_disable_crypt(struct tape_device *device)
{
struct tape_request *request;

request = __tape_3592_disable_crypt(device);
if (IS_ERR(request))
return PTR_ERR(request);
return tape_do_io_free(device, request);
}

static void tape_3592_disable_crypt_async(struct tape_device *device)
{
struct tape_request *request;

request = __tape_3592_disable_crypt(device);
if (!IS_ERR(request))
tape_do_io_async_free(device, request);
}

/*
* IOCTL: Set encryption status
*/
Expand Down Expand Up @@ -457,8 +495,7 @@ tape_3590_ioctl(struct tape_device *device, unsigned int cmd, unsigned long arg)
/*
* SENSE Medium: Get Sense data about medium state
*/
static int
tape_3590_sense_medium(struct tape_device *device)
static int tape_3590_sense_medium(struct tape_device *device)
{
struct tape_request *request;

Expand All @@ -470,6 +507,18 @@ tape_3590_sense_medium(struct tape_device *device)
return tape_do_io_free(device, request);
}

static void tape_3590_sense_medium_async(struct tape_device *device)
{
struct tape_request *request;

request = tape_alloc_request(1, 128);
if (IS_ERR(request))
return;
request->op = TO_MSEN;
tape_ccw_end(request->cpaddr, MEDIUM_SENSE, 128, request->cpdata);
tape_do_io_async_free(device, request);
}

/*
* MTTELL: Tell block. Return the number of block relative to current file.
*/
Expand Down Expand Up @@ -546,28 +595,30 @@ tape_3590_read_opposite(struct tape_device *device,
* 2. The attention msg is written to the "read subsystem data" buffer.
* In this case we probably should print it to the console.
*/
static int
tape_3590_read_attmsg(struct tape_device *device)
static void tape_3590_read_attmsg_async(struct tape_device *device)
{
struct tape_request *request;
char *buf;

request = tape_alloc_request(3, 4096);
if (IS_ERR(request))
return PTR_ERR(request);
return;
request->op = TO_READ_ATTMSG;
buf = request->cpdata;
buf[0] = PREP_RD_SS_DATA;
buf[6] = RD_ATTMSG; /* read att msg */
tape_ccw_cc(request->cpaddr, PERFORM_SS_FUNC, 12, buf);
tape_ccw_cc(request->cpaddr + 1, READ_SS_DATA, 4096 - 12, buf + 12);
tape_ccw_end(request->cpaddr + 2, NOP, 0, NULL);
return tape_do_io_free(device, request);
tape_do_io_async_free(device, request);
}

/*
* These functions are used to schedule follow-up actions from within an
* interrupt context (like unsolicited interrupts).
* Note: the work handler is called by the system work queue. The tape
* commands started by the handler need to be asynchrounous, otherwise
* a deadlock can occur e.g. in case of a deferred cc=1 (see __tape_do_irq).
*/
struct work_handler_data {
struct tape_device *device;
Expand All @@ -583,16 +634,16 @@ tape_3590_work_handler(struct work_struct *work)

switch (p->op) {
case TO_MSEN:
tape_3590_sense_medium(p->device);
tape_3590_sense_medium_async(p->device);
break;
case TO_READ_ATTMSG:
tape_3590_read_attmsg(p->device);
tape_3590_read_attmsg_async(p->device);
break;
case TO_CRYPT_ON:
tape_3592_enable_crypt(p->device);
tape_3592_enable_crypt_async(p->device);
break;
case TO_CRYPT_OFF:
tape_3592_disable_crypt(p->device);
tape_3592_disable_crypt_async(p->device);
break;
default:
DBF_EVENT(3, "T3590: work handler undefined for "
Expand Down

0 comments on commit 0c2bd9b

Please sign in to comment.