[PATCH] [RFC] Xilinx SystemACE: Add media hotplug support

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



From: Grant Likely <[email protected]>

Please review and comment.  This patch works in my setup, but I haven't
tested exhaustively yet.  I also need to fixup the documentation to
reflect new states before I request this patch to be merged.

Question for the block layer experts:  I'm using add_disk()/del_gendisk()
functions to inform the kernel of media insertion and removal events.  Is
this the best way to do this?  I looked at the .media_changed and
.revalidate_disk hooks, but it doesn't seem like they offer the driver
any way to notify the kernel of media removal (as opposed to the kernel
asking the driver)

Cheers,
g.
---

 drivers/block/xsysace.c |  265 ++++++++++++++++++++++++++++++++++-------------
 1 files changed, 189 insertions(+), 76 deletions(-)

diff --git a/drivers/block/xsysace.c b/drivers/block/xsysace.c
index 0cdc868..9b3df96 100644
--- a/drivers/block/xsysace.c
+++ b/drivers/block/xsysace.c
@@ -53,7 +53,7 @@
  *    The request method in particular schedules the tasklet when a new
  *    request has been indicated by the block layer.  Once started, the
  *    FSM proceeds as far as it can processing the request until it
- *    needs on a hardware event.  At this point, it must yield execution.
+ *    needs a hardware event.  At this point, it must yield execution.
  *
  *    A state has two options when yielding execution:
  *    1. ace_fsm_yield()
@@ -71,7 +71,8 @@
  *    Additionally, the driver maintains a kernel timer which can process
  *    the FSM.  If the FSM gets stalled, typically due to a missed
  *    interrupt, then the kernel timer will expire and the driver can
- *    continue where it left off.
+ *    continue where it left off.  The stall timer is also used to watch
+ *    for media removal/insertion events.
  *
  * To Do:
  *    - Add FPGA configuration control interface.
@@ -90,6 +91,7 @@
 #include <linux/slab.h>
 #include <linux/blkdev.h>
 #include <linux/hdreg.h>
+#include <linux/workqueue.h>
 #include <linux/platform_device.h>
 #if defined(CONFIG_OF)
 #include <linux/of_device.h>
@@ -170,17 +172,18 @@ struct ace_reg_ops;
 struct ace_device {
 	/* driver state data */
 	int id;
-	int media_change;
 	int users;
 	struct list_head list;
 
 	/* finite state machine data */
 	struct tasklet_struct fsm_tasklet;
+	struct work_struct fsm_worker;
 	uint fsm_task;		/* Current activity (ACE_TASK_*) */
 	uint fsm_state;		/* Current state (ACE_FSM_STATE_*) */
 	uint fsm_continue_flag;	/* cleared to exit FSM mainloop */
 	uint fsm_iter_num;
 	struct timer_list stall_timer;
+	int stall_count;
 
 	/* Transfer state/result, use for both id and block request */
 	struct request *req;	/* request being processed */
@@ -189,7 +192,6 @@ struct ace_device {
 	int data_result;	/* Result of transfer; 0 := success */
 
 	int id_req_count;	/* count of id requests */
-	int id_result;
 	struct completion id_completion;	/* used when id req finishes */
 	int in_irq;
 
@@ -212,6 +214,7 @@ struct ace_device {
 };
 
 static int ace_major;
+struct workqueue_struct *ace_workqueue;
 
 /* ---------------------------------------------------------------------
  * Low level register access
@@ -429,21 +432,24 @@ void ace_fix_driveid(struct hd_driveid *id)
 #define ACE_TASK_IDENTIFY  1
 #define ACE_TASK_READ      2
 #define ACE_TASK_WRITE     3
-#define ACE_FSM_NUM_TASKS  4
+#define ACE_NUM_TASKS      4
 
 /* FSM state definitions */
-#define ACE_FSM_STATE_IDLE               0
-#define ACE_FSM_STATE_REQ_LOCK           1
-#define ACE_FSM_STATE_WAIT_LOCK          2
-#define ACE_FSM_STATE_WAIT_CFREADY       3
-#define ACE_FSM_STATE_IDENTIFY_PREPARE   4
-#define ACE_FSM_STATE_IDENTIFY_TRANSFER  5
-#define ACE_FSM_STATE_IDENTIFY_COMPLETE  6
-#define ACE_FSM_STATE_REQ_PREPARE        7
-#define ACE_FSM_STATE_REQ_TRANSFER       8
-#define ACE_FSM_STATE_REQ_COMPLETE       9
-#define ACE_FSM_STATE_ERROR             10
-#define ACE_FSM_NUM_STATES              11
+#define ACE_FSM_STATE_NO_MEDIA           0
+#define ACE_FSM_STATE_KICKSTART          1
+#define ACE_FSM_STATE_IDLE               2
+#define ACE_FSM_STATE_REQ_LOCK           3
+#define ACE_FSM_STATE_WAIT_LOCK          4
+#define ACE_FSM_STATE_WAIT_CFREADY       5
+#define ACE_FSM_STATE_IDENTIFY_PREPARE   6
+#define ACE_FSM_STATE_IDENTIFY_TRANSFER  7
+#define ACE_FSM_STATE_IDENTIFY_COMPLETE  8
+#define ACE_FSM_STATE_REQ_PREPARE        9
+#define ACE_FSM_STATE_REQ_TRANSFER      10
+#define ACE_FSM_STATE_REQ_COMPLETE      11
+#define ACE_FSM_STATE_INVALIDATE_MEDIA  12
+#define ACE_FSM_STATE_MEDIA_DISABLED    13
+#define ACE_FSM_NUM_STATES              14
 
 /* Set flag to exit FSM loop and reschedule tasklet */
 static inline void ace_fsm_yield(struct ace_device *ace)
@@ -490,18 +496,53 @@ static void ace_fsm_dostate(struct ace_device *ace)
 		ace->fsm_state, ace->id_req_count);
 #endif
 
+	/* Before doing anything; check the CF detect bit.  If the card
+	 * is not present; then there are only a couple of states which
+	 * we can be in */
+	if ((ace_in(ace, ACE_STATUS) & ACE_STATUS_CFDETECT) == 0) {
+		/* Card is missing; short circuit to appropriate state */
+		switch (ace->fsm_state) {
+		  case ACE_FSM_STATE_NO_MEDIA:
+		  case ACE_FSM_STATE_MEDIA_DISABLED:
+			ace->fsm_state = ACE_FSM_STATE_NO_MEDIA;
+			break;
+
+		  default:
+			ace->fsm_state = ACE_FSM_STATE_INVALIDATE_MEDIA;
+		}
+	}
+
+	/* Process the next state in the FSM */
 	switch (ace->fsm_state) {
+	case ACE_FSM_STATE_NO_MEDIA:
+		/* No media inserted into the drive.  If media is inserted
+		 * then kick the workqueue.  The workqueue will cause a
+		 * transition to the IDLE state. */
+		if (ace_in(ace, ACE_STATUS) & ACE_STATUS_CFDETECT) {
+			ace->fsm_state = ACE_FSM_STATE_KICKSTART;
+			break;
+		}
+		ace->fsm_continue_flag = 0;
+		break;
+
+	case ACE_FSM_STATE_KICKSTART:
+		/* Everything looks good hardware wise; kick off the
+		 * initialization sequence.
+		 *
+		 * fsm_worker causes transition from here to the IDLE state.
+		 * The FSM cannot transition itself.
+		 */
+		queue_work(ace_workqueue, &ace->fsm_worker);
+		ace->fsm_continue_flag = 0;
+		break;
+
 	case ACE_FSM_STATE_IDLE:
 		/* See if there is anything to do */
 		if (ace->id_req_count || ace_get_next_request(ace->queue)) {
 			ace->fsm_iter_num++;
 			ace->fsm_state = ACE_FSM_STATE_REQ_LOCK;
-			mod_timer(&ace->stall_timer, jiffies + HZ);
-			if (!timer_pending(&ace->stall_timer))
-				add_timer(&ace->stall_timer);
 			break;
 		}
-		del_timer(&ace->stall_timer);
 		ace->fsm_continue_flag = 0;
 		break;
 
@@ -538,7 +579,10 @@ static void ace_fsm_dostate(struct ace_device *ace)
 			break;
 		}
 
-		/* Device is ready for command; determine what to do next */
+		/* Device is ready for command; determine what to do next.
+		 * - If a revalidate is pending, do that.
+		 * - Otherwise go to to process the next block request.
+		 */
 		if (ace->id_req_count)
 			ace->fsm_state = ACE_FSM_STATE_IDENTIFY_PREPARE;
 		else
@@ -598,22 +642,19 @@ static void ace_fsm_dostate(struct ace_device *ace)
 
 		if (ace->data_result) {
 			/* Error occured, disable the disk */
-			ace->media_change = 1;
-			set_capacity(ace->gd, 0);
 			dev_err(ace->dev, "error fetching CF id (%i)\n",
 				ace->data_result);
-		} else {
-			ace->media_change = 0;
-
-			/* Record disk parameters */
-			set_capacity(ace->gd, ace->cf_id.lba_capacity);
-			dev_info(ace->dev, "capacity: %i sectors\n",
-				 ace->cf_id.lba_capacity);
+			ace->fsm_state = ACE_FSM_STATE_INVALIDATE_MEDIA;
+			break;
 		}
 
-		/* We're done, drop to IDLE state and notify waiters */
+		/* Record disk parameters */
+		set_capacity(ace->gd, ace->cf_id.lba_capacity);
+		dev_info(ace->dev, "capacity: %i sectors\n",
+			 ace->cf_id.lba_capacity);
 		ace->fsm_state = ACE_FSM_STATE_IDLE;
-		ace->id_result = ace->data_result;
+
+		/* We're done, notify waiters */
 		while (ace->id_req_count) {
 			complete(&ace->id_completion);
 			ace->id_req_count--;
@@ -727,12 +768,90 @@ static void ace_fsm_dostate(struct ace_device *ace)
 		ace->fsm_state = ACE_FSM_STATE_IDLE;
 		break;
 
+	case ACE_FSM_STATE_INVALIDATE_MEDIA:
+		/* The media has been removed, or a fatal error has
+		 * occured.
+		 *
+		 * This state can only transition to the MEDIA_DISABLED
+		 * state; but it is not done in this state machine handler
+		 * Rather, when this state is entered, the workqueue is
+		 * kicked to deregister the gendisk and it is the workqueue
+		 * that causes the transition to the MEDIA_DISABLED state
+		 */
+
+		/* If there are any waiters, wake them up so they are not
+		 * hung on the problem */
+		while (ace->id_req_count) {
+			complete(&ace->id_completion);
+			ace->id_req_count--;
+		}
+
+		/* Schedule the worker to invalidate the block device */
+		queue_work(ace_workqueue, &ace->fsm_worker);
+		ace->fsm_continue_flag = 0;
+		break;
+
+	case ACE_FSM_STATE_MEDIA_DISABLED:
+		/* The media is no longer registered.  If the sysace reports
+		 * that the media has been removed, then we can transition
+		 * to the NO_MEDIA state.
+		 */
+		ace->fsm_continue_flag = 0;
+		break;
+
 	default:
-		ace->fsm_state = ACE_FSM_STATE_IDLE;
+		/* Something weird has happened.  Go to the catchall
+		 * exception handling state */
+		ace->fsm_state = ACE_FSM_STATE_INVALIDATE_MEDIA;
 		break;
 	}
 }
 
+static void ace_fsm_work(struct work_struct *work)
+{
+	struct ace_device *ace;
+	unsigned long flags;
+
+	ace = container_of(work, struct ace_device, fsm_worker);
+
+	/* Note on worker states: this worker can only begin processing if
+	 * the FSM is either in the KICKSTART or the INVALIDATE_MEDIA states.
+	 * In both cases, the FSM cannot transition out of the state itself.
+	 * Instead, this worker must begin processing and cause a transition
+	 * to the next state manually. */
+	if (ace->fsm_state == ACE_FSM_STATE_KICKSTART) {
+		/* Hardware is ready for initialization sequence */
+		dev_info(ace->dev, "Kickstart\n");
+		ace->fsm_state = ACE_FSM_STATE_IDLE;
+
+		spin_lock_irqsave(&ace->lock, flags);
+		ace->id_req_count++;
+		spin_unlock_irqrestore(&ace->lock, flags);
+
+		tasklet_schedule(&ace->fsm_tasklet);
+		wait_for_completion(&ace->id_completion);
+
+		/* Make the sysace device 'live' */
+		if (ace->fsm_state != ACE_FSM_STATE_INVALIDATE_MEDIA);
+			add_disk(ace->gd);
+	}
+
+	if (ace->fsm_state == ACE_FSM_STATE_INVALIDATE_MEDIA) {
+		dev_info(ace->dev, "card error/removed; Invalidating\n");
+		/* Media has been removed or an error has occured.
+		 * Unregister the block device so it can no longer be used.
+		 */
+		if (ace->gd->flags & GENHD_FL_UP)
+			del_gendisk(ace->gd);
+
+		/* All done; go to the MEDIA_DISABLED state.
+		 * This is safe to do w/o the lock because the FSM is stalled
+		 * on the INVALIDATE_MEDIA state.
+		 */
+		ace->fsm_state = ACE_FSM_STATE_MEDIA_DISABLED;
+	};
+}
+
 static void ace_fsm_tasklet(unsigned long data)
 {
 	struct ace_device *ace = (void *)data;
@@ -753,10 +872,28 @@ static void ace_stall_timer(unsigned long data)
 	struct ace_device *ace = (void *)data;
 	unsigned long flags;
 
-	dev_warn(ace->dev,
-		 "kicking stalled fsm; state=%i task=%i iter=%i dc=%i\n",
-		 ace->fsm_state, ace->fsm_task, ace->fsm_iter_num,
-		 ace->data_count);
+	/* Report if FSM stalls when we don't expect it. */
+	switch (ace->fsm_state) {
+	  case ACE_FSM_STATE_NO_MEDIA:
+	  case ACE_FSM_STATE_KICKSTART:
+	  case ACE_FSM_STATE_IDLE:
+		break;
+
+	  case ACE_FSM_STATE_INVALIDATE_MEDIA:
+	  case ACE_FSM_STATE_MEDIA_DISABLED:
+		dev_warn(ace->dev, "FSM timer: state=%i t=%i i=%i dc=%i\n",
+			 ace->fsm_state, ace->fsm_task,
+			 ace->fsm_iter_num, ace->data_count);
+
+	  default:
+		ace->stall_count++;
+		if (ace->stall_count > 2)
+			dev_warn(ace->dev, "kicking stalled FSM; "
+				           "state=%i t=%i i=%i dc=%i\n",
+				 ace->fsm_state, ace->fsm_task,
+				 ace->fsm_iter_num, ace->data_count);
+	}
+
 	spin_lock_irqsave(&ace->lock, flags);
 
 	/* Rearm the stall timer *before* entering FSM (which may then
@@ -798,6 +935,7 @@ static irqreturn_t ace_interrupt(int irq, void *dev_id)
 	/* be safe and get the lock */
 	spin_lock(&ace->lock);
 	ace->in_irq = 1;
+	ace->stall_count = 0;
 
 	/* clear the interrupt */
 	creg = ace_in(ace, ACE_CTRL);
@@ -845,36 +983,6 @@ static void ace_request(struct request_queue * q)
 	}
 }
 
-static int ace_media_changed(struct gendisk *gd)
-{
-	struct ace_device *ace = gd->private_data;
-	dev_dbg(ace->dev, "ace_media_changed(): %i\n", ace->media_change);
-
-	return ace->media_change;
-}
-
-static int ace_revalidate_disk(struct gendisk *gd)
-{
-	struct ace_device *ace = gd->private_data;
-	unsigned long flags;
-
-	dev_dbg(ace->dev, "ace_revalidate_disk()\n");
-
-	if (ace->media_change) {
-		dev_dbg(ace->dev, "requesting cf id and scheduling tasklet\n");
-
-		spin_lock_irqsave(&ace->lock, flags);
-		ace->id_req_count++;
-		spin_unlock_irqrestore(&ace->lock, flags);
-
-		tasklet_schedule(&ace->fsm_tasklet);
-		wait_for_completion(&ace->id_completion);
-	}
-
-	dev_dbg(ace->dev, "revalidate complete\n");
-	return ace->id_result;
-}
-
 static int ace_open(struct inode *inode, struct file *filp)
 {
 	struct ace_device *ace = inode->i_bdev->bd_disk->private_data;
@@ -926,8 +1034,6 @@ static struct block_device_operations ace_fops = {
 	.owner = THIS_MODULE,
 	.open = ace_open,
 	.release = ace_release,
-	.media_changed = ace_media_changed,
-	.revalidate_disk = ace_revalidate_disk,
 	.getgeo = ace_getgeo,
 };
 
@@ -954,8 +1060,9 @@ static int __devinit ace_setup(struct ace_device *ace)
 		goto err_ioremap;
 
 	/*
-	 * Initialize the state machine tasklet and stall timer
+	 * Initialize the state machine work, tasklet and stall timer
 	 */
+	INIT_WORK(&ace->fsm_worker, ace_fsm_work);
 	tasklet_init(&ace->fsm_tasklet, ace_fsm_tasklet, (unsigned long)ace);
 	setup_timer(&ace->stall_timer, ace_stall_timer, (unsigned long)ace);
 
@@ -1026,11 +1133,8 @@ static int __devinit ace_setup(struct ace_device *ace)
 	dev_dbg(ace->dev, "physaddr 0x%lx, mapped to 0x%p, irq=%i\n",
 		ace->physaddr, ace->baseaddr, ace->irq);
 
-	ace->media_change = 1;
-	ace_revalidate_disk(ace->gd);
-
-	/* Make the sysace device 'live' */
-	add_disk(ace->gd);
+	/* Kick things off */
+	mod_timer(&ace->stall_timer, jiffies + HZ);
 
 	return 0;
 
@@ -1244,6 +1348,12 @@ static int __init ace_init(void)
 {
 	int rc;
 
+	ace_workqueue = create_singlethread_workqueue("xsysace");
+	if (!ace_workqueue) {
+		rc = -ENOMEM;
+		goto err_wq;
+	}
+
 	ace_major = register_blkdev(ace_major, "xsysace");
 	if (ace_major <= 0) {
 		rc = -ENOMEM;
@@ -1267,6 +1377,8 @@ err_plat:
 err_of:
 	unregister_blkdev(ace_major, "xsysace");
 err_blk:
+	destroy_workqueue(ace_workqueue);
+err_wq:
 	printk(KERN_ERR "xsysace: registration failed; err=%i\n", rc);
 	return rc;
 }
@@ -1274,6 +1386,7 @@ err_blk:
 static void __exit ace_exit(void)
 {
 	pr_debug("Unregistering Xilinx SystemACE driver\n");
+	destroy_workqueue(ace_workqueue);
 	platform_driver_unregister(&ace_platform_driver);
 	ace_of_unregister();
 	unregister_blkdev(ace_major, "xsysace");

--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to [email protected]
More majordomo info at  http://vger.kernel.org/majordomo-info.html
Please read the FAQ at  http://www.tux.org/lkml/

[Index of Archives]     [Kernel Newbies]     [Netfilter]     [Bugtraq]     [Photo]     [Stuff]     [Gimp]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Video 4 Linux]     [Linux for the blind]     [Linux Resources]
  Powered by Linux