Subject: Problem with large IDE disk on a MIPS bigendian system.

From: Carsten Langgaard <[email-masked]>
Date: Fri, 20 Apr 2001 13:35:27 +0200

I have noticed that a bigendian kernel can't quite handled IDE disk,
which is larger than 8GB and support LBA mode (logical addressing mode).

The IDE driver read some of the data incorrectly and therefore doesn't
recognize that the drive support LBA mode, and therefore assume that the
drive use CHS mode (physical addressing mode). The ATA spec tells large
drives to return C/H/S = 16383/16/63 independent of their size, which in
this case result in the IDE drive believe that the disk is only 8GB.

I looked a little bit on the MIPS specific IDE code (in
include/asm-mips/ide.h) and the way things are handled for bigendian
systems.
I have attached a patch below which should handle the IDE data
correctly. I also believe it's more in sync with the other bigendian
architectures.
Now I can see the full size of my 30GB disk, it is hard to find a
smaller disk these days :-)

/Carsten

Index: include/asm-mips/ide.h
===================================================================
RCS file: /home/repository/sw/linux-2.4.0/include/asm-mips/ide.h,v
retrieving revision 1.1.1.2
diff -u -r1.1.1.2 ide.h
--- include/asm-mips/ide.h      2001/02/26 09:18:02     1.1.1.2
+++ include/asm-mips/ide.h      2001/04/20 09:57:59
@@ -14,6 +14,7 @@
 #ifdef __KERNEL__

 #include <linux/config.h>
+#include <asm/io.h>

 #ifndef MAX_HWIFS
 # ifdef CONFIG_BLK_DEV_IDEPCI
@@ -89,13 +90,21 @@
 typedef union {
        unsigned all                    : 8;    /* all of the bits
together */
        struct {
+#ifdef __MIPSEB__
+               unsigned bit7           : 1;    /* always 1 */
+               unsigned lba            : 1;    /* using LBA instead of
CHS */
+               unsigned bit5           : 1;    /* always 1 */
+               unsigned unit           : 1;    /* drive select number,
0 or 1 */
+               unsigned head           : 4;    /* always zeros here */
+#else
                unsigned head           : 4;    /* always zeros here */
                unsigned unit           : 1;    /* drive select number,
0 or 1 */
                unsigned bit5           : 1;    /* always 1 */
                unsigned lba            : 1;    /* using LBA instead of
CHS */
                unsigned bit7           : 1;    /* always 1 */
+#endif
        } b;
-       } select_t;
+} select_t;

 static __inline__ int ide_request_irq(unsigned int irq, void
(*handler)(int,void *, struct pt_regs *),
                        unsigned long flags, const char *device, void
*dev_id)
@@ -125,57 +134,99 @@
        ide_ops->ide_release_region(from, extent);
 }

+#undef  SUPPORT_SLOW_DATA_PORTS
+#define SUPPORT_SLOW_DATA_PORTS 0

-#if defined(CONFIG_SWAP_IO_SPACE) && defined(__MIPSEB__)
+#undef  SUPPORT_VLB_SYNC
+#define SUPPORT_VLB_SYNC 0

-#ifdef insl
-#undef insl
-#endif
-#ifdef outsl
-#undef outsl
-#endif
-#ifdef insw
-#undef insw
-#endif
-#ifdef outsw
-#undef outsw
-#endif
+#if defined(__MIPSEB__)

-#define insw(p,a,c)
\
-do {
\
-       unsigned short *ptr = (unsigned short *)(a);
\
-       unsigned int i = (c);
\
-       while (i--)
\
-               *ptr++ = inw(p);
\
-} while (0)
-#define insl(p,a,c)
\
-do {
\
-       unsigned long *ptr = (unsigned long *)(a);
\
-       unsigned int i = (c);
\
-       while (i--)
\
-               *ptr++ = inl(p);
\
-} while (0)
-#define outsw(p,a,c)
\
-do {
\
-       unsigned short *ptr = (unsigned short *)(a);
\
-       unsigned int i = (c);
\
-       while (i--)
\
-               outw(*ptr++, (p));
\
-} while (0)
-#define outsl(p,a,c) {
\
-       unsigned long *ptr = (unsigned long *)(a);
\
-       unsigned int i = (c);
\
-       while (i--)
\
-               outl(*ptr++, (p));
\
-} while (0)
+#define T_CHAR          (0x0000)        /* char:  don't touch  */
+#define T_SHORT         (0x4000)        /* short: 12 -> 21     */
+#define T_INT           (0x8000)        /* int:   1234 -> 4321 */
+#define T_TEXT          (0xc000)        /* text:  12 -> 21     */
+
+#define T_MASK_TYPE     (0xc000)
+#define T_MASK_COUNT    (0x3fff)
+
+#define D_CHAR(cnt)     (T_CHAR  | (cnt))
+#define D_SHORT(cnt)    (T_SHORT | (cnt))
+#define D_INT(cnt)      (T_INT   | (cnt))
+#define D_TEXT(cnt)     (T_TEXT  | (cnt))
+
+static u_short driveid_types[] = {
+       D_SHORT(10),    /* config - vendor2 */
+       D_TEXT(20),     /* serial_no */
+       D_SHORT(3),     /* buf_type - ecc_bytes */
+       D_TEXT(48),     /* fw_rev - model */
+       D_CHAR(2),      /* max_multsect - vendor3 */
+       D_SHORT(1),     /* dword_io */
+       D_CHAR(2),      /* vendor4 - capability */
+       D_SHORT(1),     /* reserved50 */
+       D_CHAR(4),      /* vendor5 - tDMA */
+       D_SHORT(4),     /* field_valid - cur_sectors */
+       D_INT(1),       /* cur_capacity */
+       D_CHAR(2),      /* multsect - multsect_valid */
+       D_INT(1),       /* lba_capacity */
+       D_SHORT(194)    /* dma_1word - reservedyy */
+};

-#endif /* defined(CONFIG_SWAP_IO_SPACE) && defined(__MIPSEB__)  */
+#define num_driveid_types
(sizeof(driveid_types)/sizeof(*driveid_types))

+static __inline__ void ide_fix_driveid(struct hd_driveid *id)
+{
+       u_char *p = (u_char *)id;
+       int i, j, cnt;
+       u_char t;
+
+       for (i = 0; i < num_driveid_types; i++) {
+               cnt = driveid_types[i] & T_MASK_COUNT;
+               switch (driveid_types[i] & T_MASK_TYPE) {
+               case T_CHAR:
+                       p += cnt;
+                       break;
+               case T_SHORT:
+                       for (j = 0; j < cnt; j++) {
+                               t = p[0];
+                               p[0] = p[1];
+                               p[1] = t;
+                               p += 2;
+                       }
+                       break;
+               case T_INT:
+                       for (j = 0; j < cnt; j++) {
+                               t = p[0];
+                               p[0] = p[3];
+                               p[3] = t;
+                               t = p[1];
+                               p[1] = p[2];
+                               p[2] = t;
+                               p += 4;
+                       }
+                       break;
+               case T_TEXT:
+                       for (j = 0; j < cnt; j += 2) {
+                               t = p[0];
+                               p[0] = p[1];
+                               p[1] = t;
+                               p += 2;
+                       }
+                       break;
+               };
+       }
+}
+
+#else /* defined(CONFIG_SWAP_IO_SPACE) && defined(__MIPSEB__)  */
+
+#define ide_fix_driveid(id)            do {} while (0)
+
+#endif
+
 /*
  * The following are not needed for the non-m68k ports
  */
 #define ide_ack_intr(hwif)             (1)
-#define ide_fix_driveid(id)            do {} while (0)
 #define ide_release_lock(lock)         do {} while (0)
 #define ide_get_lock(lock, hdlr, data) do {} while (0)

--
_    _ ____  ___   Carsten Langgaard   Mailto:[email-masked]
|\  /|||___)(___   MIPS Denmark        Direct: +45 4486 5527
| \/ |||    ____)  Lautrupvang 4B      Switch: +45 4486 5555
  TECHNOLOGIES     2750 Ballerup       Fax...: +45 4486 5556
                   Denmark             http://www.mips.com

---

From: Carsten Langgaard <[email-masked]>
Date: Fri, 20 Apr 2001 16:15:59 +0200

I should probably point out that the fix change the addressing mode on
large disks (>8GB) supporting LBA mode.
So you want be able to run on a system which was created by an older
kernel, but it probably didn't work properly anyway before the fix.
This only count for bigendian systems on large disks.

/Carsten