Commit ffedb452 authored by James Bottomley's avatar James Bottomley Committed by James Bottomley
Browse files

[SCSI] fix scsi process problems and clean up the target reap issues



In order to use the new execute_in_process_context() API, you have to
provide it with the work storage, which I do in SCSI in scsi_device and
scsi_target, but which also means that we can no longer queue up the
target reaps, so instead I moved the target to a state model which
allows target_alloc to detect if we've received a dying target and wait
for it to be gone.  Hopefully, this should also solve the target
namespace race.

Signed-off-by: default avatarJames Bottomley <James.Bottomley@SteelEye.com>
parent 1fa44eca
Loading
Loading
Loading
Loading
+0 −58
Original line number Diff line number Diff line
@@ -2257,61 +2257,3 @@ scsi_target_unblock(struct device *dev)
		device_for_each_child(dev, NULL, target_unblock);
}
EXPORT_SYMBOL_GPL(scsi_target_unblock);


struct work_queue_work {
	struct work_struct	work;
	void			(*fn)(void *);
	void			*data;
};

static void execute_in_process_context_work(void *data)
{
	void (*fn)(void *data);
	struct work_queue_work *wqw = data;

	fn = wqw->fn;
	data = wqw->data;

	kfree(wqw);

	fn(data);
}

/**
 * scsi_execute_in_process_context - reliably execute the routine with user context
 * @fn:		the function to execute
 * @data:	data to pass to the function
 *
 * Executes the function immediately if process context is available,
 * otherwise schedules the function for delayed execution.
 *
 * Returns:	0 - function was executed
 *		1 - function was scheduled for execution
 *		<0 - error
 */
int scsi_execute_in_process_context(void (*fn)(void *data), void *data)
{
	struct work_queue_work *wqw;

	if (!in_interrupt()) {
		fn(data);
		return 0;
	}

	wqw = kmalloc(sizeof(struct work_queue_work), GFP_ATOMIC);

	if (unlikely(!wqw)) {
		printk(KERN_ERR "Failed to allocate memory\n");
		WARN_ON(1);
		return -ENOMEM;
	}

	INIT_WORK(&wqw->work, execute_in_process_context_work, wqw);
	wqw->fn = fn;
	wqw->data = data;
	schedule_work(&wqw->work);

	return 1;
}
EXPORT_SYMBOL_GPL(scsi_execute_in_process_context);
+33 −16
Original line number Diff line number Diff line
@@ -349,6 +349,8 @@ static struct scsi_target *scsi_alloc_target(struct device *parent,
	starget->channel = channel;
	INIT_LIST_HEAD(&starget->siblings);
	INIT_LIST_HEAD(&starget->devices);
	starget->state = STARGET_RUNNING;
 retry:
	spin_lock_irqsave(shost->host_lock, flags);

	found_target = __scsi_find_target(parent, channel, id);
@@ -381,9 +383,16 @@ static struct scsi_target *scsi_alloc_target(struct device *parent,
	found_target->reap_ref++;
	spin_unlock_irqrestore(shost->host_lock, flags);
	put_device(parent);
	if (found_target->state != STARGET_DEL) {
		kfree(starget);
		return found_target;
	}
	/* Unfortunately, we found a dying target; need to
	 * wait until it's dead before we can get a new one */
	put_device(&found_target->dev);
	flush_scheduled_work();
	goto retry;
}

static void scsi_target_reap_usercontext(void *data)
{
@@ -391,21 +400,13 @@ static void scsi_target_reap_usercontext(void *data)
	struct Scsi_Host *shost = dev_to_shost(starget->dev.parent);
	unsigned long flags;

	spin_lock_irqsave(shost->host_lock, flags);

	if (--starget->reap_ref == 0 && list_empty(&starget->devices)) {
		list_del_init(&starget->siblings);
		spin_unlock_irqrestore(shost->host_lock, flags);
	transport_remove_device(&starget->dev);
	device_del(&starget->dev);
	transport_destroy_device(&starget->dev);
		put_device(&starget->dev);
		return;

	}
	spin_lock_irqsave(shost->host_lock, flags);
	list_del_init(&starget->siblings);
	spin_unlock_irqrestore(shost->host_lock, flags);

	return;
	put_device(&starget->dev);
}

/**
@@ -419,7 +420,23 @@ static void scsi_target_reap_usercontext(void *data)
 */
void scsi_target_reap(struct scsi_target *starget)
{
	scsi_execute_in_process_context(scsi_target_reap_usercontext, starget);
	struct Scsi_Host *shost = dev_to_shost(starget->dev.parent);
	unsigned long flags;

	spin_lock_irqsave(shost->host_lock, flags);

	if (--starget->reap_ref == 0 && list_empty(&starget->devices)) {
		BUG_ON(starget->state == STARGET_DEL);
		starget->state = STARGET_DEL;
		spin_unlock_irqrestore(shost->host_lock, flags);
		execute_in_process_context(scsi_target_reap_usercontext,
					   starget, &starget->ew);
		return;

	}
	spin_unlock_irqrestore(shost->host_lock, flags);

	return;
}

/**
+3 −1
Original line number Diff line number Diff line
@@ -256,7 +256,9 @@ static void scsi_device_dev_release_usercontext(void *data)

static void scsi_device_dev_release(struct device *dev)
{
	scsi_execute_in_process_context(scsi_device_dev_release_usercontext,	dev);
	struct scsi_device *sdp = to_scsi_device(dev);
	execute_in_process_context(scsi_device_dev_release_usercontext, dev,
				   &sdp->ew);
}

static struct class sdev_class = {
+0 −2
Original line number Diff line number Diff line
@@ -433,6 +433,4 @@ struct scsi_lun {
/* Used to obtain the PCI location of a device */
#define SCSI_IOCTL_GET_PCI		0x5387

int scsi_execute_in_process_context(void (*fn)(void *data), void *data);

#endif /* _SCSI_SCSI_H */
+10 −0
Original line number Diff line number Diff line
@@ -4,6 +4,7 @@
#include <linux/device.h>
#include <linux/list.h>
#include <linux/spinlock.h>
#include <linux/workqueue.h>
#include <asm/atomic.h>

struct request_queue;
@@ -137,6 +138,8 @@ struct scsi_device {
	struct device		sdev_gendev;
	struct class_device	sdev_classdev;

	struct execute_work	ew; /* used to get process context on put */

	enum scsi_device_state sdev_state;
	unsigned long		sdev_data[0];
} __attribute__((aligned(sizeof(unsigned long))));
@@ -153,6 +156,11 @@ struct scsi_device {
#define scmd_printk(prefix, scmd, fmt, a...)	\
	dev_printk(prefix, &(scmd)->device->sdev_gendev, fmt, ##a)

enum scsi_target_state {
	STARGET_RUNNING = 1,
	STARGET_DEL,
};

/*
 * scsi_target: representation of a scsi target, for now, this is only
 * used for single_lun devices. If no one has active IO to the target,
@@ -172,6 +180,8 @@ struct scsi_target {
						/* means no lun present */

	char			scsi_level;
	struct execute_work	ew;
	enum scsi_target_state	state;
	void 			*hostdata; /* available to low-level driver */
	unsigned long		starget_data[0]; /* for the transport */
	/* starget_data must be the last element!!!! */