lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [day] [month] [year] [list]
Message-id: <1315395437-25838-1-git-send-email-eli.billauer@gmail.com>
Date:	Wed, 07 Sep 2011 14:37:17 +0300
From:	Eli Billauer <eli.billauer@...il.com>
To:	linux-kernel@...r.kernel.org
Cc:	grant.likely@...retlab.ca, Eli Billauer <eli.billauer@...il.com>
Subject: [PATCH] xsysace.c fixed to run reliably with 8-bit interface

It all started with the CompactFlash on my Xilinx SP605 board being horribly
unreliable. After a lot of work, it turned out that the problem is that
the System ACE chip (or its Xilinx core) sometimes ignores the first out_8()
in a series of writes (including the LSB of a 16-bit word in 8 bit mode).

This is worked around simply by a harmless dummy write before any series of
writes to the chip's register space. It's effective only in 8-bit mode.

With this patch, I have error-less access on the Compact Flash using an
ext2 filesystem. It has been tested with 150MB of data written and read back.
Before it, I was lucky if I managed to mount the filesystem even as read only.

The immediate result of the hardware problem was that sectors were read and
written from the wrong place every now and then. I've made quite some changes
to the driver, some of which are directly related to this problem, and some
are just a matter of being pedantic with the datasheet.

As I was trying to figure out what was wrong, I made quite a few changes in
the driver. Some of them are just a matter of being pedantic (e.g. following
the datasheet exactly) and others had to do with making the driver react
gracefully to things going horribly wrong, as necessary until I came up with
that simple workaround. Since the solution is quirky, I decided to leave
these now apparently unnecessary changes, as they have a low impact on
performance, and the nature of the hardware bug worked around isn't clear.

Here's a summary of the changes:

* The workaround mentioned above.

* Readback on the ACE_MPULBA register immediately after writing to it, and
  aborting if a wrong value is found. This is a silly check, but before the
  workaround, the register wasn't updated with the desired value every
  now and then (1:100 or so) causing the driver to read or write to the wrong
  sector. This problem was in fact what made me start working on this.
  Yup, this is very odd, but this readback check kicks in often enough.

* In order to support this, there are some changes in the FSM's logic,
  and an ACE_FSM_STATE_ABORT state was introduced. This state is also assigned
  when the a stalled FSM gets a timer kick. It's harmless, since the FSM's
  new logic keeps trying handling the same set of sectors until successful.
  Same goes for failed transfers, as indicated by interrupt.

* The reset and lock are now released after each transfer, as required by
  spec.

* The check for the existence of a CF card was removed, since it caused
  nasty false alarms under stress testing. This happened most likely while
  the FSM was in the middle of aborting something.

* Each __blk_end_request_cur() is seen as an independent request, and no
  continuity in sector numbers is assumed, even though it's that way in
  practice. I don't know if this is needed, but once implemented, it's a
  small overhead and more robust.

* The deprecated of_property_read_u32() call was replaced with
  of_get_property(). Default application was corrected as well.

* The ACE_BUF_PER_SECTOR define is used at all places applicable (a correct
  constant appeared in some places)

Signed-off-by: Eli Billauer <eli.billauer@...il.com>
---
 drivers/block/xsysace.c |  197 ++++++++++++++++++++++++++++++++--------------
 1 files changed, 137 insertions(+), 60 deletions(-)

diff --git a/drivers/block/xsysace.c b/drivers/block/xsysace.c
index fb1975d..011d299 100644
--- a/drivers/block/xsysace.c
+++ b/drivers/block/xsysace.c
@@ -238,6 +238,15 @@ static u16 ace_in_8(struct ace_device *ace, int reg)
 static void ace_out_8(struct ace_device *ace, int reg, u16 val)
 {
 	void __iomem *r = ace->baseaddr + reg;
+	void __iomem *dummy = ace->baseaddr + ACE_BUSMODE;
+
+	/*
+	 * This is just a dummy write which works around some hardware bug.
+	 * Without this, some writes are missed by the System ACE chip.
+	 */
+
+	out_8(dummy, 0); /* Set bus to 8 bit (it's already...) */
+
 	out_8(r, val);
 	out_8(r + 1, val >> 8);
 }
@@ -254,9 +263,17 @@ static void ace_datain_8(struct ace_device *ace)
 
 static void ace_dataout_8(struct ace_device *ace)
 {
-	void __iomem *r = ace->baseaddr + 0x40;
+	void __iomem *r = ace->baseaddr + 0x40;
+	void __iomem *dummy = ace->baseaddr + ACE_BUSMODE;
 	u8 *src = ace->data_ptr;
 	int i = ACE_FIFO_SIZE;
+
+	/*
+	 * This is just a dummy write which works around some hardware bug.
+	 * Without this, some writes are missed by the System ACE chip.
+	 */
+	out_8(dummy, 0); /* Set bus to 8 bit (it's already...) */
+
 	while (i--)
 		out_8(r++, *src++);
 	ace->data_ptr = src;
@@ -439,9 +456,10 @@ void ace_fix_driveid(u16 *id)
 #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_UNLOCK       9
 #define ACE_FSM_STATE_ERROR             10
-#define ACE_FSM_NUM_STATES              11
+#define ACE_FSM_STATE_ABORT             11
+#define ACE_FSM_NUM_STATES              12
 
 /* Set flag to exit FSM loop and reschedule tasklet */
 static inline void ace_fsm_yield(struct ace_device *ace)
@@ -487,37 +505,11 @@ static void ace_fsm_dostate(struct ace_device *ace)
 	dev_dbg(ace->dev, "fsm_state=%i, id_req_count=%i\n",
 		ace->fsm_state, ace->id_req_count);
 #endif
-
-	/* Verify that there is actually a CF in the slot. If not, then
-	 * bail out back to the idle state and wake up all the waiters */
-	status = ace_in32(ace, ACE_STATUS);
-	if ((status & ACE_STATUS_CFDETECT) == 0) {
-		ace->fsm_state = ACE_FSM_STATE_IDLE;
-		ace->media_change = 1;
-		set_capacity(ace->gd, 0);
-		dev_info(ace->dev, "No CF in slot\n");
-
-		/* Drop all in-flight and pending requests */
-		if (ace->req) {
-			__blk_end_request_all(ace->req, -EIO);
-			ace->req = NULL;
-		}
-		while ((req = blk_fetch_request(ace->queue)) != NULL)
-			__blk_end_request_all(req, -EIO);
-
-		/* Drop back to IDLE state and notify waiters */
-		ace->fsm_state = ACE_FSM_STATE_IDLE;
-		ace->id_result = -EIO;
-		while (ace->id_req_count) {
-			complete(&ace->id_completion);
-			ace->id_req_count--;
-		}
-	}
-
 	switch (ace->fsm_state) {
 	case ACE_FSM_STATE_IDLE:
 		/* See if there is anything to do */
-		if (ace->id_req_count || ace_get_next_request(ace->queue)) {
+		if (ace->req ||
+		    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);
@@ -646,26 +638,41 @@ static void ace_fsm_dostate(struct ace_device *ace)
 		break;
 
 	case ACE_FSM_STATE_REQ_PREPARE:
-		req = ace_get_next_request(ace->queue);
-		if (!req) {
-			ace->fsm_state = ACE_FSM_STATE_IDLE;
-			break;
-		}
-		blk_start_request(req);
+		if (!ace->req) {
+			req = ace_get_next_request(ace->queue);
+			if (!req) {
+				ace->fsm_state = ACE_FSM_STATE_IDLE;
+				break;
+			}
+			blk_start_request(req);
+
+			dev_dbg(ace->dev,
+				"request: sec=%llx hcnt=%x, ccnt=%x, dir=%i\n",
+				(unsigned long long)blk_rq_pos(req),
+				blk_rq_sectors(req), blk_rq_cur_sectors(req),
+				rq_data_dir(req));
 
-		/* Okay, it's a data request, set it up for transfer */
-		dev_dbg(ace->dev,
-			"request: sec=%llx hcnt=%x, ccnt=%x, dir=%i\n",
-			(unsigned long long)blk_rq_pos(req),
-			blk_rq_sectors(req), blk_rq_cur_sectors(req),
-			rq_data_dir(req));
+			ace->req = req;
+		} else {
+			req = ace->req;
+		}
 
-		ace->req = req;
 		ace->data_ptr = req->buffer;
 		ace->data_count = blk_rq_cur_sectors(req) * ACE_BUF_PER_SECTOR;
 		ace_out32(ace, ACE_MPULBA, blk_rq_pos(req) & 0x0FFFFFFF);
 
-		count = blk_rq_sectors(req);
+		if (ace_in32(ace, ACE_MPULBA) != blk_rq_pos(req)) {
+			dev_dbg(ace->dev,
+				"ACE_MPULBA = %d != %d = blk_rq_pos(req)\n",
+				ace_in32(ace, ACE_MPULBA),
+				(u32) blk_rq_pos(req));
+
+			ace->fsm_state = ACE_FSM_STATE_ABORT;
+			break;
+		}
+
+		count = blk_rq_cur_sectors(req);
+
 		if (rq_data_dir(req)) {
 			/* Kick off write request */
 			dev_dbg(ace->dev, "write data\n");
@@ -699,7 +706,8 @@ static void ace_fsm_dostate(struct ace_device *ace)
 			dev_dbg(ace->dev,
 				"CFBSY set; t=%i iter=%i c=%i dc=%i irq=%i\n",
 				ace->fsm_task, ace->fsm_iter_num,
-				blk_rq_cur_sectors(ace->req) * 16,
+				blk_rq_cur_sectors(ace->req) *
+				ACE_BUF_PER_SECTOR,
 				ace->data_count, ace->in_irq);
 			ace_fsm_yield(ace);	/* need to poll CFBSY bit */
 			break;
@@ -708,7 +716,8 @@ static void ace_fsm_dostate(struct ace_device *ace)
 			dev_dbg(ace->dev,
 				"DATABUF not set; t=%i iter=%i c=%i dc=%i irq=%i\n",
 				ace->fsm_task, ace->fsm_iter_num,
-				blk_rq_cur_sectors(ace->req) * 16,
+				blk_rq_cur_sectors(ace->req) *
+				ACE_BUF_PER_SECTOR,
 				ace->data_count, ace->in_irq);
 			ace_fsm_yieldirq(ace);
 			break;
@@ -721,7 +730,10 @@ static void ace_fsm_dostate(struct ace_device *ace)
 			ace->reg_ops->datain(ace);
 		ace->data_count--;
 
-		/* If there are still buffers to be transfers; jump out here */
+		/*
+		 * If there are still buffers to be transferred,
+		 * jump out here
+		 */
 		if (ace->data_count != 0) {
 			ace_fsm_yieldirq(ace);
 			break;
@@ -733,22 +745,68 @@ static void ace_fsm_dostate(struct ace_device *ace)
 			 *      blk_rq_sectors(ace->req),
 			 *      blk_rq_cur_sectors(ace->req));
 			 */
-			ace->data_ptr = ace->req->buffer;
-			ace->data_count = blk_rq_cur_sectors(ace->req) * 16;
-			ace_fsm_yieldirq(ace);
+
+			/*
+			 * Going to IDLE effectively means starting over
+			 * immediately with the next chunk, because ace->req
+			 * is non-NULL.
+			 */
+
+			ace->fsm_state = ACE_FSM_STATE_UNLOCK;
 			break;
 		}
 
-		ace->fsm_state = ACE_FSM_STATE_REQ_COMPLETE;
+		ace->req = NULL;
+		ace->fsm_state = ACE_FSM_STATE_UNLOCK;
 		break;
 
-	case ACE_FSM_STATE_REQ_COMPLETE:
-		ace->req = NULL;
+	case ACE_FSM_STATE_UNLOCK:
+		/* The datasheet requires clearing reset and then unlock */
+
+		val = ace_in(ace, ACE_CTRL);
+		ace_out(ace, ACE_CTRL, val & ~ACE_CTRL_CFGRESET);
 
-		/* Finished request; go to idle state */
+		val = ace_in(ace, ACE_CTRL);
+		ace_out(ace, ACE_CTRL,
+			val & ~ACE_CTRL_LOCKREQ & ~ACE_CTRL_FORCELOCKREQ);
+
 		ace->fsm_state = ACE_FSM_STATE_IDLE;
 		break;
 
+	case ACE_FSM_STATE_ABORT:
+		/* Take the lock by force if necessary. */
+
+		if (!(ace_in(ace, ACE_STATUS) & ACE_STATUS_MPULOCK)) {
+			val = ace_in(ace, ACE_CTRL);
+			ace_out(ace, ACE_CTRL, val | ACE_CTRL_FORCELOCKREQ);
+		}
+
+		/*
+		 * If there's still data in the buffer, it has to be
+		 * drained, or the chip will refuse to give the lock
+		 * again later on. Since we can reach this point from
+		 * basically anywhere, it's safest to use a dedicated
+		 * 32-byte buffer.
+		 */
+
+		if (ace_in(ace, ACE_STATUS) & ACE_STATUS_DATABUFRDY) {
+			u16 junkbuffer[ACE_FIFO_SIZE/2];
+
+			ace->data_ptr = junkbuffer;
+
+			if (ace_in(ace, ACE_STATUS) & ACE_STATUS_DATABUFMODE)
+				ace->reg_ops->dataout(ace);
+			else
+				ace->reg_ops->datain(ace);
+
+			ace_fsm_yield(ace);
+			break;
+		}
+
+		ace_out(ace, ACE_SECCNTCMD, ACE_SECCNTCMD_ABORT);
+		ace->fsm_state = ACE_FSM_STATE_UNLOCK;
+		break;
+
 	default:
 		ace->fsm_state = ACE_FSM_STATE_IDLE;
 		break;
@@ -779,12 +837,20 @@ static void ace_stall_timer(unsigned long data)
 		 "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);
+
 	spin_lock_irqsave(&ace->lock, flags);
 
 	/* Rearm the stall timer *before* entering FSM (which may then
 	 * delete the timer) */
 	mod_timer(&ace->stall_timer, jiffies + HZ);
 
+	/*
+	 * Attempt to abort the current operation. Since ace->req doesn't
+	 * change, and is taken into account in the IDLE state, this
+	 * aggressive measure is not expected to cause any data loss.
+	 */
+	ace->fsm_state = ACE_FSM_STATE_ABORT;
+
 	/* Loop over state machine until told to stop */
 	ace->fsm_continue_flag = 1;
 	while (ace->fsm_continue_flag)
@@ -804,8 +870,11 @@ static int ace_interrupt_checkstate(struct ace_device *ace)
 	/* Check for error occurrence */
 	if ((sreg & (ACE_STATUS_CFGERROR | ACE_STATUS_CFCERROR)) &&
 	    (creg & ACE_CTRL_ERRORIRQ)) {
-		dev_err(ace->dev, "transfer failure\n");
+		dev_err(ace->dev, "Transfer failure.\n");
 		ace_dump_regs(ace);
+
+		ace->fsm_state = ACE_FSM_STATE_ABORT;
+
 		return -EIO;
 	}
 
@@ -928,7 +997,8 @@ static int ace_release(struct gendisk *disk, fmode_t mode)
 	ace->users--;
 	if (ace->users == 0) {
 		val = ace_in(ace, ACE_CTRL);
-		ace_out(ace, ACE_CTRL, val & ~ACE_CTRL_LOCKREQ);
+		ace_out(ace, ACE_CTRL,
+			val & ~ACE_CTRL_LOCKREQ & ~ACE_CTRL_FORCELOCKREQ);
 	}
 	spin_unlock_irqrestore(&ace->lock, flags);
 	mutex_unlock(&xsysace_mutex);
@@ -1056,6 +1126,7 @@ static int __devinit ace_setup(struct ace_device *ace)
 
 	ace->media_change = 1;
 	ace_revalidate_disk(ace->gd);
+	ace->req = NULL;
 
 	/* Make the sysace device 'live' */
 	add_disk(ace->gd);
@@ -1155,16 +1226,22 @@ static int __devinit ace_probe(struct platform_device *dev)
 {
 	resource_size_t physaddr = 0;
 	int bus_width = ACE_BUS_WIDTH_16; /* FIXME: should not be hard coded */
-	u32 id = dev->id;
+	u32 id;
+	const u32 *id_p;
 	int irq = NO_IRQ;
 	int i;
 
 	dev_dbg(&dev->dev, "ace_probe(%p)\n", dev);
 
 	/* device id and bus width */
-	of_property_read_u32(dev->dev.of_node, "port-number", &id);
-	if (id < 0)
+
+	id_p = of_get_property(dev->dev.of_node, "port-number", NULL);
+
+	if (id_p)
+		id = be32_to_cpu(*id_p);
+	else
 		id = 0;
+
 	if (of_find_property(dev->dev.of_node, "8-bit", NULL))
 		bus_width = ACE_BUS_WIDTH_8;
 
-- 
1.7.2.3

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

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ