Subject: Re: Large Drives and old BIOS's
To: NetBSD Tech-Kern <tech-kern@netbsd.org>
From: Mike Pelley <mike@pelley.com>
List: tech-kern
Date: 06/05/2001 23:55:58
A couple of months ago I mentioned that I had a 60 gig maxtor drive and
an old BIOS that would not boot with it connected.  The solution was to
set the cylinder limitation jumper on the drive, and then have the OS
reset the size of the drive once we were past the BIOS.

Bill Sommerfeld (thanks Bill!) gave me the info I needed to get it
working (at http://mail-index.netbsd.org/tech-kern/2001/04/12/0019.html),
but my solution is a big kludge right now.  Unfortunately I don't have
the knowledge to clean it up, but I was hoping someone might either give
me some pointers or take it over so it can be put into the tree
eventually.  I've been told that the Linux kernel supports this now, but
I haven't looked at it myself.

In my current solution (patches included), I execute a
READ_NATIVE_MAX_ADDRESS and then a SET_MAX_ADDRESS in the kernel (wdattach
in wd.c) before the probe of the drive.  The probe (wd_get_params) then
correctly retrieves the size of the drive.

However, once the machine is booted I cannot read past the 32G point
(which is where the cylinder limitation jumper cuts off the drive).  If
I run the READ and SET again from userland, everything is fine.

It seems like the first SET works, but then something resets the drive
and I have to run it again after boot (currently I run a program just
before the fsck in /etc/rc.d/fsck).

So, besides general cleanup stuff, there are two things I'd need to fix.
First, the drive should be correctly set in the kernel without the
userland program.  Second, wdattach should check the drive to see if the
READ_NATIVE_MAX_ADDRESS is different from what wd_get_params finds, so
we only reset the size if necessary.  I tried to implement this but I
started to encounter panic's I couldn't easily resolve.

I imagine when it was working the whole thing would be wrapped in a
#define MAXTOR_CYLLIM or something similar with a matching OPTION in the
kernel config.

Thanks!   Mike.

---

Patch to wd.c (against 1.5 branch from tonight) - the last part of this
patch is to support the userland program and should not be required
in the end (from Bill S's email):

Index: wd.c
===================================================================
RCS file: /cvsroot/syssrc/sys/dev/ata/wd.c,v
retrieving revision 1.205.2.2
diff -u -r1.205.2.2 wd.c
--- wd.c	2001/05/06 20:48:42	1.205.2.2
+++ wd.c	2001/06/06 03:42:20
@@ -258,6 +258,7 @@
 {
 	struct wd_softc *wd = (void *)self;
 	struct ata_atapi_attach *aa_link= aux;
+	struct wdc_command wdc_c, wdc_c2;
 	int i, blank;
 	char buf[41], pbuf[9], c, *p, *q;
 	WDCDEBUG_PRINT(("wdattach\n"), DEBUG_FUNCS | DEBUG_PROBE);
@@ -271,6 +272,48 @@
 	/* give back our softc to our caller */
 	wd->drvp->drv_softc = &wd->sc_dev;
 
+	/* READ_NATIVE_MAX_ADDRESS */
+	memset(&wdc_c, 0, sizeof(struct wdc_command));
+	wdc_c.r_command = 0xf8; /* should be #defined */
+	wdc_c.r_head = 0x40;
+	wdc_c.r_st_bmask = WDCS_DRDY;
+	wdc_c.r_st_pmask = WDCS_DRDY;
+	wdc_c.flags = AT_READREG | AT_POLL | AT_WAIT;
+	wdc_c.timeout = 1000; /* 1s timeout */
+	if (wdc_exec_command(wd->drvp, &wdc_c) != WDC_COMPLETE) {
+		printf("%s: read max size command didn't complete\n",
+		    wd->sc_dev.dv_xname);
+	} else if (wdc_c.flags & AT_TIMEOU) {
+		printf("%s: read max size command timeout\n",
+		    wd->sc_dev.dv_xname);
+	} else if (wdc_c.flags & AT_DF) {
+		printf("%s: read max size command: drive fault\n",
+		    wd->sc_dev.dv_xname);
+	} else {
+		/* SET_MAX_ADDRESS */
+		/* (should probably reuse wdc_c) */
+		memset(&wdc_c2, 0, sizeof(struct wdc_command));
+		wdc_c2.r_command = 0xf9; /* should be #defined */
+		wdc_c2.r_sector = wdc_c.r_sector;
+		wdc_c2.r_head = 0x40 | (wdc_c.r_head & 0xf);
+		wdc_c2.r_cyl = wdc_c.r_cyl;
+		wdc_c2.r_st_bmask = WDCS_DRDY;
+		wdc_c2.r_st_pmask = WDCS_DRDY;
+		wdc_c2.flags = AT_READREG | AT_POLL;
+		wdc_c2.timeout = 1000; /* 1s timeout */
+		if (wdc_exec_command(wd->drvp, &wdc_c2) !=
+		  WDC_COMPLETE) {
+		    printf("%s: set max size command didn't complete\n",
+			    wd->sc_dev.dv_xname);
+		} else if (wdc_c2.flags & AT_TIMEOU) {
+		    printf("%s: set max size command timeout\n",
+			    wd->sc_dev.dv_xname);
+		} else if (wdc_c2.flags & AT_DF) {
+		    printf("%s: set max size command: drive fault\n",
+			    wd->sc_dev.dv_xname);
+		}
+	}
+
 	/* read our drive info */
 	if (wd_get_params(wd, AT_POLL, &wd->sc_params) != 0) {
 		printf("%s: IDENTIFY failed\n", wd->sc_dev.dv_xname);
@@ -1556,7 +1599,7 @@
 
 	wdc_c.timeout = wi->wi_atareq.timeout;
 	wdc_c.r_command = wi->wi_atareq.command;
-	wdc_c.r_head = wi->wi_atareq.head & 0x0f;
+	wdc_c.r_head = wi->wi_atareq.head & 0x4f;
 	wdc_c.r_cyl = wi->wi_atareq.cylinder;
 	wdc_c.r_sector = wi->wi_atareq.sec_num;
 	wdc_c.r_count = wi->wi_atareq.sec_count;

---

Userland program setmax.c (copied from Bill S's email):


#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ataio.h>

int main(int argc, char **argv)
{
  int fd, r;
  atareq_t atr, natr;

  atr.flags = ATACMD_READREG;
  atr.command = 0xf8;
  atr.features = 0;
  atr.sec_count = 0;
  atr.sec_num = 0;
  atr.head = 0x40;
  atr.cylinder = 0;
  atr.databuf = 0;
  atr.datalen = 0;
  atr.timeout = 1000;
  atr.retsts = 0;
  atr.error = 0;

  fd = open(argv[1], O_RDWR, 0);
  if (fd < 0) {
    perror(argv[1]);
    exit(1);
  }
  sleep(5);
  r = ioctl(fd, ATAIOCCOMMAND, &atr);
  if (r < 0) {
    perror("ATAIOCCOMMAND");
    exit(1);
  }
  printf("error: %d sec_count: %d sec_num: %d cylinder: %d head: %d retsts: %d\n",
         atr.error,
         atr.sec_count,
         atr.sec_num,
         atr.cylinder,
         atr.head,
         atr.retsts);

  natr.flags = ATACMD_READREG;
  natr.command = 0xf9;
  natr.features = 0;
  natr.sec_count = 0;
  natr.sec_num = atr.sec_num;
  natr.head = 0x40 | (atr.head & 0xf);
  natr.cylinder = atr.cylinder;
  natr.databuf = 0;
  natr.datalen = 0;
  natr.timeout = 1000;
  natr.retsts = 0;
  natr.error = 0;

  r = ioctl(fd, ATAIOCCOMMAND, &natr);
  if (r < 0) {
    perror("ATAIOCCOMMAND");
    exit(1);
  }
  printf("error: %d sec_count: %d sec_num: %d cylinder: %d head: %d retsts: %d\n",
         natr.error,
         natr.sec_count,
         natr.sec_num,
         natr.cylinder,
         natr.head,
         natr.retsts);

  return 0;
}