1/*
2 * Copyright (c) 2004-2007 Apple Inc. All rights reserved.
3 *
4 * IMPORTANT:  This Apple software is supplied to you by Apple Inc. ("Apple") in
5 * consideration of your agreement to the following terms, and your use, installation,
6 * modification or redistribution of this Apple software constitutes acceptance of these
7 * terms.  If you do not agree with these terms, please do not use, install, modify or
8 * redistribute this Apple software.
9 *
10 * In consideration of your agreement to abide by the following terms, and subject to these
11 * terms, Apple grants you a personal, non exclusive license, under Apple�s copyrights in this
12 * original Apple software (the �Apple Software�), to use, reproduce, modify and redistribute
13 * the Apple Software, with or without modifications, in source and/or binary forms; provided
14 * that if you redistribute the Apple Software in its entirety and without modifications, you
15 * must retain this notice and the following text and disclaimers in all such redistributions
16 * of the Apple Software.  Neither the name, trademarks, service marks or logos of Apple
17 * Computer, Inc. may be used to endorse or promote products derived from the Apple Software
18 * without specific prior written permission from Apple. Except as expressly stated in this
19 * notice, no other rights or licenses, express or implied, are granted by Apple herein,
20 * including but not limited to any patent rights that may be infringed by your derivative
21 * works or by other works in which the Apple Software may be incorporated.
22 *
23 * The Apple Software is provided by Apple on an "AS IS" basis.  APPLE MAKES NO WARRANTIES,
24 * EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED WARRANTIES OF NON-
25 * INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, REGARDING THE APPLE
26 * SOFTWARE OR ITS USE AND OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS.
27 *
28 * IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
30 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE,
31 * REPRODUCTION, MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND
32 * WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR
33 * OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
34 */
35
36
37//�����������������������������������������������������������������������������
38//	Imports
39//�����������������������������������������������������������������������������
40
41#import "SCSITargetProberAppDelegate.h"
42#import "SCSITargetProberDocument.h"
43#import "SCSITargetProberKeys.h"
44#import "SCSIInitiator.h"
45#import "AppPreferences.h"
46#import "FakeSCSIInitiator.h"
47#import <IOKit/IOKitLib.h>
48
49
50//�����������������������������������������������������������������������������
51//	Constants
52//�����������������������������������������������������������������������������
53
54#define kIOSCSIParallelInterfaceControllerClassString	"IOSCSIParallelInterfaceController"
55#define kIOSCSITargetDeviceClassString					"IOSCSITargetDevice"
56
57
58//�����������������������������������������������������������������������������
59//	Prototypes
60//�����������������������������������������������������������������������������
61
62static void
63ClearIterator ( io_iterator_t iterator );
64
65static int
66domainComparator ( id obj1, id obj2, void * context );
67
68
69//�����������������������������������������������������������������������������
70//	Implementation
71//�����������������������������������������������������������������������������
72
73@implementation SCSITargetProberAppDelegate
74
75
76- ( id ) init
77{
78
79	self = [ super init ];
80
81	if ( self != nil )
82	{
83
84		port					= NULL;
85		appearedIterator		= MACH_PORT_NULL;
86		disappearedIterator		= MACH_PORT_NULL;
87
88		[ self setInitiators: [ [ [ NSMutableArray alloc ] initWithCapacity: 0 ] autorelease ] ];
89
90	}
91
92	return self;
93
94}
95
96
97- ( NSArray * ) initiators
98{
99	return initiators;
100}
101
102
103- ( void ) setInitiators: ( NSMutableArray * ) i
104{
105
106	[ i retain ];
107	[ initiators release ];
108	initiators = i;
109
110}
111
112
113- ( void ) applicationDidFinishLaunching: ( NSNotification * ) notification
114{
115
116#pragma unused ( notification )
117
118	NSMutableDictionary *	dict = [ [ [ NSMutableDictionary alloc ] init ] autorelease ];
119
120	// Set some initial defaults. If the user doesn't have a customized user
121	// defaults database (they haven't modified the prefs window), we want to
122	// have the basic defaults of "ID" and "Description" visible in the table
123	// view.
124	[ dict setValue: [ NSNumber numberWithBool: YES ] forKey: kShowTargetIDString ];
125	[ dict setValue: [ NSNumber numberWithBool: YES ] forKey: kShowDescriptionString ];
126	[ dict setValue: [ NSNumber numberWithBool: NO ] forKey: kShowRevisionString ];
127	[ dict setValue: [ NSNumber numberWithBool: NO ] forKey: kShowFeaturesString ];
128	[ dict setValue: [ NSNumber numberWithBool: NO ] forKey: kShowPDTString ];
129
130	// Set the standard user defaults up.
131	[ [ NSUserDefaultsController sharedUserDefaultsController ] setInitialValues: dict ];
132
133	// Start our IONotificationPort stuff
134	[ self startNotifications ];
135
136	// If no controllers are found, tell the user.
137	if ( [ initiators count ] == 0 )
138	{
139
140		// No cards found. Tell the user then quit.
141		NSRunAlertPanel ( NSLocalizedString ( kNoControllersFoundTitle, "" ),
142						  NSLocalizedString ( kNoControllersFoundText, "" ),
143						  nil, nil, nil );
144		[ NSApp terminate: self ];
145
146	}
147
148	else
149	{
150		// Create a new document window
151		[ self newDocument: self ];
152	}
153
154}
155
156
157// Action called to instantiate a new document. This is called from
158// -applicationDidFinishLaunching and by the Menu Item File->New
159// and by the Cmd + 'N' key sequence.
160
161- ( IBAction ) newDocument: ( id ) sender
162{
163#pragma unused ( sender )
164	[ SCSITargetProberDocument newDocument: initiators ];
165}
166
167
168// Action called to load the preferences window. This is called from
169// SCSITargetProber->Preferences and from Cmd + ',' key sequence.
170
171- ( IBAction ) showPrefs: ( id ) sender
172{
173#pragma unused ( sender )
174	[ AppPreferences showPrefs ];
175}
176
177
178// Called to start IOKit notifications. We get notifications when
179// new IOSCSIParallelInterfaceController subclasses register
180// and when IOSCSITargetDevice objects register (they don't do
181// that yet but they will soon). Until IOSCSITargetDevice objects
182// register, the UI will not update on a successful reprobe, but
183// the plumbing is already done in the app to make the UI refresh
184// when that changes in the kernel.
185
186- ( void ) startNotifications
187{
188
189	IOReturn				result			= kIOReturnSuccess;
190	CFRunLoopSourceRef		source			= NULL;
191	CFRunLoopRef			runLoop			= NULL;
192	CFMutableDictionaryRef	matchingDict	= NULL;
193
194	// Create a notification port on which to receive messages.
195	port = IONotificationPortCreate ( kIOMasterPortDefault );
196	require ( ( port != NULL ), ErrorExit );
197
198	// Get the runloop source so we can attach it to our runloop.
199	source = IONotificationPortGetRunLoopSource ( port );
200	require ( ( source != NULL ), ReleasePort );
201
202	// Create the matching dictionary. Our matching dictionary uses
203	// the "IOSCSIParallelInterfaceController" class matching - basically
204	// any device which inherits from IOSCSIParallelInterfaceController.
205	matchingDict = IOServiceMatching ( kIOSCSIParallelInterfaceControllerClassString );
206	require ( ( matchingDict != NULL ), ReleaseSource );
207
208	// Retain the dictionary as IOServiceAddMatchingNotification() retains one
209	// reference.
210	CFRetain ( matchingDict );
211
212	// Set up notifications on first matches (plug in of CardBus SCSI cards).
213	result = IOServiceAddMatchingNotification ( port,
214												kIOFirstMatchNotification,
215												matchingDict,
216												AppearedNotificationHandler,
217												( void * ) self,
218												&appearedIterator );
219
220	// Since we got back an iterator already from this routine, we call the handler to immediately
221	// dispatch any devices there already
222	AppearedNotificationHandler ( ( void * ) self, appearedIterator );
223
224	// Set up notifications on termination (unplug of CardBus SCSI cards).
225	result = IOServiceAddMatchingNotification ( port,
226												kIOTerminatedNotification,
227												matchingDict,
228												DisappearedNotificationHandler,
229												( void * ) self,
230												&disappearedIterator );
231
232	// Since we got back an iterator already from this routine, we call the handler to immediately
233	// dispatch any devices there already
234	DisappearedNotificationHandler ( ( void * ) self, disappearedIterator );
235
236	// Create the matching dictionary. Our matching dictionary uses
237	// the "IOSCSITargetDevice" class matching - basically
238	// any device which inherits from IOSCSITargetDevice.
239	matchingDict = IOServiceMatching ( kIOSCSITargetDeviceClassString );
240	require ( ( matchingDict != NULL ), ReleaseSource );
241
242	// Retain the dictionary as IOServiceAddMatchingNotification() retains one
243	// reference.
244	CFRetain ( matchingDict );
245
246	// Set up notifications on first matches (new device arrives when we reprobe).
247	result = IOServiceAddMatchingNotification ( port,
248												kIOFirstMatchNotification,
249												matchingDict,
250												AppearedNotificationHandler,
251												( void * ) self,
252												&appearedIterator );
253
254	// Since we got back an iterator already, we want to empty it. We'll find target devices at
255	// launch by iterating over the registry. The dynamic stuff is for when we reprobe...
256	ClearIterator ( appearedIterator );
257
258	// Set up notifications on termination (device goes away).
259	result = IOServiceAddMatchingNotification ( port,
260												kIOTerminatedNotification,
261												matchingDict,
262												DisappearedNotificationHandler,
263												( void * ) self,
264												&disappearedIterator );
265
266	// Since we got back an iterator already, we want to empty it. We'll find target devices at
267	// launch by iterating over the registry. The dynamic stuff is for when we reprobe...
268	ClearIterator ( disappearedIterator );
269
270	// Get our run loop so we can add the notification's source to it.
271	runLoop = [ [ NSRunLoop currentRunLoop ] getCFRunLoop ];
272
273	// Add our runLoopSource to our runLoop
274	CFRunLoopAddSource ( runLoop, source, kCFRunLoopDefaultMode );
275
276	return;
277
278
279ReleaseSource:
280
281
282	CFRelease ( source );
283	source = NULL;
284
285
286ReleasePort:
287
288
289	IONotificationPortDestroy ( port );
290	port = NULL;
291
292
293ErrorExit:
294
295
296	return;
297
298}
299
300
301// Called to stop IOKit notifications.
302
303- ( void ) stopNotifications
304{
305
306	// Destroy the notify port if necessary
307	if ( port != NULL )
308	{
309
310		IONotificationPortDestroy ( port );
311		port = NULL;
312
313	}
314
315	// Destroy the iterator used for device appeared notifications
316	if ( appearedIterator != MACH_PORT_NULL )
317	{
318
319		( void ) IOObjectRelease ( appearedIterator );
320		appearedIterator = MACH_PORT_NULL;
321
322	}
323
324	// Destroy the iterator used for device disappeared notifications
325	if ( disappearedIterator != MACH_PORT_NULL )
326	{
327
328		( void ) IOObjectRelease ( disappearedIterator );
329		disappearedIterator = MACH_PORT_NULL;
330
331	}
332
333}
334
335
336// Called when devices we registered an interest in appear.
337
338- ( void ) appearedNotification: ( io_iterator_t ) iterator
339{
340
341	io_service_t	service = MACH_PORT_NULL;
342
343	service = IOIteratorNext ( iterator );
344	while ( service != MACH_PORT_NULL )
345	{
346
347		// Is this an IOSCSIParallelInterfaceController?
348		if ( IOObjectConformsTo ( service, kIOSCSIParallelInterfaceControllerClassString ) )
349		{
350
351			SCSIInitiator *		newInitiator = nil;
352
353			// Yes, it is an IOSCSIParallelInterfaceController. Create a
354			// SCSIInitiator object to represent it.
355			newInitiator = [ [ SCSIInitiator alloc ] initWithService: service ];
356
357			// Add the new object to the list.
358			[ initiators addObject: newInitiator ];
359
360			// Sort the list.
361			[ initiators sortUsingFunction: domainComparator context: nil ];
362
363			// Adding to the list retains the object, so we can safely release
364			// our refcount on it. When the object is removed from the list,
365			// it will be released.
366			[ newInitiator release ];
367
368		}
369
370		// Is this an IOSCSITargetDevice?
371		else if ( IOObjectConformsTo ( service, kIOSCSITargetDeviceClassString ) )
372		{
373
374			SCSIDevice *	newDevice	= nil;
375			SCSIInitiator * initiator	= nil;
376			io_service_t	parent		= MACH_PORT_NULL;
377			int				domainID	= 0;
378			int				count		= 0;
379			int				index		= 0;
380
381			// Get the IOSCSIParallelInterfaceDevice object.
382			IORegistryEntryGetParentEntry ( service, kIOServicePlane, &parent );
383
384			// Create the SCSIDevice object with the IOSCSIParallelInterfaceDevice.
385			newDevice = [ [ SCSIDevice alloc ] initWithService: parent ];
386
387			// Release the parent, we don't need it any more...
388			IOObjectRelease ( parent );
389
390			// Get the domainID for this new device.
391			domainID = [ [ newDevice domainIdentifier ] intValue ];
392
393			// Find out where to add the target device. Look at
394			// each initiator to see if the domainID matches.
395			count = [ initiators count ];
396			for ( index = 0; index < count; index++ )
397			{
398
399				initiator = [ initiators objectAtIndex: index ];
400
401				// Does domainID match?
402				if ( domainID == [ initiator domainID ] )
403				{
404
405					// Yes, add the target device to this initiator.
406					[ initiator addTargetDevice: newDevice ];
407
408				}
409
410			}
411
412			// We can safely release this device now. Either it was added
413			// to an initiator, or there wasn't an initator to associate it
414			// with...
415			[ newDevice release ];
416
417		}
418
419		// On to the next in the list...
420		IOObjectRelease ( service );
421		service = IOIteratorNext ( iterator );
422
423	}
424
425}
426
427
428// Called when devices we registered an interest in disappear.
429
430- ( void ) disappearedNotification: ( io_iterator_t ) iterator
431{
432
433	io_service_t	service = MACH_PORT_NULL;
434
435	service = IOIteratorNext ( iterator );
436	while ( service != MACH_PORT_NULL )
437	{
438
439		// Is this an IOSCSIParallelInterfaceController?
440		if ( IOObjectConformsTo ( service, kIOSCSIParallelInterfaceControllerClassString ) )
441		{
442
443			int				domainID	= 0;
444			NSEnumerator *	enumerator	= nil;
445			SCSIInitiator * initiator	= nil;
446
447			// Yes, get the domainID for this io_service_t.
448			domainID = [ SCSIInitiator domainIDForService: service ];
449
450			enumerator = [ initiators objectEnumerator ];
451			initiator = [ enumerator nextObject ];
452
453			// Find out which initiator to remove from the list.
454			while ( initiator != nil )
455			{
456
457				// Does the domainID match?
458				if ( [ initiator domainID ] == domainID )
459				{
460
461					// Yes, remove this one.
462					[ initiators removeObject: initiator ];
463					break;
464
465				}
466
467				// On to the next object..
468				initiator = [ enumerator nextObject ];
469
470			}
471
472		}
473
474		// Is this an IOSCSITargetDevice?
475		else if ( IOObjectConformsTo ( service, kIOSCSITargetDeviceClassString ) )
476		{
477
478			SCSIInitiator * initiator	= nil;
479			int				domainID	= 0;
480			int				count		= 0;
481			int				index		= 0;
482			int				targetID	= 0;
483
484			// Yes, get the domainID and targetID.
485			domainID = [ SCSIDevice domainIDForService: service ];
486			targetID = [ SCSIDevice targetIDForService: service ];
487
488			// Find which initiator has the same domainID.
489			count = [ initiators count ];
490			for ( index = 0; index < count; index++ )
491			{
492
493				initiator = [ initiators objectAtIndex: index ];
494
495				// Does the domainID match?
496				if ( domainID == [ initiator domainID ] )
497				{
498
499					// Yes, remove the device from this initiator's list.
500					[ initiator removeTargetDevice: targetID ];
501					break;
502
503				}
504
505			}
506
507		}
508
509		// On to the next in the list...
510		IOObjectRelease ( service );
511		service = IOIteratorNext ( iterator );
512
513	}
514
515}
516
517
518- ( void ) dealloc
519{
520
521	[ self stopNotifications ];
522	[ self setInitiators: nil ];
523
524	// Call our superclass
525	[ super dealloc ];
526
527}
528
529
530@end
531
532
533#if 0
534#pragma mark -
535#pragma mark Static methods
536#pragma mark -
537#endif
538
539
540//�����������������������������������������������������������������������������
541//	domainComparator - Compares devices based on deviceIdentifier field
542//�����������������������������������������������������������������������������
543
544static int
545domainComparator ( id obj1, id obj2, void * context )
546{
547
548#pragma unused ( context )
549
550	int		result = NSOrderedSame;
551
552	if ( [ obj1 domainID ] < [ obj2 domainID ] )
553	{
554		result = NSOrderedAscending;
555	}
556
557	if ( [ obj1 domainID ] > [ obj2 domainID ] )
558	{
559		result = NSOrderedDescending;
560	}
561
562	return result;
563
564}
565
566
567//�����������������������������������������������������������������������������
568//	ClearIterator - Clears devices from an iterator without performing
569//					any action
570//�����������������������������������������������������������������������������
571
572static void
573ClearIterator ( io_iterator_t iterator )
574{
575
576	io_service_t	service = MACH_PORT_NULL;
577
578	service = IOIteratorNext ( iterator );
579	while ( service != MACH_PORT_NULL )
580	{
581
582		IOObjectRelease ( service );
583		service = IOIteratorNext ( iterator );
584
585	}
586
587}
588
589
590#if 0
591#pragma mark -
592#pragma mark C->Obj-C glue
593#pragma mark -
594#endif
595
596
597//�����������������������������������������������������������������������������
598//	AppearedNotificationHandler - C->Obj-C glue
599//�����������������������������������������������������������������������������
600
601static void
602AppearedNotificationHandler ( void * refCon, io_iterator_t iterator )
603{
604
605	SCSITargetProberAppDelegate *	app = ( SCSITargetProberAppDelegate * ) refCon;
606	[ app appearedNotification: iterator ];
607
608}
609
610
611//�����������������������������������������������������������������������������
612//	DisappearedNotificationHandler - C->Obj-C glue
613//�����������������������������������������������������������������������������
614
615static void
616DisappearedNotificationHandler ( void * refCon, io_iterator_t iterator )
617{
618
619	SCSITargetProberAppDelegate *	app = ( SCSITargetProberAppDelegate * ) refCon;
620	[ app disappearedNotification: iterator ];
621
622}