]>
Commit | Line | Data |
---|---|---|
1da177e4 LT |
1 | /* |
2 | * Driver for the SWIM (Super Woz Integrated Machine) IOP | |
3 | * floppy controller on the Macintosh IIfx and Quadra 900/950 | |
4 | * | |
5 | * Written by Joshua M. Thompson ([email protected]) | |
6 | * based on the SWIM3 driver (c) 1996 by Paul Mackerras. | |
7 | * | |
8 | * This program is free software; you can redistribute it and/or | |
9 | * modify it under the terms of the GNU General Public License | |
10 | * as published by the Free Software Foundation; either version | |
11 | * 2 of the License, or (at your option) any later version. | |
12 | * | |
13 | * 1999-06-12 (jmt) - Initial implementation. | |
14 | */ | |
15 | ||
16 | /* | |
17 | * ------------------- | |
18 | * Theory of Operation | |
19 | * ------------------- | |
20 | * | |
21 | * Since the SWIM IOP is message-driven we implement a simple request queue | |
22 | * system. One outstanding request may be queued at any given time (this is | |
23 | * an IOP limitation); only when that request has completed can a new request | |
24 | * be sent. | |
25 | */ | |
26 | ||
27 | #include <linux/stddef.h> | |
28 | #include <linux/kernel.h> | |
29 | #include <linux/sched.h> | |
30 | #include <linux/timer.h> | |
31 | #include <linux/delay.h> | |
32 | #include <linux/fd.h> | |
33 | #include <linux/ioctl.h> | |
34 | #include <linux/blkdev.h> | |
35 | #include <asm/io.h> | |
36 | #include <asm/uaccess.h> | |
37 | #include <asm/mac_iop.h> | |
38 | #include <asm/swim_iop.h> | |
39 | ||
40 | #define DRIVER_VERSION "Version 0.1 (1999-06-12)" | |
41 | ||
42 | #define MAX_FLOPPIES 4 | |
43 | ||
44 | enum swim_state { | |
45 | idle, | |
46 | available, | |
47 | revalidating, | |
48 | transferring, | |
49 | ejecting | |
50 | }; | |
51 | ||
52 | struct floppy_state { | |
53 | enum swim_state state; | |
54 | int drive_num; /* device number */ | |
55 | int secpercyl; /* disk geometry information */ | |
56 | int secpertrack; | |
57 | int total_secs; | |
58 | int write_prot; /* 1 if write-protected, 0 if not, -1 dunno */ | |
59 | int ref_count; | |
60 | struct timer_list timeout; | |
61 | int ejected; | |
62 | struct wait_queue *wait; | |
63 | int wanted; | |
64 | int timeout_pending; | |
65 | }; | |
66 | ||
67 | struct swim_iop_req { | |
68 | int sent; | |
69 | int complete; | |
70 | __u8 command[32]; | |
71 | struct floppy_state *fs; | |
72 | void (*done)(struct swim_iop_req *); | |
73 | }; | |
74 | ||
75 | static struct swim_iop_req *current_req; | |
76 | static int floppy_count; | |
77 | ||
78 | static struct floppy_state floppy_states[MAX_FLOPPIES]; | |
79 | static DEFINE_SPINLOCK(swim_iop_lock); | |
80 | ||
81 | #define CURRENT elv_next_request(swim_queue) | |
82 | ||
83 | static char *drive_names[7] = { | |
84 | "not installed", /* DRV_NONE */ | |
85 | "unknown (1)", /* DRV_UNKNOWN */ | |
86 | "a 400K drive", /* DRV_400K */ | |
87 | "an 800K drive" /* DRV_800K */ | |
88 | "unknown (4)", /* ???? */ | |
89 | "an FDHD", /* DRV_FDHD */ | |
90 | "unknown (6)", /* ???? */ | |
91 | "an Apple HD20" /* DRV_HD20 */ | |
92 | }; | |
93 | ||
94 | int swimiop_init(void); | |
95 | static void swimiop_init_request(struct swim_iop_req *); | |
96 | static int swimiop_send_request(struct swim_iop_req *); | |
97 | static void swimiop_receive(struct iop_msg *, struct pt_regs *); | |
98 | static void swimiop_status_update(int, struct swim_drvstatus *); | |
99 | static int swimiop_eject(struct floppy_state *fs); | |
100 | ||
101 | static int floppy_ioctl(struct inode *inode, struct file *filp, | |
102 | unsigned int cmd, unsigned long param); | |
103 | static int floppy_open(struct inode *inode, struct file *filp); | |
104 | static int floppy_release(struct inode *inode, struct file *filp); | |
105 | static int floppy_check_change(struct gendisk *disk); | |
106 | static int floppy_revalidate(struct gendisk *disk); | |
107 | static int grab_drive(struct floppy_state *fs, enum swim_state state, | |
108 | int interruptible); | |
109 | static void release_drive(struct floppy_state *fs); | |
110 | static void set_timeout(struct floppy_state *fs, int nticks, | |
111 | void (*proc)(unsigned long)); | |
112 | static void fd_request_timeout(unsigned long); | |
113 | static void do_fd_request(request_queue_t * q); | |
114 | static void start_request(struct floppy_state *fs); | |
115 | ||
116 | static struct block_device_operations floppy_fops = { | |
117 | .open = floppy_open, | |
118 | .release = floppy_release, | |
119 | .ioctl = floppy_ioctl, | |
120 | .media_changed = floppy_check_change, | |
121 | .revalidate_disk= floppy_revalidate, | |
122 | }; | |
123 | ||
124 | static struct request_queue *swim_queue; | |
125 | /* | |
126 | * SWIM IOP initialization | |
127 | */ | |
128 | ||
129 | int swimiop_init(void) | |
130 | { | |
131 | volatile struct swim_iop_req req; | |
132 | struct swimcmd_status *cmd = (struct swimcmd_status *) &req.command[0]; | |
133 | struct swim_drvstatus *ds = &cmd->status; | |
134 | struct floppy_state *fs; | |
135 | int i; | |
136 | ||
137 | current_req = NULL; | |
138 | floppy_count = 0; | |
139 | ||
140 | if (!iop_ism_present) | |
141 | return -ENODEV; | |
142 | ||
143 | if (register_blkdev(FLOPPY_MAJOR, "fd")) | |
144 | return -EBUSY; | |
145 | ||
146 | swim_queue = blk_init_queue(do_fd_request, &swim_iop_lock); | |
147 | if (!swim_queue) { | |
148 | unregister_blkdev(FLOPPY_MAJOR, "fd"); | |
149 | return -ENOMEM; | |
150 | } | |
151 | ||
152 | printk("SWIM-IOP: %s by Joshua M. Thompson ([email protected])\n", | |
153 | DRIVER_VERSION); | |
154 | ||
155 | if (iop_listen(SWIM_IOP, SWIM_CHAN, swimiop_receive, "SWIM") != 0) { | |
156 | printk(KERN_ERR "SWIM-IOP: IOP channel already in use; can't initialize.\n"); | |
157 | unregister_blkdev(FLOPPY_MAJOR, "fd"); | |
158 | blk_cleanup_queue(swim_queue); | |
159 | return -EBUSY; | |
160 | } | |
161 | ||
162 | printk(KERN_ERR "SWIM_IOP: probing for installed drives.\n"); | |
163 | ||
164 | for (i = 0 ; i < MAX_FLOPPIES ; i++) { | |
165 | memset(&floppy_states[i], 0, sizeof(struct floppy_state)); | |
166 | fs = &floppy_states[floppy_count]; | |
167 | ||
168 | swimiop_init_request(&req); | |
169 | cmd->code = CMD_STATUS; | |
170 | cmd->drive_num = i + 1; | |
171 | if (swimiop_send_request(&req) != 0) continue; | |
172 | while (!req.complete); | |
173 | if (cmd->error != 0) { | |
174 | printk(KERN_ERR "SWIM-IOP: probe on drive %d returned error %d\n", i, (uint) cmd->error); | |
175 | continue; | |
176 | } | |
177 | if (ds->installed != 0x01) continue; | |
178 | printk("SWIM-IOP: drive %d is %s (%s, %s, %s, %s)\n", i, | |
179 | drive_names[ds->info.type], | |
180 | ds->info.external? "ext" : "int", | |
181 | ds->info.scsi? "scsi" : "floppy", | |
182 | ds->info.fixed? "fixed" : "removable", | |
183 | ds->info.secondary? "secondary" : "primary"); | |
184 | swimiop_status_update(floppy_count, ds); | |
185 | fs->state = idle; | |
186 | ||
187 | init_timer(&fs->timeout); | |
188 | floppy_count++; | |
189 | } | |
190 | printk("SWIM-IOP: detected %d installed drives.\n", floppy_count); | |
191 | ||
192 | for (i = 0; i < floppy_count; i++) { | |
193 | struct gendisk *disk = alloc_disk(1); | |
194 | if (!disk) | |
195 | continue; | |
196 | disk->major = FLOPPY_MAJOR; | |
197 | disk->first_minor = i; | |
198 | disk->fops = &floppy_fops; | |
199 | sprintf(disk->disk_name, "fd%d", i); | |
200 | disk->private_data = &floppy_states[i]; | |
201 | disk->queue = swim_queue; | |
202 | set_capacity(disk, 2880 * 2); | |
203 | add_disk(disk); | |
204 | } | |
205 | ||
206 | return 0; | |
207 | } | |
208 | ||
209 | static void swimiop_init_request(struct swim_iop_req *req) | |
210 | { | |
211 | req->sent = 0; | |
212 | req->complete = 0; | |
213 | req->done = NULL; | |
214 | } | |
215 | ||
216 | static int swimiop_send_request(struct swim_iop_req *req) | |
217 | { | |
218 | unsigned long flags; | |
219 | int err; | |
220 | ||
221 | /* It's doubtful an interrupt routine would try to send */ | |
222 | /* a SWIM request, but I'd rather play it safe here. */ | |
223 | ||
224 | local_irq_save(flags); | |
225 | ||
226 | if (current_req != NULL) { | |
227 | local_irq_restore(flags); | |
228 | return -ENOMEM; | |
229 | } | |
230 | ||
231 | current_req = req; | |
232 | ||
233 | /* Interrupts should be back on for iop_send_message() */ | |
234 | ||
235 | local_irq_restore(flags); | |
236 | ||
237 | err = iop_send_message(SWIM_IOP, SWIM_CHAN, (void *) req, | |
238 | sizeof(req->command), (__u8 *) &req->command[0], | |
239 | swimiop_receive); | |
240 | ||
241 | /* No race condition here; we own current_req at this point */ | |
242 | ||
243 | if (err) { | |
244 | current_req = NULL; | |
245 | } else { | |
246 | req->sent = 1; | |
247 | } | |
248 | return err; | |
249 | } | |
250 | ||
251 | /* | |
252 | * Receive a SWIM message from the IOP. | |
253 | * | |
254 | * This will be called in two cases: | |
255 | * | |
256 | * 1. A message has been successfully sent to the IOP. | |
257 | * 2. An unsolicited message was received from the IOP. | |
258 | */ | |
259 | ||
260 | void swimiop_receive(struct iop_msg *msg, struct pt_regs *regs) | |
261 | { | |
262 | struct swim_iop_req *req; | |
263 | struct swimmsg_status *sm; | |
264 | struct swim_drvstatus *ds; | |
265 | ||
266 | req = current_req; | |
267 | ||
268 | switch(msg->status) { | |
269 | case IOP_MSGSTATUS_COMPLETE: | |
270 | memcpy(&req->command[0], &msg->reply[0], sizeof(req->command)); | |
271 | req->complete = 1; | |
272 | if (req->done) (*req->done)(req); | |
273 | current_req = NULL; | |
274 | break; | |
275 | case IOP_MSGSTATUS_UNSOL: | |
276 | sm = (struct swimmsg_status *) &msg->message[0]; | |
277 | ds = &sm->status; | |
278 | swimiop_status_update(sm->drive_num, ds); | |
279 | iop_complete_message(msg); | |
280 | break; | |
281 | } | |
282 | } | |
283 | ||
284 | static void swimiop_status_update(int drive_num, struct swim_drvstatus *ds) | |
285 | { | |
286 | struct floppy_state *fs = &floppy_states[drive_num]; | |
287 | ||
288 | fs->write_prot = (ds->write_prot == 0x80); | |
289 | if ((ds->disk_in_drive != 0x01) && (ds->disk_in_drive != 0x02)) { | |
290 | fs->ejected = 1; | |
291 | } else { | |
292 | fs->ejected = 0; | |
293 | } | |
294 | switch(ds->info.type) { | |
295 | case DRV_400K: | |
296 | fs->secpercyl = 10; | |
297 | fs->secpertrack = 10; | |
298 | fs->total_secs = 800; | |
299 | break; | |
300 | case DRV_800K: | |
301 | fs->secpercyl = 20; | |
302 | fs->secpertrack = 10; | |
303 | fs->total_secs = 1600; | |
304 | break; | |
305 | case DRV_FDHD: | |
306 | fs->secpercyl = 36; | |
307 | fs->secpertrack = 18; | |
308 | fs->total_secs = 2880; | |
309 | break; | |
310 | default: | |
311 | fs->secpercyl = 0; | |
312 | fs->secpertrack = 0; | |
313 | fs->total_secs = 0; | |
314 | break; | |
315 | } | |
316 | } | |
317 | ||
318 | static int swimiop_eject(struct floppy_state *fs) | |
319 | { | |
320 | int err, n; | |
321 | struct swim_iop_req req; | |
322 | struct swimcmd_eject *cmd = (struct swimcmd_eject *) &req.command[0]; | |
323 | ||
324 | err = grab_drive(fs, ejecting, 1); | |
325 | if (err) return err; | |
326 | ||
327 | swimiop_init_request(&req); | |
328 | cmd->code = CMD_EJECT; | |
329 | cmd->drive_num = fs->drive_num; | |
330 | err = swimiop_send_request(&req); | |
331 | if (err) { | |
332 | release_drive(fs); | |
333 | return err; | |
334 | } | |
335 | for (n = 2*HZ; n > 0; --n) { | |
336 | if (req.complete) break; | |
337 | if (signal_pending(current)) { | |
338 | err = -EINTR; | |
339 | break; | |
340 | } | |
341 | current->state = TASK_INTERRUPTIBLE; | |
342 | schedule_timeout(1); | |
343 | } | |
344 | release_drive(fs); | |
345 | return cmd->error; | |
346 | } | |
347 | ||
348 | static struct floppy_struct floppy_type = | |
349 | { 2880,18,2,80,0,0x1B,0x00,0xCF,0x6C,NULL }; /* 7 1.44MB 3.5" */ | |
350 | ||
351 | static int floppy_ioctl(struct inode *inode, struct file *filp, | |
352 | unsigned int cmd, unsigned long param) | |
353 | { | |
354 | struct floppy_state *fs = inode->i_bdev->bd_disk->private_data; | |
355 | int err; | |
356 | ||
357 | if ((cmd & 0x80) && !capable(CAP_SYS_ADMIN)) | |
358 | return -EPERM; | |
359 | ||
360 | switch (cmd) { | |
361 | case FDEJECT: | |
362 | if (fs->ref_count != 1) | |
363 | return -EBUSY; | |
364 | err = swimiop_eject(fs); | |
365 | return err; | |
366 | case FDGETPRM: | |
367 | if (copy_to_user((void *) param, (void *) &floppy_type, | |
368 | sizeof(struct floppy_struct))) | |
369 | return -EFAULT; | |
370 | return 0; | |
371 | } | |
372 | return -ENOTTY; | |
373 | } | |
374 | ||
375 | static int floppy_open(struct inode *inode, struct file *filp) | |
376 | { | |
377 | struct floppy_state *fs = inode->i_bdev->bd_disk->private_data; | |
378 | ||
379 | if (fs->ref_count == -1 || filp->f_flags & O_EXCL) | |
380 | return -EBUSY; | |
381 | ||
382 | if ((filp->f_flags & O_NDELAY) == 0 && (filp->f_mode & 3)) { | |
383 | check_disk_change(inode->i_bdev); | |
384 | if (fs->ejected) | |
385 | return -ENXIO; | |
386 | } | |
387 | ||
388 | if ((filp->f_mode & 2) && fs->write_prot) | |
389 | return -EROFS; | |
390 | ||
391 | if (filp->f_flags & O_EXCL) | |
392 | fs->ref_count = -1; | |
393 | else | |
394 | ++fs->ref_count; | |
395 | ||
396 | return 0; | |
397 | } | |
398 | ||
399 | static int floppy_release(struct inode *inode, struct file *filp) | |
400 | { | |
401 | struct floppy_state *fs = inode->i_bdev->bd_disk->private_data; | |
402 | if (fs->ref_count > 0) | |
403 | fs->ref_count--; | |
404 | return 0; | |
405 | } | |
406 | ||
407 | static int floppy_check_change(struct gendisk *disk) | |
408 | { | |
409 | struct floppy_state *fs = disk->private_data; | |
410 | return fs->ejected; | |
411 | } | |
412 | ||
413 | static int floppy_revalidate(struct gendisk *disk) | |
414 | { | |
415 | struct floppy_state *fs = disk->private_data; | |
416 | grab_drive(fs, revalidating, 0); | |
417 | /* yadda, yadda */ | |
418 | release_drive(fs); | |
419 | return 0; | |
420 | } | |
421 | ||
422 | static void floppy_off(unsigned int nr) | |
423 | { | |
424 | } | |
425 | ||
426 | static int grab_drive(struct floppy_state *fs, enum swim_state state, | |
427 | int interruptible) | |
428 | { | |
429 | unsigned long flags; | |
430 | ||
431 | local_irq_save(flags); | |
432 | if (fs->state != idle) { | |
433 | ++fs->wanted; | |
434 | while (fs->state != available) { | |
435 | if (interruptible && signal_pending(current)) { | |
436 | --fs->wanted; | |
437 | local_irq_restore(flags); | |
438 | return -EINTR; | |
439 | } | |
440 | interruptible_sleep_on(&fs->wait); | |
441 | } | |
442 | --fs->wanted; | |
443 | } | |
444 | fs->state = state; | |
445 | local_irq_restore(flags); | |
446 | return 0; | |
447 | } | |
448 | ||
449 | static void release_drive(struct floppy_state *fs) | |
450 | { | |
451 | unsigned long flags; | |
452 | ||
453 | local_irq_save(flags); | |
454 | fs->state = idle; | |
455 | start_request(fs); | |
456 | local_irq_restore(flags); | |
457 | } | |
458 | ||
459 | static void set_timeout(struct floppy_state *fs, int nticks, | |
460 | void (*proc)(unsigned long)) | |
461 | { | |
462 | unsigned long flags; | |
463 | ||
464 | local_irq_save(flags); | |
465 | if (fs->timeout_pending) | |
466 | del_timer(&fs->timeout); | |
467 | init_timer(&fs->timeout); | |
468 | fs->timeout.expires = jiffies + nticks; | |
469 | fs->timeout.function = proc; | |
470 | fs->timeout.data = (unsigned long) fs; | |
471 | add_timer(&fs->timeout); | |
472 | fs->timeout_pending = 1; | |
473 | local_irq_restore(flags); | |
474 | } | |
475 | ||
476 | static void do_fd_request(request_queue_t * q) | |
477 | { | |
478 | int i; | |
479 | ||
480 | for (i = 0 ; i < floppy_count ; i++) { | |
481 | start_request(&floppy_states[i]); | |
482 | } | |
483 | } | |
484 | ||
485 | static void fd_request_complete(struct swim_iop_req *req) | |
486 | { | |
487 | struct floppy_state *fs = req->fs; | |
488 | struct swimcmd_rw *cmd = (struct swimcmd_rw *) &req->command[0]; | |
489 | ||
490 | del_timer(&fs->timeout); | |
491 | fs->timeout_pending = 0; | |
492 | fs->state = idle; | |
493 | if (cmd->error) { | |
494 | printk(KERN_ERR "SWIM-IOP: error %d on read/write request.\n", cmd->error); | |
495 | end_request(CURRENT, 0); | |
496 | } else { | |
497 | CURRENT->sector += cmd->num_blocks; | |
498 | CURRENT->current_nr_sectors -= cmd->num_blocks; | |
499 | if (CURRENT->current_nr_sectors <= 0) { | |
500 | end_request(CURRENT, 1); | |
501 | return; | |
502 | } | |
503 | } | |
504 | start_request(fs); | |
505 | } | |
506 | ||
507 | static void fd_request_timeout(unsigned long data) | |
508 | { | |
509 | struct floppy_state *fs = (struct floppy_state *) data; | |
510 | ||
511 | fs->timeout_pending = 0; | |
512 | end_request(CURRENT, 0); | |
513 | fs->state = idle; | |
514 | } | |
515 | ||
516 | static void start_request(struct floppy_state *fs) | |
517 | { | |
518 | volatile struct swim_iop_req req; | |
519 | struct swimcmd_rw *cmd = (struct swimcmd_rw *) &req.command[0]; | |
520 | ||
521 | if (fs->state == idle && fs->wanted) { | |
522 | fs->state = available; | |
523 | wake_up(&fs->wait); | |
524 | return; | |
525 | } | |
526 | while (CURRENT && fs->state == idle) { | |
527 | if (CURRENT->bh && !buffer_locked(CURRENT->bh)) | |
528 | panic("floppy: block not locked"); | |
529 | #if 0 | |
530 | printk("do_fd_req: dev=%s cmd=%d sec=%ld nr_sec=%ld buf=%p\n", | |
531 | CURRENT->rq_disk->disk_name, CURRENT->cmd, | |
532 | CURRENT->sector, CURRENT->nr_sectors, CURRENT->buffer); | |
533 | printk(" rq_status=%d errors=%d current_nr_sectors=%ld\n", | |
534 | CURRENT->rq_status, CURRENT->errors, CURRENT->current_nr_sectors); | |
535 | #endif | |
536 | ||
537 | if (CURRENT->sector < 0 || CURRENT->sector >= fs->total_secs) { | |
538 | end_request(CURRENT, 0); | |
539 | continue; | |
540 | } | |
541 | if (CURRENT->current_nr_sectors == 0) { | |
542 | end_request(CURRENT, 1); | |
543 | continue; | |
544 | } | |
545 | if (fs->ejected) { | |
546 | end_request(CURRENT, 0); | |
547 | continue; | |
548 | } | |
549 | ||
550 | swimiop_init_request(&req); | |
551 | req.fs = fs; | |
552 | req.done = fd_request_complete; | |
553 | ||
554 | if (CURRENT->cmd == WRITE) { | |
555 | if (fs->write_prot) { | |
556 | end_request(CURRENT, 0); | |
557 | continue; | |
558 | } | |
559 | cmd->code = CMD_WRITE; | |
560 | } else { | |
561 | cmd->code = CMD_READ; | |
562 | ||
563 | } | |
564 | cmd->drive_num = fs->drive_num; | |
565 | cmd->buffer = CURRENT->buffer; | |
566 | cmd->first_block = CURRENT->sector; | |
567 | cmd->num_blocks = CURRENT->current_nr_sectors; | |
568 | ||
569 | if (swimiop_send_request(&req)) { | |
570 | end_request(CURRENT, 0); | |
571 | continue; | |
572 | } | |
573 | ||
574 | set_timeout(fs, HZ*CURRENT->current_nr_sectors, | |
575 | fd_request_timeout); | |
576 | ||
577 | fs->state = transferring; | |
578 | } | |
579 | } |