diff options
Diffstat (limited to 'drivers/block/swim_iop.c')
-rw-r--r-- | drivers/block/swim_iop.c | 579 |
1 files changed, 579 insertions, 0 deletions
diff --git a/drivers/block/swim_iop.c b/drivers/block/swim_iop.c new file mode 100644 index 000000000000..a1283f6dc018 --- /dev/null +++ b/drivers/block/swim_iop.c | |||
@@ -0,0 +1,579 @@ | |||
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 (funaho@jurai.org) | ||
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 (funaho@jurai.org)\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 | } | ||