diff options
Diffstat (limited to 'src/kernel/blk_drv/hd.c')
-rw-r--r-- | src/kernel/blk_drv/hd.c | 349 |
1 files changed, 349 insertions, 0 deletions
diff --git a/src/kernel/blk_drv/hd.c b/src/kernel/blk_drv/hd.c new file mode 100644 index 0000000..d5e1aa9 --- /dev/null +++ b/src/kernel/blk_drv/hd.c | |||
@@ -0,0 +1,349 @@ | |||
1 | /* | ||
2 | * linux/kernel/hd.c | ||
3 | * | ||
4 | * (C) 1991 Linus Torvalds | ||
5 | */ | ||
6 | |||
7 | /* | ||
8 | * This is the low-level hd interrupt support. It traverses the | ||
9 | * request-list, using interrupts to jump between functions. As | ||
10 | * all the functions are called within interrupts, we may not | ||
11 | * sleep. Special care is recommended. | ||
12 | * | ||
13 | * modified by Drew Eckhardt to check nr of hd's from the CMOS. | ||
14 | */ | ||
15 | |||
16 | #include <linux/config.h> | ||
17 | #include <linux/sched.h> | ||
18 | #include <linux/fs.h> | ||
19 | #include <linux/kernel.h> | ||
20 | #include <linux/hdreg.h> | ||
21 | #include <asm/system.h> | ||
22 | #include <asm/io.h> | ||
23 | #include <asm/segment.h> | ||
24 | |||
25 | #define MAJOR_NR 3 | ||
26 | #include "blk.h" | ||
27 | |||
28 | #define CMOS_READ(addr) ({ \ | ||
29 | outb_p(0x80|addr,0x70); \ | ||
30 | inb_p(0x71); \ | ||
31 | }) | ||
32 | |||
33 | /* Max read/write errors/sector */ | ||
34 | #define MAX_ERRORS 7 | ||
35 | #define MAX_HD 2 | ||
36 | |||
37 | static void recal_intr(void); | ||
38 | |||
39 | static int recalibrate = 0; | ||
40 | static int reset = 0; | ||
41 | |||
42 | /* | ||
43 | * This struct defines the HD's and their types. | ||
44 | */ | ||
45 | struct hd_i_struct { | ||
46 | int head,sect,cyl,wpcom,lzone,ctl; | ||
47 | }; | ||
48 | #ifdef HD_TYPE | ||
49 | struct hd_i_struct hd_info[] = { HD_TYPE }; | ||
50 | #define NR_HD ((sizeof (hd_info))/(sizeof (struct hd_i_struct))) | ||
51 | #else | ||
52 | struct hd_i_struct hd_info[] = { {0,0,0,0,0,0},{0,0,0,0,0,0} }; | ||
53 | static int NR_HD = 0; | ||
54 | #endif | ||
55 | |||
56 | static struct hd_struct { | ||
57 | long start_sect; | ||
58 | long nr_sects; | ||
59 | } hd[5*MAX_HD]={{0,0},}; | ||
60 | |||
61 | #define port_read(port,buf,nr) \ | ||
62 | __asm__("cld;rep;insw"::"d" (port),"D" (buf),"c" (nr)) | ||
63 | |||
64 | #define port_write(port,buf,nr) \ | ||
65 | __asm__("cld;rep;outsw"::"d" (port),"S" (buf),"c" (nr)) | ||
66 | |||
67 | extern void hd_interrupt(void); | ||
68 | extern void rd_load(void); | ||
69 | |||
70 | /* This may be used only once, enforced by 'static int callable' */ | ||
71 | int sys_setup(void * BIOS) | ||
72 | { | ||
73 | static int callable = 1; | ||
74 | int i,drive; | ||
75 | unsigned char cmos_disks; | ||
76 | struct partition *p; | ||
77 | struct buffer_head * bh; | ||
78 | |||
79 | if (!callable) | ||
80 | return -1; | ||
81 | callable = 0; | ||
82 | #ifndef HD_TYPE | ||
83 | for (drive=0 ; drive<2 ; drive++) { | ||
84 | hd_info[drive].cyl = *(unsigned short *) BIOS; | ||
85 | hd_info[drive].head = *(unsigned char *) (2+BIOS); | ||
86 | hd_info[drive].wpcom = *(unsigned short *) (5+BIOS); | ||
87 | hd_info[drive].ctl = *(unsigned char *) (8+BIOS); | ||
88 | hd_info[drive].lzone = *(unsigned short *) (12+BIOS); | ||
89 | hd_info[drive].sect = *(unsigned char *) (14+BIOS); | ||
90 | BIOS += 16; | ||
91 | } | ||
92 | if (hd_info[1].cyl) | ||
93 | NR_HD=2; | ||
94 | else | ||
95 | NR_HD=1; | ||
96 | #endif | ||
97 | for (i=0 ; i<NR_HD ; i++) { | ||
98 | hd[i*5].start_sect = 0; | ||
99 | hd[i*5].nr_sects = hd_info[i].head* | ||
100 | hd_info[i].sect*hd_info[i].cyl; | ||
101 | } | ||
102 | |||
103 | /* | ||
104 | We querry CMOS about hard disks : it could be that | ||
105 | we have a SCSI/ESDI/etc controller that is BIOS | ||
106 | compatable with ST-506, and thus showing up in our | ||
107 | BIOS table, but not register compatable, and therefore | ||
108 | not present in CMOS. | ||
109 | |||
110 | Furthurmore, we will assume that our ST-506 drives | ||
111 | <if any> are the primary drives in the system, and | ||
112 | the ones reflected as drive 1 or 2. | ||
113 | |||
114 | The first drive is stored in the high nibble of CMOS | ||
115 | byte 0x12, the second in the low nibble. This will be | ||
116 | either a 4 bit drive type or 0xf indicating use byte 0x19 | ||
117 | for an 8 bit type, drive 1, 0x1a for drive 2 in CMOS. | ||
118 | |||
119 | Needless to say, a non-zero value means we have | ||
120 | an AT controller hard disk for that drive. | ||
121 | |||
122 | |||
123 | */ | ||
124 | |||
125 | if ((cmos_disks = CMOS_READ(0x12)) & 0xf0) | ||
126 | if (cmos_disks & 0x0f) | ||
127 | NR_HD = 2; | ||
128 | else | ||
129 | NR_HD = 1; | ||
130 | else | ||
131 | NR_HD = 0; | ||
132 | for (i = NR_HD ; i < 2 ; i++) { | ||
133 | hd[i*5].start_sect = 0; | ||
134 | hd[i*5].nr_sects = 0; | ||
135 | } | ||
136 | for (drive=0 ; drive<NR_HD ; drive++) { | ||
137 | if (!(bh = bread(0x300 + drive*5,0))) { | ||
138 | printk("Unable to read partition table of drive %d\n\r", | ||
139 | drive); | ||
140 | panic(""); | ||
141 | } | ||
142 | if (bh->b_data[510] != 0x55 || (unsigned char) | ||
143 | bh->b_data[511] != 0xAA) { | ||
144 | printk("Bad partition table on drive %d\n\r",drive); | ||
145 | panic(""); | ||
146 | } | ||
147 | p = 0x1BE + (void *)bh->b_data; | ||
148 | for (i=1;i<5;i++,p++) { | ||
149 | hd[i+5*drive].start_sect = p->start_sect; | ||
150 | hd[i+5*drive].nr_sects = p->nr_sects; | ||
151 | } | ||
152 | brelse(bh); | ||
153 | } | ||
154 | if (NR_HD) | ||
155 | printk("Partition table%s ok.\n\r",(NR_HD>1)?"s":""); | ||
156 | rd_load(); | ||
157 | mount_root(); | ||
158 | return (0); | ||
159 | } | ||
160 | |||
161 | static int controller_ready(void) | ||
162 | { | ||
163 | int retries=100000; | ||
164 | |||
165 | while (--retries && (inb_p(HD_STATUS)&0x80)); | ||
166 | return (retries); | ||
167 | } | ||
168 | |||
169 | static int win_result(void) | ||
170 | { | ||
171 | int i=inb_p(HD_STATUS); | ||
172 | |||
173 | if ((i & (BUSY_STAT | READY_STAT | WRERR_STAT | SEEK_STAT | ERR_STAT)) | ||
174 | == (READY_STAT | SEEK_STAT)) | ||
175 | return(0); /* ok */ | ||
176 | if (i&1) i=inb(HD_ERROR); | ||
177 | return (1); | ||
178 | } | ||
179 | |||
180 | static void hd_out(unsigned int drive,unsigned int nsect,unsigned int sect, | ||
181 | unsigned int head,unsigned int cyl,unsigned int cmd, | ||
182 | void (*intr_addr)(void)) | ||
183 | { | ||
184 | register int port asm("dx"); | ||
185 | |||
186 | if (drive>1 || head>15) | ||
187 | panic("Trying to write bad sector"); | ||
188 | if (!controller_ready()) | ||
189 | panic("HD controller not ready"); | ||
190 | do_hd = intr_addr; | ||
191 | outb_p(hd_info[drive].ctl,HD_CMD); | ||
192 | port=HD_DATA; | ||
193 | outb_p(hd_info[drive].wpcom>>2,++port); | ||
194 | outb_p(nsect,++port); | ||
195 | outb_p(sect,++port); | ||
196 | outb_p(cyl,++port); | ||
197 | outb_p(cyl>>8,++port); | ||
198 | outb_p(0xA0|(drive<<4)|head,++port); | ||
199 | outb(cmd,++port); | ||
200 | } | ||
201 | |||
202 | static int drive_busy(void) | ||
203 | { | ||
204 | unsigned int i; | ||
205 | |||
206 | for (i = 0; i < 10000; i++) | ||
207 | if (READY_STAT == (inb_p(HD_STATUS) & (BUSY_STAT|READY_STAT))) | ||
208 | break; | ||
209 | i = inb(HD_STATUS); | ||
210 | i &= BUSY_STAT | READY_STAT | SEEK_STAT; | ||
211 | if (i == (READY_STAT | SEEK_STAT)) | ||
212 | return(0); | ||
213 | printk("HD controller times out\n\r"); | ||
214 | return(1); | ||
215 | } | ||
216 | |||
217 | static void reset_controller(void) | ||
218 | { | ||
219 | int i; | ||
220 | |||
221 | outb(4,HD_CMD); | ||
222 | for(i = 0; i < 100; i++) nop(); | ||
223 | outb(hd_info[0].ctl & 0x0f ,HD_CMD); | ||
224 | if (drive_busy()) | ||
225 | printk("HD-controller still busy\n\r"); | ||
226 | if ((i = inb(HD_ERROR)) != 1) | ||
227 | printk("HD-controller reset failed: %02x\n\r",i); | ||
228 | } | ||
229 | |||
230 | static void reset_hd(int nr) | ||
231 | { | ||
232 | reset_controller(); | ||
233 | hd_out(nr,hd_info[nr].sect,hd_info[nr].sect,hd_info[nr].head-1, | ||
234 | hd_info[nr].cyl,WIN_SPECIFY,&recal_intr); | ||
235 | } | ||
236 | |||
237 | void unexpected_hd_interrupt(void) | ||
238 | { | ||
239 | printk("Unexpected HD interrupt\n\r"); | ||
240 | } | ||
241 | |||
242 | static void bad_rw_intr(void) | ||
243 | { | ||
244 | if (++CURRENT->errors >= MAX_ERRORS) | ||
245 | end_request(0); | ||
246 | if (CURRENT->errors > MAX_ERRORS/2) | ||
247 | reset = 1; | ||
248 | } | ||
249 | |||
250 | static void read_intr(void) | ||
251 | { | ||
252 | if (win_result()) { | ||
253 | bad_rw_intr(); | ||
254 | do_hd_request(); | ||
255 | return; | ||
256 | } | ||
257 | port_read(HD_DATA,CURRENT->buffer,256); | ||
258 | CURRENT->errors = 0; | ||
259 | CURRENT->buffer += 512; | ||
260 | CURRENT->sector++; | ||
261 | if (--CURRENT->nr_sectors) { | ||
262 | do_hd = &read_intr; | ||
263 | return; | ||
264 | } | ||
265 | end_request(1); | ||
266 | do_hd_request(); | ||
267 | } | ||
268 | |||
269 | static void write_intr(void) | ||
270 | { | ||
271 | if (win_result()) { | ||
272 | bad_rw_intr(); | ||
273 | do_hd_request(); | ||
274 | return; | ||
275 | } | ||
276 | if (--CURRENT->nr_sectors) { | ||
277 | CURRENT->sector++; | ||
278 | CURRENT->buffer += 512; | ||
279 | do_hd = &write_intr; | ||
280 | port_write(HD_DATA,CURRENT->buffer,256); | ||
281 | return; | ||
282 | } | ||
283 | end_request(1); | ||
284 | do_hd_request(); | ||
285 | } | ||
286 | |||
287 | static void recal_intr(void) | ||
288 | { | ||
289 | if (win_result()) | ||
290 | bad_rw_intr(); | ||
291 | do_hd_request(); | ||
292 | } | ||
293 | |||
294 | void do_hd_request(void) | ||
295 | { | ||
296 | int i,r = 0; | ||
297 | unsigned int block,dev; | ||
298 | unsigned int sec,head,cyl; | ||
299 | unsigned int nsect; | ||
300 | |||
301 | INIT_REQUEST; | ||
302 | dev = MINOR(CURRENT->dev); | ||
303 | block = CURRENT->sector; | ||
304 | if (dev >= 5*NR_HD || block+2 > hd[dev].nr_sects) { | ||
305 | end_request(0); | ||
306 | goto repeat; | ||
307 | } | ||
308 | block += hd[dev].start_sect; | ||
309 | dev /= 5; | ||
310 | __asm__("divl %4":"=a" (block),"=d" (sec):"0" (block),"1" (0), | ||
311 | "r" (hd_info[dev].sect)); | ||
312 | __asm__("divl %4":"=a" (cyl),"=d" (head):"0" (block),"1" (0), | ||
313 | "r" (hd_info[dev].head)); | ||
314 | sec++; | ||
315 | nsect = CURRENT->nr_sectors; | ||
316 | if (reset) { | ||
317 | reset = 0; | ||
318 | recalibrate = 1; | ||
319 | reset_hd(CURRENT_DEV); | ||
320 | return; | ||
321 | } | ||
322 | if (recalibrate) { | ||
323 | recalibrate = 0; | ||
324 | hd_out(dev,hd_info[CURRENT_DEV].sect,0,0,0, | ||
325 | WIN_RESTORE,&recal_intr); | ||
326 | return; | ||
327 | } | ||
328 | if (CURRENT->cmd == WRITE) { | ||
329 | hd_out(dev,nsect,sec,head,cyl,WIN_WRITE,&write_intr); | ||
330 | for(i=0 ; i<3000 && !(r=inb_p(HD_STATUS)&DRQ_STAT) ; i++) | ||
331 | /* nothing */ ; | ||
332 | if (!r) { | ||
333 | bad_rw_intr(); | ||
334 | goto repeat; | ||
335 | } | ||
336 | port_write(HD_DATA,CURRENT->buffer,256); | ||
337 | } else if (CURRENT->cmd == READ) { | ||
338 | hd_out(dev,nsect,sec,head,cyl,WIN_READ,&read_intr); | ||
339 | } else | ||
340 | panic("unknown hd-command"); | ||
341 | } | ||
342 | |||
343 | void hd_init(void) | ||
344 | { | ||
345 | blk_dev[MAJOR_NR].request_fn = DEVICE_REQUEST; | ||
346 | set_intr_gate(0x2E,&hd_interrupt); | ||
347 | outb_p(inb_p(0x21)&0xfb,0x21); | ||
348 | outb(inb_p(0xA1)&0xbf,0xA1); | ||
349 | } | ||