|
|
1.1 root 1: /*
2: * Copyright (c) 1999 Apple Computer, Inc. All rights reserved.
3: *
4: * @APPLE_LICENSE_HEADER_START@
5: *
6: * Portions Copyright (c) 1999 Apple Computer, Inc. All Rights
7: * Reserved. This file contains Original Code and/or Modifications of
8: * Original Code as defined in and that are subject to the Apple Public
9: * Source License Version 1.1 (the "License"). You may not use this file
10: * except in compliance with the License. Please obtain a copy of the
11: * License at http://www.apple.com/publicsource and read it before using
12: * this file.
13: *
14: * The Original Code and all software distributed under the License are
15: * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
16: * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
17: * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
18: * FITNESS FOR A PARTICULAR PURPOSE OR NON- INFRINGEMENT. Please see the
19: * License for the specific language governing rights and limitations
20: * under the License.
21: *
22: * @APPLE_LICENSE_HEADER_END@
23: */
24: /* Copyright (c) 1991 NeXT Computer, Inc. All rights reserved.
25: *
26: * SCSIDisk.m - Implementation of exported methods of SCSI Disk device class.
27: *
28: * HISTORY
29: *
30: * 30-Jul-98 Dan Markarian at Apple. See Radar 2260508, 2258860. If the
31: * SCSIDisk driver attempts to probe a non-zero LUN and a
32: * target exists on LUN 0 that is actively doing (tagged) I/O,
33: * the target may abort all commands with an overlapped command
34: * error, causing the original command to fail without completion.
35: * Suggested work-arounds (for MacOS X Server, release 1): probe
36: * only LUN 0. If a target is discovered, reserve all LUNs for
37: * that target. This code must be revisited for IOKit.
38: *
39: * 11-Feb-91 Doug Mitchell at NeXT
40: * Created.
41: */
42:
43: #define MACH_USER_API 1
44: #undef KERNEL_PRIVATE
45:
46: #import <bsd/sys/types.h>
47: #import <driverkit/return.h>
48: #import <driverkit/IODisk.h>
49: #import <driverkit/SCSIDiskPrivate.h>
50: #import <driverkit/SCSIDiskTypes.h>
51: #import <driverkit/SCSIDisk.h>
52: #import <driverkit/SCSIDiskKern.h>
53: #import <driverkit/SCSIStructInlines.h>
54: #import <bsd/dev/scsireg.h>
55: #import <driverkit/xpr_mi.h>
56: #import <kernserv/prototypes.h>
57: #import <mach/mach_interface.h>
58: #import <driverkit/kernelDiskMethods.h>
59: #import <machkit/NXLock.h>
60:
61: #ifdef DEBUG
62: #define SCSI_SA_TEST 0
63: #else DEBUG
64: #define SCSI_SA_TEST 0
65: #endif DEBUG
66:
67: /*
68: * FIXME - ensure that diskUnit doesn't exceed NUM_SD_DEV.
69: */
70: static int diskUnit = 0;
71:
72: @implementation SCSIDisk
73:
74: + (IODeviceStyle)deviceStyle
75: {
76: return IO_IndirectDevice;
77: }
78:
79: /*
80: * The protocol we need as an indirect device.
81: */
82: static Protocol *protocols[] = {
83: @protocol(IOSCSIControllerExported),
84: nil
85: };
86:
87: + (Protocol **)requiredProtocols
88: {
89: return protocols;
90: }
91:
92:
93: + initialize
94: {
95: if(self == [SCSIDisk class]) {
96: sd_init_idmap();
97: }
98: return [super initialize];
99: }
100:
101: /*
102: * probe is invoked at load time. It determines what devices are on the
103: * bus and alloc's and init:'s an instance of this class for each one.
104: */
105: + (BOOL)probe : deviceDescription;
106: {
107: char Target, Lun;
108: sdInitReturn_t irtn = SDR_ERROR;
109: SCSIDisk *diskId = nil;
110: IODevAndIdInfo *idMap = sd_idmap();
111: id controllerId = [deviceDescription directDevice];
112: BOOL brtn = NO;
113: SCSIDisk *diskIdArray[SCSI_NLUNS];
114: int nLuns;
115: /*
116: * Radar 2005639
117: */
118: unsigned int paramValue[1];
119: unsigned int count = 1;
120: IOReturn ioReturn;
121:
122: /* asm volatile("int3"); */
123:
124: #if hppa
125: /* search from the top down on hp since that was done before */
126: for(Target=[controllerId numberOfTargets]-1; Target>=0; Target--)
127: #else
128: for(Target=0; Target<[controllerId numberOfTargets]; Target++)
129: #endif
130: {
131:
132: #if 0 // Prior to Radar Fix #2260508
133: for(Lun=nLuns=0; Lun<SCSI_NLUNS; Lun++) {
134: #else // Radar Fix #2260508
135: for(Lun=nLuns=0; Lun<1; Lun++) {
136: #endif // Radar Fix #2260508
137: if(diskId == nil) {
138: /*
139: * Create an instance, do some basic
140: * initialization. Set up a default
141: * device name for error reporting during
142: * initialization.
143: */
144: diskId = [SCSIDisk alloc];
145: [diskId setName:"SCSIDisk"];
146: /*
147: * Radar 2005639. If the controller implements
148: * the getIntValues APPLE_MAX_THREADS selector,
149: * instantiate with the specified number of
150: * of threads, otherwise let the diskID
151: * instantiate itself with its default number.
152: */
153: ioReturn = [controllerId getIntValues
154: : ¶mValue[0]
155: forParameter : APPLE_MAX_THREADS
156: count : &count];
157: if (ioReturn == IO_R_SUCCESS && count == 1) {
158: IOLog("%s: %s requests %d threads\n",
159: [self name],
160: [controllerId name],
161: paramValue[0]
162: );
163: [diskId initResourcesWithThreadCount
164: : paramValue[0]];
165: }
166: else {
167: IOLog(
168: "%s: %s does not support %s selector, using default\n",
169: [self name],
170: [controllerId name],
171: APPLE_MAX_THREADS
172: );
173: [diskId initResources];
174: }
175: [diskId setDevAndIdInfo:&(idMap[diskUnit])];
176: }
177: if([controllerId reserveTarget:Target
178: lun:Lun
179: forOwner:diskId]) {
180: /*
181: * Someone already has this one.
182: */
183: continue;
184: }
185: diskId->_isReserved = 1;
186: #if 1 // Radar Fix #2260508
187: /*
188: * A valid target at LUN 0 has been reserved: reserve
189: * all non-zero LUNs as well. Note that _controller
190: * must be set before calling this next method, so go
191: * ahead and set it ourselves since the diskId object
192: * might be newly allocated (and yet-uninitialized).
193: */
194: diskId->_controller = controllerId;
195: [diskId reserveNonZeroLunsOnTarget:Target];
196: #endif // Radar Fix #2260508
197: irtn = [diskId SCSIDiskInit:(int)diskUnit
198: targetId:Target
199: lun:Lun
200: controller:controllerId];
201: [diskId setDeviceDescription:deviceDescription];
202: switch(irtn) {
203: case SDR_GOOD:
204: /*
205: * Postpone registering this device
206: * until we have looked at all other LUNs.
207: * This prevents I/O to multiple LUNs
208: * while we are probing, which is not handled
209: * well by some devices.
210: */
211: diskIdArray[nLuns++] = diskId;
212: diskUnit++;
213: diskId = nil;
214: brtn = YES;
215: break;
216:
217: default:
218: #if 1 // Radar Fix #2260508
219: /*
220: * LUN 0 will be unreserved: unreserve all
221: * non-zero LUNs first. Note _controller
222: * is already set at this point.
223: */
224: [diskId releaseNonZeroLunsOnTarget:Target];
225: #endif // Radar Fix #2260508
226: [controllerId releaseTarget:Target
227: lun:Lun
228: forOwner:diskId];
229: diskId->_isReserved = 0;
230:
231: if(irtn == SDR_SELECTTO) {
232: /*
233: * Skip the rest of the luns on
234: * this target.
235: */
236: goto nextTarget;
237: }
238: /*
239: * else try next lun.
240: */
241: }
242: } /* for lun */
243: nextTarget:
244: /* Now we have looked at all luns. */
245: for (Lun=0; Lun < nLuns; Lun++) {
246: SCSIDisk *thisId = diskIdArray[Lun];
247: /*
248: * All right! Have IODisk superclass take
249: * care of the rest.
250: */
251: [thisId setDeviceKind:"SCSIDisk"];
252: [thisId setIsPhysical:YES];
253: [thisId registerDevice];
254: thisId->_isRegistered = 1;
255: }
256: continue;
257: } /* for target */
258:
259: /*
260: * Free up leftover owner and id. At this point, diskId does NOT have
261: * a target/lun reserved.
262: */
263: if(diskId) {
264: [diskId free];
265: }
266:
267: #if SCSI_SA_TEST
268: diskTest("sd0");
269: #endif SCSI_SA_TEST
270: return brtn;
271: }
272:
273: /*
274: * IODiskReadingAndWriting protocol methods.
275: */
276: - (IOReturn) readAt : (unsigned)offset
277: length : (unsigned)length
278: buffer : (unsigned char *)buffer
279: actualLength : (unsigned *)actualLength
280: client : (vm_task_t)client
281: {
282: IOReturn rtn;
283:
284: xpr_sd("%s read: offset 0x%x length 0x%x\n",
285: [self name], offset, length, 4,5);
286: rtn = [self deviceRwCommon : SDOP_READ
287: block : offset
288: length : length
289: buffer : buffer
290: client: client
291: pending : NULL
292: actualLength : actualLength];
293: xpr_sd("%s read: RETURNING %s\n", [self name],
294: [self stringFromReturn:rtn], 3,4,5);
295: return(rtn);
296: }
297:
298: - (IOReturn) readAsyncAt : (unsigned)offset
299: length : (unsigned)length
300: buffer : (unsigned char *)buffer
301: pending : (void *)pending
302: client : (vm_task_t)client
303: {
304: IOReturn rtn;
305:
306: xpr_sd("%s readAsync: offset 0x%x length 0x%x\n",
307: [self name], offset, length, 4,5);
308: rtn = [self deviceRwCommon : SDOP_READ
309: block : offset
310: length : length
311: buffer : buffer
312: client : client
313: pending : (void *)pending
314: actualLength : NULL];
315: xpr_sd("%s readAsync: RETURNING %s\n", [self name],
316: [self stringFromReturn:rtn], 3,4,5);
317: return(rtn);
318: }
319:
320: - (IOReturn) writeAt : (unsigned)offset
321: length : (unsigned)length
322: buffer : (unsigned char *)buffer
323: actualLength : (unsigned *)actualLength
324: client : (vm_task_t)client
325: {
326: IOReturn rtn;
327:
328: xpr_sd("%s write: offset 0x%x length 0x%x\n",
329: [self name], offset, length, 4,5);
330: rtn = [self deviceRwCommon : SDOP_WRITE
331: block : offset
332: length : length
333: buffer : buffer
334: client: client
335: pending : NULL
336: actualLength : actualLength];
337: xpr_sd("%s deviceWrite: RETURNING %s\n", [self name],
338: [self stringFromReturn:rtn], 3,4,5);
339: return(rtn);
340: }
341:
342: - (IOReturn) writeAsyncAt : (unsigned)offset
343: length : (unsigned)length
344: buffer : (unsigned char *)buffer
345: pending : (void *)pending
346: client : (vm_task_t)client
347: {
348: IOReturn rtn;
349:
350: xpr_sd("%s writeAsync: offset 0x%x length 0x%x\n",
351: [self name], offset, length, 4,5);
352: rtn = [self deviceRwCommon : SDOP_WRITE
353: block : offset
354: length : length
355: buffer : buffer
356: client : client
357: pending : (void *)pending
358: actualLength : NULL];
359: xpr_sd("%s writeAsync: RETURNING %s\n", [self name],
360: [self stringFromReturn:rtn], 3,4,5);
361: return(rtn);
362: }
363:
364:
365: - (void) synchronizeCache
366: {
367: IOSCSIRequest scsiReq;
368: cdb_10_t *cdbp = &scsiReq.cdb.cdb_c10;
369: sdBuf_t *sdBuf;
370:
371: bzero(&scsiReq, sizeof(IOSCSIRequest));
372: scsiReq.target = _target;
373: scsiReq.lun = _lun;
374: scsiReq.timeoutLength = SD_TIMEOUT_SIMPLE;
375: scsiReq.disconnect = 1;
376:
377: sdBuf = [self allocSdBuf:NULL];
378: sdBuf->command = SDOP_CDB_WRITE;
379: sdBuf->scsiReq = &scsiReq;
380: sdBuf->retryDisable = 1;
381: sdBuf->needsDisk = 0;
382:
383: /* Zeroing the lba and count fields will cause the drive to
384: * flush everything. We should be able to zero these fields
385: * explicitly, but for some reason the 10-byte commands don't
386: * have the proper field definitions.
387: */
388: bzero(cdbp,10);
389:
390: cdbp->c10_opcode = 0x35;
391: cdbp->c10_lun = _lun;
392:
393: [self enqueueSdBuf:sdBuf];
394:
395: [self freeSdBuf:sdBuf];
396: }
397:
398: - (IODiskReadyState)updateReadyState
399: {
400: IOSCSIRequest scsiReq;
401: cdb_6_t *cdbp = &scsiReq.cdb.cdb_c6;
402: sdBuf_t *sdBuf;
403: IODiskReadyState readyState;
404: esense_reply_t senseReply;
405:
406: xpr_sd("%s updateReadyState\n", [self name], 2,3,4,5);
407:
408: if( NO == _isReserved) {
409: return([self lastReadyState]);
410: }
411:
412: bzero(&scsiReq, sizeof(IOSCSIRequest));
413: scsiReq.target = _target;
414: scsiReq.lun = _lun;
415: scsiReq.timeoutLength = SD_TIMEOUT_SIMPLE;
416: scsiReq.disconnect = 1;
417:
418: sdBuf = [self allocSdBuf:NULL];
419: sdBuf->command = SDOP_CDB_WRITE;
420: sdBuf->scsiReq = &scsiReq;
421: sdBuf->retryDisable = 1;
422: sdBuf->needsDisk = 0;
423:
424: cdbp->c6_opcode = C6OP_TESTRDY;
425: cdbp->c6_lun = _lun;
426:
427: [self enqueueSdBuf:sdBuf];
428:
429: /*
430: * FIXME - how to distinguish not ready from no disk???
431: */
432: switch(scsiReq.driverStatus) {
433: case SR_IOST_GOOD:
434: readyState = IO_Ready;
435: break;
436: default:
437: readyState = IO_NotReady;
438: break;
439:
440: }
441: xpr_sd("%s updateReadyState: DONE; state = %s\n",
442: [self name],
443: IOFindNameForValue(readyState, readyStateValues), 3,4,5);
444: [self freeSdBuf:sdBuf];
445: return(readyState);
446: }
447:
448: /*
449: * IOPhysicalDiskMethods protocol methods.
450: */
451: - (IOReturn) ejectPhysical
452: {
453: return [self scsiStartStop:SS_EJECT inhibitRetry:NO];
454: }
455:
456:
457: /*
458: * Get physical parameters (dev_size, block_size, etc.) from new disk.
459: */
460: - (IOReturn)updatePhysicalParameters
461: {
462: capacity_reply_t capacityData;
463: mode_sel_data_t modeData;
464: sc_status_t rtn;
465: int block;
466: void *dataBuf;
467: void *freePtr;
468: unsigned freeCnt;
469: u_int block_size;
470: BOOL wp = NO;
471: int i;
472:
473: rtn = [self updateReadyState];
474: if(rtn) {
475: return IO_R_NOT_READY;
476: }
477: rtn = [self sdReadCapacity:&capacityData];
478: if(rtn) {
479: return(IO_R_IO);
480: }
481: [self setDiskSize:scsi_lastlba(&capacityData) + 1];
482: [self setBlockSize:scsi_blklen(&capacityData)];
483:
484: /*
485: * Try reading a block to see if disk is formatted.
486: */
487: block_size = [self blockSize];
488: dataBuf = [_controller allocateBufferOfLength:block_size
489: actualStart:&freePtr
490: actualLength:&freeCnt];
491: block = 10;
492: for(i=0; i<5; i++) {
493: rtn = [self sdRawRead:block blockCnt:1 buffer:dataBuf];
494: if(rtn == IO_R_SUCCESS) {
495: break;
496: }
497: block += 10;
498: }
499: if(rtn == IO_R_SUCCESS)
500: [self setFormattedInternal:1];
501: else
502: [self setFormattedInternal:0];
503:
504: IOFree(freePtr, freeCnt);
505:
506: /*
507: * Mark CD-ROMs as write-protected; use a mode sense for the others.
508: */
509:
510: if(_inquiryDeviceType == DEVTYPE_CDROM)
511: wp = YES;
512: else {
513: bzero(&modeData, sizeof(mode_sel_data_t));
514: rtn = [self sdModeSense:&modeData];
515:
516: if (modeData.msd_header.msh_wp)
517: wp = YES;
518: }
519:
520: [self setWriteProtected: wp];
521:
522: return(IO_R_SUCCESS);
523: }
524:
525: /*
526: * Called by volCheck thread when WS has told us that a requested disk is
527: * not present. Pending I/Os which require a disk to be present must be
528: * aborted.
529: */
530: - (void)abortRequest
531: {
532: sdBuf_t *sdBuf;
533:
534: xpr_sd("%s: abortRequest\n", [self name], 2,3,4,5);
535:
536: sdBuf = [self allocSdBuf:NULL];
537: sdBuf->command = SDOP_ABORT;
538: sdBuf->needsDisk = 0;
539: [self enqueueSdBuf:sdBuf];
540: xpr_sd("%s abortRequest: done %s\n", [self name], 2,3,4,5);
541: [self freeSdBuf:sdBuf];
542: return;
543: }
544:
545: /*
546: * Called by the volCheck thread when a transition to "ready" is detected.
547: * Pending I/Os which require a disk may proceed. All we have to do is
548: * wakeup the I/O threads which are waiting for something to show up
549: * in an I/O queue.
550: */
551: - (void)diskBecameReady
552: {
553: xpr_sd("diskBecameReady: %s\n", [self name], 2,3,4,5);
554: [_ioQLock lock];
555: [_ioQLock unlockWith:WORK_AVAILABLE];
556: }
557:
558: /*
559: * Inquire if disk is present; if not, and 'prompt' is YES, ask for it.
560: * Returns IO_R_NO_DISK if:
561: * prompt YES, disk not present, and user cancels request for disk.
562: * prompt NO, disk not present.
563: * Else returns IO_R_SUCCESS.
564: */
565: - (IOReturn)isDiskReady : (BOOL)prompt
566: {
567: sdBuf_t *sdBuf;
568: IOReturn rtn;
569:
570: xpr_sd("%s: diskBecameReady\n", [self name], 2,3,4,5);
571:
572: if( NO == _isReserved)
573: return(IO_R_NO_DISK);
574:
575: if([self lastReadyState] == IO_Ready) {
576: /*
577: * This one's easy...
578: */
579: return(IO_R_SUCCESS);
580: }
581: if(!prompt) {
582: return(IO_R_NO_DISK);
583: }
584: sdBuf = [self allocSdBuf:NULL];
585: sdBuf->command = SDOP_PROBEDISK;
586: sdBuf->needsDisk = 1;
587: rtn = [self enqueueSdBuf:sdBuf];
588: xpr_sd("%s diskBecameReady: returning %s\n", [self name],
589: [self stringFromReturn:rtn], 3,4,5);
590: [self freeSdBuf:sdBuf];
591: return(rtn);
592: }
593:
594: /*
595: * We have to override IODisk's setLastReadyState: so we know when to
596: * clear our local ejectPending flag.
597: */
598: - (void)setLastReadyState : (IODiskReadyState)readyState
599: {
600: xpr_sd("sd setLastReadyState\n", 1,2,3,4,5);
601: if(_ejectPending && (readyState != IO_Ejecting))
602: _ejectPending = 0;
603: [super setLastReadyState:readyState];
604: }
605:
606: /*
607: * Exported methods unique to the SCSIDisk class.
608: * Note caller must provide well-aligned DMA buffers.
609: */
610: - (IOReturn) sdCdbRead : (IOSCSIRequest *)scsiReq /* SCSI parameters */
611: buffer:(void *)buffer /* data destination */
612: client:(vm_task_t)client;
613: {
614: sdBuf_t *sdBuf;
615: IOReturn rtn;
616:
617: xpr_sd("sd%d sdCdbRead: opcode = 0x%x\n", [self unit],
618: scsiReq->cdb.cdb_opcode, 3,4,5);
619:
620: sdBuf = [self allocSdBuf:NULL];
621: sdBuf->command = SDOP_CDB_READ;
622: sdBuf->scsiReq = scsiReq;
623: sdBuf->buf = buffer;
624: sdBuf->client = client;
625: sdBuf->pending = NULL;
626: sdBuf->needsDisk = 0;
627: rtn = [self enqueueSdBuf:sdBuf];
628: xpr_sd("sd%d sdCdbRead: returning %s\n", [self unit],
629: [self stringFromReturn:rtn], 3,4,5);
630: [self freeSdBuf:sdBuf];
631: return(rtn);
632: }
633:
634: - (IOReturn) sdCdbWrite : (IOSCSIRequest *)scsiReq /* SCSI parameters */
635: buffer:(void *)buffer /* data destination */
636: client:(vm_task_t)client
637: {
638: sdBuf_t *sdBuf;
639: IOReturn rtn;
640:
641: xpr_sd("sd%d sdCdbWrite: opcode = 0x%x\n", [self unit],
642: scsiReq->cdb.cdb_opcode, 3,4,5);
643:
644: sdBuf = [self allocSdBuf:NULL];
645: sdBuf->command = SDOP_CDB_WRITE;
646: sdBuf->scsiReq = scsiReq;
647: sdBuf->buf = buffer;
648: sdBuf->client = client;
649: sdBuf->pending = NULL;
650: sdBuf->needsDisk = 0;
651: rtn = [self enqueueSdBuf:sdBuf];
652: xpr_sd("sd%d sdCdbWrite: returning %s\n", [self unit],
653: [self stringFromReturn:rtn], 3,4,5);
654: [self freeSdBuf:sdBuf];
655: return(rtn);
656: }
657:
658: - (int)target
659: {
660: return _target;
661: }
662:
663: - (int)lun
664: {
665: return _lun;
666: }
667:
668: /*
669: * This is mainly here for the convenience of 486 CDROM boot. It's cheap so
670: * we'll leave it in for other platforms too.
671: */
672: - (unsigned char)inquiryDeviceType
673: {
674: return _inquiryDeviceType;
675: }
676:
677: - controller
678: {
679: return _controller;
680: }
681:
682: - getDevicePath:(char *)path maxLength:(int)maxLen useAlias:(BOOL)doAlias
683: {
684: if( [super getDevicePath:path maxLength:maxLen useAlias:doAlias]) {
685:
686: char unitStr[ 12 ];
687: int len = maxLen - strlen( path);
688:
689: sprintf( unitStr, "/@%x", [self target]);
690: len -= strlen( unitStr);
691: if( len < 0)
692: return( nil);
693: strcat( path, unitStr);
694: if( [self lun]) {
695: sprintf( unitStr, ",%x", [self lun]);
696: len -= strlen( unitStr);
697: if( len < 0)
698: return( nil);
699: strcat( path, unitStr);
700: }
701: return( self);
702: }
703: return( nil);
704: }
705:
706: - (char *) matchDevicePath:(char *)matchPath
707: {
708: BOOL matches = NO;
709: char * unitStr;
710: extern long int strtol(const char *nptr, char **endptr, int base);
711:
712: unitStr = [super matchDevicePath:matchPath];
713: if( unitStr) {
714: unitStr = (char *)strchr( unitStr, '@');
715: if( unitStr) {
716: matches = ([self target] == strtol( unitStr + 1, &unitStr, 16));
717: if( matches && (*unitStr == ','))
718: matches = ([self lun] == strtol( unitStr + 1, &unitStr, 16));
719: }
720: }
721: if( matches)
722: return( unitStr);
723: else
724: return( NULL);
725: }
726:
727: - property_IOUnit:(char *)result length:(unsigned int *)maxLen
728: {
729: if( [self lun])
730: sprintf( result, "%d,%d", [self target], [self lun]);
731: else
732: sprintf( result, "%d", [self target]);
733:
734: return( self);
735: }
736:
737: - property_IODeviceType:(char *)types length:(unsigned int *)maxLen
738: {
739: static const char * inquiryDevTypeStrings[ 9 ] = {
740: 0, IOTypeTape, IOTypePrinter, 0,
741: IOTypeWORM, IOTypeCDROM, IOTypeScanner, IOTypeOptical,
742: IOTypeChanger
743: };
744:
745: [super property_IODeviceType:types length:maxLen];
746:
747: if( (_inquiryDeviceType <= 8) && inquiryDevTypeStrings[ _inquiryDeviceType ]) {
748: strcat( types, " ");
749: strcat( types, inquiryDevTypeStrings[ _inquiryDeviceType ]);
750: }
751: return( self);
752: }
753:
754: @end
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.