summaryrefslogtreecommitdiffstats
path: root/drivers/ide/ide-geometry.c
blob: 7b56818557ab76565532dba760948f6825528501 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
/*
 * linux/drivers/ide/ide-geometry.c
 */
#include <linux/config.h>
#include <linux/ide.h>
#include <linux/mc146818rtc.h>
#include <asm/io.h>

/*
 * We query CMOS about hard disks : it could be that we have a SCSI/ESDI/etc
 * controller that is BIOS compatible with ST-506, and thus showing up in our
 * BIOS table, but not register compatible, and therefore not present in CMOS.
 *
 * Furthermore, we will assume that our ST-506 drives <if any> are the primary
 * drives in the system -- the ones reflected as drive 1 or 2.  The first
 * drive is stored in the high nibble of CMOS byte 0x12, the second in the low
 * nibble.  This will be either a 4 bit drive type or 0xf indicating use byte
 * 0x19 for an 8 bit type, drive 1, 0x1a for drive 2 in CMOS.  A non-zero value
 * means we have an AT controller hard disk for that drive.
 *
 * Of course, there is no guarantee that either drive is actually on the
 * "primary" IDE interface, but we don't bother trying to sort that out here.
 * If a drive is not actually on the primary interface, then these parameters
 * will be ignored.  This results in the user having to supply the logical
 * drive geometry as a boot parameter for each drive not on the primary i/f.
 */
/*
 * The only "perfect" way to handle this would be to modify the setup.[cS] code
 * to do BIOS calls Int13h/Fn08h and Int13h/Fn48h to get all of the drive info
 * for us during initialization.  I have the necessary docs -- any takers?  -ml
 */
/*
 * I did this, but it doesnt work - there is no reasonable way to find the
 * correspondence between the BIOS numbering of the disks and the Linux
 * numbering. -aeb
 *
 * The code below is bad. One of the problems is that drives 1 and 2
 * may be SCSI disks (even when IDE disks are present), so that
 * the geometry we read here from BIOS is attributed to the wrong disks.
 * Consequently, also the former "drive->present = 1" below was a mistake.
 *
 * Eventually the entire routine below should be removed.
 */
void probe_cmos_for_drives (ide_hwif_t *hwif)
{
#ifdef __i386__
	extern struct drive_info_struct drive_info;
	byte cmos_disks, *BIOS = (byte *) &drive_info;
	int unit;
	unsigned long flags;

#ifdef CONFIG_BLK_DEV_PDC4030
	if (hwif->chipset == ide_pdc4030 && hwif->channel != 0)
		return;
#endif /* CONFIG_BLK_DEV_PDC4030 */
	spin_lock_irqsave(&rtc_lock, flags);
	cmos_disks = CMOS_READ(0x12);
	spin_unlock_irqrestore(&rtc_lock, flags);
	/* Extract drive geometry from CMOS+BIOS if not already setup */
	for (unit = 0; unit < MAX_DRIVES; ++unit) {
		ide_drive_t *drive = &hwif->drives[unit];

		if ((cmos_disks & (0xf0 >> (unit*4)))
		   && !drive->present && !drive->nobios) {
			unsigned short cyl = *(unsigned short *)BIOS;
			unsigned char head = *(BIOS+2);
			unsigned char sect = *(BIOS+14);
			if (cyl > 0 && head > 0 && sect > 0 && sect < 64) {
				drive->cyl   = drive->bios_cyl  = cyl;
				drive->head  = drive->bios_head = head;
				drive->sect  = drive->bios_sect = sect;
				drive->ctl   = *(BIOS+8);
			} else {
				printk("hd%d: C/H/S=%d/%d/%d from BIOS ignored\n",
				       unit, cyl, head, sect);
			}
		}

		BIOS += 16;
	}
#endif
}


#ifdef CONFIG_BLK_DEV_IDE

extern ide_drive_t * get_info_ptr(kdev_t);
extern unsigned long current_capacity (ide_drive_t *);

/*
 * If heads is nonzero: find a translation with this many heads and S=63.
 * Otherwise: find out how OnTrack Disk Manager would translate the disk.
 */
static void
ontrack(ide_drive_t *drive, int heads, unsigned int *c, int *h, int *s) {
	static const byte dm_head_vals[] = {4, 8, 16, 32, 64, 128, 255, 0};
	const byte *headp = dm_head_vals;
	unsigned long total;

	/*
	 * The specs say: take geometry as obtained from Identify,
	 * compute total capacity C*H*S from that, and truncate to
	 * 1024*255*63. Now take S=63, H the first in the sequence
	 * 4, 8, 16, 32, 64, 128, 255 such that 63*H*1024 >= total.
	 * [Please tell aeb@cwi.nl in case this computes a
	 * geometry different from what OnTrack uses.]
	 */
	total = DRIVER(drive)->capacity(drive);

	*s = 63;

	if (heads) {
		*h = heads;
		*c = total / (63 * heads);
		return;
	}

	while (63 * headp[0] * 1024 < total && headp[1] != 0)
		 headp++;
	*h = headp[0];
	*c = total / (63 * headp[0]);
}

/*
 * This routine is called from the partition-table code in pt/msdos.c.
 * It has two tasks:
 * (i) to handle Ontrack DiskManager by offsetting everything by 63 sectors,
 *  or to handle EZdrive by remapping sector 0 to sector 1.
 * (ii) to invent a translated geometry.
 * Part (i) is suppressed if the user specifies the "noremap" option
 * on the command line.
 * Part (ii) is suppressed if the user specifies an explicit geometry.
 *
 * The ptheads parameter is either 0 or tells about the number of
 * heads shown by the end of the first nonempty partition.
 * If this is either 16, 32, 64, 128, 240 or 255 we'll believe it.
 *
 * The xparm parameter has the following meaning:
 *	 0 = convert to CHS with fewer than 1024 cyls
 *	     using the same method as Ontrack DiskManager.
 *	 1 = same as "0", plus offset everything by 63 sectors.
 *	-1 = similar to "0", plus redirect sector 0 to sector 1.
 *	 2 = convert to a CHS geometry with "ptheads" heads.
 *
 * Returns 0 if the translation was not possible, if the device was not 
 * an IDE disk drive, or if a geometry was "forced" on the commandline.
 * Returns 1 if the geometry translation was successful.
 */
int ide_xlate_1024 (kdev_t i_rdev, int xparm, int ptheads, const char *msg)
{
	ide_drive_t *drive;
	const char *msg1 = "";
	int heads = 0;
	int c, h, s;
	int transl = 1;		/* try translation */
	int ret = 0;

	drive = get_info_ptr(i_rdev);
	if (!drive)
		return 0;

	/* remap? */
	if (drive->remap_0_to_1 != 2) {
		if (xparm == 1) {		/* DM */
			drive->sect0 = 63;
			msg1 = " [remap +63]";
			ret = 1;
		} else if (xparm == -1) {	/* EZ-Drive */
			if (drive->remap_0_to_1 == 0) {
				drive->remap_0_to_1 = 1;
				msg1 = " [remap 0->1]";
				ret = 1;
			}
		}
	}

	/* There used to be code here that assigned drive->id->CHS
	   to drive->CHS and that to drive->bios_CHS. However,
	   some disks have id->C/H/S = 4092/16/63 but are larger than 2.1 GB.
	   In such cases that code was wrong.  Moreover,
	   there seems to be no reason to do any of these things. */

	/* translate? */
	if (drive->forced_geom)
		transl = 0;

	/* does ptheads look reasonable? */
	if (ptheads == 32 || ptheads == 64 || ptheads == 128 ||
	    ptheads == 240 || ptheads == 255)
		heads = ptheads;

	if (xparm == 2) {
		if (!heads ||
		   (drive->bios_head >= heads && drive->bios_sect == 63))
			transl = 0;
	}
	if (xparm == -1) {
		if (drive->bios_head > 16)
			transl = 0;     /* we already have a translation */
	}

	if (transl) {
		ontrack(drive, heads, &c, &h, &s);
		drive->bios_cyl = c;
		drive->bios_head = h;
		drive->bios_sect = s;
		ret = 1;
	}

	drive->part[0].nr_sects = current_capacity(drive);

	if (ret)
		printk("%s%s [%d/%d/%d]", msg, msg1,
		       drive->bios_cyl, drive->bios_head, drive->bios_sect);
	return ret;
}
#endif /* CONFIG_BLK_DEV_IDE */