2 * drivers/cdrom/viocd.c
4 ***************************************************************************
5 * iSeries Virtual CD Rom
7 * Authors: Dave Boutcher <boutcher@us.ibm.com>
8 * Ryan Arnold <ryanarn@us.ibm.com>
9 * Colin Devilbiss <devilbis@us.ibm.com>
11 * (C) Copyright 2000 IBM Corporation
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License as
15 * published by the Free Software Foundation; either version 2 of the
16 * License, or (at your option) anyu later version.
18 * This program is distributed in the hope that it will be useful, but
19 * WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 * General Public License for more details.
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software Foundation,
25 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
26 ***************************************************************************
27 * This routine provides access to CD ROM drives owned and managed by an
28 * OS/400 partition running on the same box as this Linux partition.
30 * All operations are performed by sending messages back and forth to
31 * the OS/400 partition.
34 * This device driver can either use it's own major number, or it can
35 * pretend to be an AZTECH drive. This is controlled with a
36 * CONFIG option. You can either call this an elegant solution to the
37 * fact that a lot of software doesn't recognize a new CD major number...
38 * or you can call this a really ugly hack. Your choice.
42 #include <linux/major.h>
43 #include <linux/config.h>
45 /***************************************************************************
46 * Decide if we are using our own major or pretending to be an AZTECH drive
47 ***************************************************************************/
48 #ifdef CONFIG_VIOCD_AZTECH
49 #define MAJOR_NR AZTECH_CDROM_MAJOR
50 #define do_viocd_request do_aztcd_request
52 #define MAJOR_NR VIOCD_MAJOR
55 #define VIOCD_VERS "1.04"
57 #include <linux/blk.h>
58 #include <linux/cdrom.h>
59 #include <linux/errno.h>
60 #include <linux/init.h>
61 #include <linux/pci.h>
62 #include <linux/proc_fs.h>
63 #include <linux/module.h>
65 #include <asm/iSeries/HvTypes.h>
66 #include <asm/iSeries/HvLpEvent.h>
68 #include <asm/iSeries/iSeries_proc.h>
70 extern struct pci_dev * iSeries_vio_dev;
72 #define signalLpEvent HvCallEvent_signalLpEventFast
75 struct HvLpEvent event;
82 u64 mOffset; // On open, the max number of disks
83 u64 mLen; // On open, the size of the disk
84 u32 mBlockSize; // Only set on open
85 u32 mMediaSize; // Only set on open
93 viocdlockdoor = 0x0005,
94 viocdgetinfo = 0x0006,
98 /* Should probably make this a module parameter....sigh
100 #define VIOCD_MAX_CD 8
101 int viocd_blocksizes[VIOCD_MAX_CD];
102 static u64 viocd_size_in_bytes[VIOCD_MAX_CD];
104 /* This is the structure we use to exchange info between driver and interrupt
107 struct viocd_waitevent {
108 struct semaphore *sem;
113 /* this is a lookup table for the true capabilities of a device */
114 struct capability_entry {
119 static struct capability_entry capability_table[] = {
120 { "6330", CDC_LOCK | CDC_DVD_RAM },
121 { "6321", CDC_LOCK },
126 struct block_device_operations viocd_fops =
130 release: cdrom_release,
132 check_media_change: cdrom_media_changed,
135 /* These are our internal structures for keeping track of devices
137 static int viocd_numdev;
144 static struct cdrom_info *viocd_unitinfo = NULL;
151 static struct disk_info viocd_diskinfo[VIOCD_MAX_CD];
153 static struct cdrom_device_info viocd_info[VIOCD_MAX_CD];
155 static spinlock_t viocd_lock = SPIN_LOCK_UNLOCKED;
158 static LIST_HEAD(reqlist);
162 static int viocd_end_request(struct request *req, int uptodate)
164 if (end_that_request_first(req, uptodate, DEVICE_NAME))
166 end_that_request_last(req);
171 /* Get info on CD devices from OS/400
173 static void get_viocd_info(void)
178 DECLARE_MUTEX_LOCKED(Semaphore);
179 struct viocd_waitevent we;
181 // If we don't have a host, bail out
182 if (viopath_hostLp == HvLpIndexInvalid)
185 if (viocd_unitinfo == NULL)
187 kmalloc(sizeof(struct cdrom_info) * VIOCD_MAX_CD,
190 memset(viocd_unitinfo, 0x00,
191 sizeof(struct cdrom_info) * VIOCD_MAX_CD);
193 dmaaddr = pci_map_single(iSeries_vio_dev, viocd_unitinfo,
194 sizeof(struct cdrom_info) * VIOCD_MAX_CD,
196 if (dmaaddr == 0xFFFFFFFF) {
197 printk(KERN_WARNING_VIO "error allocating tce\n");
203 hvrc = signalLpEvent(viopath_hostLp,
204 HvLpEvent_Type_VirtualIo,
205 viomajorsubtype_cdio | viocdgetinfo,
206 HvLpEvent_AckInd_DoAck,
207 HvLpEvent_AckType_ImmediateAck,
208 viopath_sourceinst(viopath_hostLp),
209 viopath_targetinst(viopath_hostLp),
210 (u64) (unsigned long) &we,
214 sizeof(struct cdrom_info) * VIOCD_MAX_CD,
216 if (hvrc != HvLpEvent_Rc_Good) {
217 printk(KERN_WARNING_VIO "cdrom error sending event. rc %d\n", (int) hvrc);
224 printk(KERN_WARNING_VIO "bad rc %d on getinfo\n", we.rc);
229 for (i = 0; (i < VIOCD_MAX_CD) && (viocd_unitinfo[i].rsrcname[0]); i++) {
236 static int viocd_open(struct cdrom_device_info *cdi, int purpose)
238 DECLARE_MUTEX_LOCKED(Semaphore);
239 int device_no = MINOR(cdi->dev);
241 struct viocd_waitevent we;
242 struct disk_info *diskinfo = &viocd_diskinfo[device_no];
244 // If we don't have a host, bail out
245 if (viopath_hostLp == HvLpIndexInvalid || device_no >= viocd_numdev)
249 hvrc = signalLpEvent(viopath_hostLp,
250 HvLpEvent_Type_VirtualIo,
251 viomajorsubtype_cdio | viocdopen,
252 HvLpEvent_AckInd_DoAck,
253 HvLpEvent_AckType_ImmediateAck,
254 viopath_sourceinst(viopath_hostLp),
255 viopath_targetinst(viopath_hostLp),
256 (u64) (unsigned long) &we,
258 ((u64) device_no << 48),
261 printk(KERN_WARNING_VIO "bad rc on signalLpEvent %d\n",
271 if (diskinfo->useCount == 0) {
272 if(diskinfo->blocksize > 0) {
273 viocd_blocksizes[device_no] = diskinfo->blocksize;
274 viocd_size_in_bytes[device_no] = diskinfo->blocksize * diskinfo->mediasize;
276 viocd_size_in_bytes[device_no] = 0xFFFFFFFFFFFFFFFF;
285 static void viocd_release(struct cdrom_device_info *cdi)
287 int device_no = MINOR(cdi->dev);
290 /* If we don't have a host, bail out */
291 if (viopath_hostLp == HvLpIndexInvalid
292 || device_no >= viocd_numdev)
295 hvrc = signalLpEvent(viopath_hostLp,
296 HvLpEvent_Type_VirtualIo,
297 viomajorsubtype_cdio | viocdclose,
298 HvLpEvent_AckInd_NoAck,
299 HvLpEvent_AckType_ImmediateAck,
300 viopath_sourceinst(viopath_hostLp),
301 viopath_targetinst(viopath_hostLp),
304 ((u64) device_no << 48),
307 printk(KERN_WARNING_VIO "bad rc on signalLpEvent %d\n", (int) hvrc);
314 /* Send a read or write request to OS/400
316 static int send_request(struct request *req)
320 int device_no = DEVICE_NR(req->rq_dev);
321 u64 start = req->sector * 512,
322 len = req->current_nr_sectors * 512;
323 char reading = req->cmd == READ;
324 u16 command = reading ? viocdread : viocdwrite;
327 if(start + len > viocd_size_in_bytes[device_no]) {
328 printk(KERN_WARNING_VIO "viocd%d; access position %lx, past size %lx\n",
329 device_no, (unsigned long)(start + len), (unsigned long)viocd_size_in_bytes[device_no]);
333 dmaaddr = pci_map_single(iSeries_vio_dev, req->buffer, len,
334 reading ? PCI_DMA_FROMDEVICE : PCI_DMA_TODEVICE);
335 if (dmaaddr == 0xFFFFFFFF) {
336 printk(KERN_WARNING_VIO "error allocating tce for address %p len %ld\n",
337 req->buffer, (long)len);
341 /* FIXME: experimental
342 // note the special use of mReserved1 to indicate that a response
343 // is desired, but via NoAck...
344 // HvLpEvent_AckInd_NoAck,
345 // ((u64)0x00000001 << 32) | ((u64)VIOVERSION << 16),
348 hvrc = signalLpEvent(viopath_hostLp,
349 HvLpEvent_Type_VirtualIo,
350 viomajorsubtype_cdio | command,
351 HvLpEvent_AckInd_DoAck,
352 HvLpEvent_AckType_ImmediateAck,
353 viopath_sourceinst(viopath_hostLp),
354 viopath_targetinst(viopath_hostLp),
355 (u64) (unsigned long) req->buffer,
357 ((u64) device_no << 48) | dmaaddr,
359 if (hvrc != HvLpEvent_Rc_Good) {
360 printk(KERN_WARNING_VIO "hv error on op %d\n", (int) hvrc);
371 static void do_viocd_request(request_queue_t * q)
375 char err_str[80] = "";
379 if (rwreq >= MAX_CD_REQ) {
383 device_no = CURRENT_DEV;
385 /* remove the current request from the queue */
387 blkdev_dequeue_request(req);
389 /* check for any kind of error */
390 if (device_no > viocd_numdev)
391 sprintf(err_str, "Invalid device number %d", device_no);
392 else if (send_request(req) < 0)
393 strcpy(err_str, "unable to send message to OS/400!");
395 /* if we had any sort of error, log it and cancel the request */
397 printk(KERN_WARNING_VIO "%s\n", err_str);
398 viocd_end_request(req, 0);
400 spin_lock(&viocd_lock);
401 list_add_tail(&req->queue, &reqlist);
403 spin_unlock(&viocd_lock);
408 /* Check if the CD changed
410 static int viocd_media_changed(struct cdrom_device_info *cdi, int disc_nr)
412 struct viocd_waitevent we;
414 int device_no = MINOR(cdi->dev);
416 /* This semaphore is raised in the interrupt handler */
417 DECLARE_MUTEX_LOCKED(Semaphore);
419 /* Check that we are dealing with a valid hosting partition */
420 if (viopath_hostLp == HvLpIndexInvalid) {
421 printk(KERN_WARNING_VIO "Invalid hosting partition\n");
427 /* Send the open event to OS/400 */
428 hvrc = signalLpEvent(viopath_hostLp,
429 HvLpEvent_Type_VirtualIo,
430 viomajorsubtype_cdio | viocdcheck,
431 HvLpEvent_AckInd_DoAck,
432 HvLpEvent_AckType_ImmediateAck,
433 viopath_sourceinst(viopath_hostLp),
434 viopath_targetinst(viopath_hostLp),
435 (u64) (unsigned long) &we,
437 ((u64) device_no << 48),
441 printk(KERN_WARNING_VIO "bad rc on signalLpEvent %d\n", (int) hvrc);
445 /* Wait for the interrupt handler to get the response */
448 /* Check the return code. If bad, assume no change */
450 printk(KERN_WARNING_VIO "bad rc on check_change. Assuming no change\n");
457 static int viocd_lock_door(struct cdrom_device_info *cdi, int locking)
460 u64 device_no = MINOR(cdi->dev);
461 /* NOTE: flags is 1 or 0 so it won't overwrite the device_no */
462 u64 flags = !!locking;
463 /* This semaphore is raised in the interrupt handler */
464 DECLARE_MUTEX_LOCKED(Semaphore);
465 struct viocd_waitevent we = { sem:&Semaphore };
467 /* Check that we are dealing with a valid hosting partition */
468 if (viopath_hostLp == HvLpIndexInvalid) {
469 printk(KERN_WARNING_VIO "Invalid hosting partition\n");
475 /* Send the lockdoor event to OS/400 */
476 hvrc = signalLpEvent(viopath_hostLp,
477 HvLpEvent_Type_VirtualIo,
478 viomajorsubtype_cdio | viocdlockdoor,
479 HvLpEvent_AckInd_DoAck,
480 HvLpEvent_AckType_ImmediateAck,
481 viopath_sourceinst(viopath_hostLp),
482 viopath_targetinst(viopath_hostLp),
483 (u64) (unsigned long) &we,
485 (device_no << 48) | (flags << 32),
489 printk(KERN_WARNING_VIO "bad rc on signalLpEvent %d\n", (int) hvrc);
493 /* Wait for the interrupt handler to get the response */
496 /* Check the return code. If bad, assume no change */
504 /* This routine handles incoming CD LP events
506 static void vioHandleCDEvent(struct HvLpEvent *event)
508 struct viocdlpevent *bevent = (struct viocdlpevent *) event;
509 struct viocd_waitevent *pwe;
512 /* Notification that a partition went away! */
515 /* First, we should NEVER get an int here...only acks */
516 if (event->xFlags.xFunction == HvLpEvent_Function_Int) {
517 printk(KERN_WARNING_VIO "Yikes! got an int in viocd event handler!\n");
518 if (event->xFlags.xAckInd == HvLpEvent_AckInd_DoAck) {
519 event->xRc = HvLpEvent_Rc_InvalidSubtype;
520 HvCallEvent_ackLpEvent(event);
524 switch (event->xSubtype & VIOMINOR_SUBTYPE_MASK) {
526 viocd_diskinfo[bevent->mDisk].blocksize = bevent->mBlockSize;
527 viocd_diskinfo[bevent->mDisk].mediasize = bevent->mMediaSize;
531 pwe = (struct viocd_waitevent *) (unsigned long) event->xCorrelationToken;
532 pwe->rc = event->xRc;
542 int reading = ((event->xSubtype & VIOMINOR_SUBTYPE_MASK) == viocdread);
543 struct request *req = blkdev_entry_to_request(reqlist.next);
544 /* Since this is running in interrupt mode, we need to make sure we're not
545 * stepping on any global I/O operations
547 spin_lock_irqsave(&io_request_lock, flags);
549 pci_unmap_single(iSeries_vio_dev,
552 reading ? PCI_DMA_FROMDEVICE : PCI_DMA_TODEVICE);
554 /* find the event to which this is a response */
555 while ((&req->queue != &reqlist) &&
556 ((u64) (unsigned long) req->buffer != bevent->event.xCorrelationToken))
557 req = blkdev_entry_to_request(req->queue.next);
559 /* if the event was not there, then what are we responding to?? */
560 if (&req->queue == &reqlist) {
561 printk(KERN_WARNING_VIO "Yikes! we didn't ever enqueue this guy!\n");
562 spin_unlock_irqrestore(&io_request_lock,
567 /* we don't need to keep it around anymore... */
568 spin_lock(&viocd_lock);
569 list_del(&req->queue);
571 spin_unlock(&viocd_lock);
573 char stat = event->xRc == HvLpEvent_Rc_Good;
574 int nsect = bevent->mLen >> 9;
577 printk(KERN_WARNING_VIO
578 "request %p failed with rc %d:0x%08x\n",
579 req->buffer, event->xRc, bevent->mSubTypeRc);
580 while ((nsect > 0) && (req->bh)) {
581 nsect -= req->current_nr_sectors;
582 viocd_end_request(req, stat);
584 /* we weren't done yet */
586 if (send_request(req) < 0) {
587 printk(KERN_WARNING_VIO
588 "couldn't re-submit req %p\n", req->buffer);
589 viocd_end_request(req, 0);
591 spin_lock(&viocd_lock);
592 list_add_tail(&req->queue, &reqlist);
594 spin_unlock(&viocd_lock);
599 /* restart handling of incoming requests */
600 do_viocd_request(NULL);
601 spin_unlock_irqrestore(&io_request_lock, flags);
605 pwe = (struct viocd_waitevent *) (unsigned long) event->xCorrelationToken;
606 pwe->rc = event->xRc;
607 pwe->changed = bevent->mFlags;
612 printk(KERN_WARNING_VIO "invalid subtype!");
613 if (event->xFlags.xAckInd == HvLpEvent_AckInd_DoAck) {
614 event->xRc = HvLpEvent_Rc_InvalidSubtype;
615 HvCallEvent_ackLpEvent(event);
620 /* Our file operations table
622 static struct cdrom_device_ops viocd_dops = {
624 release:viocd_release,
625 media_changed:viocd_media_changed,
626 lock_door:viocd_lock_door,
627 capability:CDC_CLOSE_TRAY | CDC_OPEN_TRAY | CDC_LOCK | CDC_SELECT_SPEED | CDC_SELECT_DISC | CDC_MULTI_SESSION | CDC_MCN | CDC_MEDIA_CHANGED | CDC_PLAY_AUDIO | CDC_RESET | CDC_IOCTLS | CDC_DRIVE_STATUS | CDC_GENERIC_PACKET | CDC_CD_R | CDC_CD_RW | CDC_DVD | CDC_DVD_R | CDC_DVD_RAM
630 /* Handle reads from the proc file system
632 static int proc_read(char *buf, char **start, off_t offset,
633 int blen, int *eof, void *data)
638 for (i = 0; i < viocd_numdev; i++) {
641 "viocd device %d is iSeries resource %10.10s type %4.4s, model %3.3s\n",
642 i, viocd_unitinfo[i].rsrcname,
643 viocd_unitinfo[i].type,
644 viocd_unitinfo[i].model);
651 /* setup our proc file system entries
653 void viocd_proc_init(struct proc_dir_entry *iSeries_proc)
655 struct proc_dir_entry *ent;
656 ent = create_proc_entry("viocd", S_IFREG | S_IRUSR, iSeries_proc);
661 ent->read_proc = proc_read;
664 /* clean up our proc file system entries
666 void viocd_proc_delete(struct proc_dir_entry *iSeries_proc)
668 remove_proc_entry("viocd", iSeries_proc);
671 static int find_capability(const char *type)
673 struct capability_entry *entry;
674 for(entry = capability_table; entry->type; ++entry)
675 if(!strncmp(entry->type, type, 4))
677 return entry->capability;
680 /* Initialize the whole device driver. Handle module and non-module
683 __init int viocd_init(void)
687 if (viopath_hostLp == HvLpIndexInvalid)
690 /* If we don't have a host, bail out */
691 if (viopath_hostLp == HvLpIndexInvalid)
694 rc = viopath_open(viopath_hostLp, viomajorsubtype_cdio, MAX_CD_REQ+2);
696 printk(KERN_WARNING_VIO "error opening path to host partition %d\n",
701 /* Initialize our request handler
704 vio_setHandler(viomajorsubtype_cdio, vioHandleCDEvent);
706 memset(&viocd_diskinfo, 0x00, sizeof(viocd_diskinfo));
710 if (viocd_numdev == 0) {
711 vio_clearHandler(viomajorsubtype_cdio);
712 viopath_close(viopath_hostLp, viomajorsubtype_cdio, MAX_CD_REQ+2);
717 "%s: iSeries Virtual CD vers %s, major %d, max disks %d, hosting partition %d\n",
718 DEVICE_NAME, VIOCD_VERS, MAJOR_NR, VIOCD_MAX_CD, viopath_hostLp);
720 if (devfs_register_blkdev(MAJOR_NR, "viocd", &viocd_fops) != 0) {
721 printk(KERN_WARNING_VIO "Unable to get major %d for viocd CD-ROM\n", MAJOR_NR);
725 blksize_size[MAJOR_NR] = viocd_blocksizes;
726 blk_init_queue(BLK_DEFAULT_QUEUE(MAJOR_NR), DEVICE_REQUEST);
727 read_ahead[MAJOR_NR] = 4;
729 memset(&viocd_info, 0x00, sizeof(viocd_info));
730 for (i = 0; i < viocd_numdev; i++) {
731 viocd_info[i].dev = MKDEV(MAJOR_NR, i);
732 viocd_info[i].ops = &viocd_dops;
733 viocd_info[i].speed = 4;
734 viocd_info[i].capacity = 1;
735 viocd_info[i].mask = ~find_capability(viocd_unitinfo[i].type);
736 sprintf(viocd_info[i].name, "viocd%d", i);
737 if (register_cdrom(&viocd_info[i]) != 0) {
738 printk(KERN_WARNING_VIO "Cannot register viocd CD-ROM %s!\n", viocd_info[i].name);
741 "cd %s is iSeries resource %10.10s type %4.4s, model %3.3s\n",
743 viocd_unitinfo[i].rsrcname,
744 viocd_unitinfo[i].type,
745 viocd_unitinfo[i].model);
750 * Create the proc entry
752 iSeries_proc_callback(&viocd_proc_init);
758 void viocd_exit(void)
761 for (i = 0; i < viocd_numdev; i++) {
762 if (unregister_cdrom(&viocd_info[i]) != 0) {
763 printk(KERN_WARNING_VIO "Cannot unregister viocd CD-ROM %s!\n", viocd_info[i].name);
766 if ((devfs_unregister_blkdev(MAJOR_NR, "viocd") == -EINVAL)) {
767 printk(KERN_WARNING_VIO "can't unregister viocd\n");
770 blk_cleanup_queue(BLK_DEFAULT_QUEUE(MAJOR_NR));
772 kfree(viocd_unitinfo);
774 iSeries_proc_callback(&viocd_proc_delete);
776 viopath_close(viopath_hostLp, viomajorsubtype_cdio, MAX_CD_REQ+2);
777 vio_clearHandler(viomajorsubtype_cdio);
782 module_init(viocd_init);
783 module_exit(viocd_exit);