2 * Copyright (c) 1998-2000 Apple Computer, Inc. All rights reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * The contents of this file constitute Original Code as defined in and
7 * are subject to the Apple Public Source License Version 1.1 (the
8 * "License"). You may not use this file except in compliance with the
9 * License. Please obtain a copy of the License at
10 * http://www.apple.com/publicsource and read it before using this file.
12 * This Original Code and all software distributed under the License are
13 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
14 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
15 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT. Please see the
17 * License for the specific language governing rights and limitations
20 * @APPLE_LICENSE_HEADER_END@
23 * Copyright (c) 1998 Apple Computer, Inc. All rights reserved.
26 * 23 Nov 98 sdouglas created from objc version.
27 * 05 Nov 99 sdouglas added UniNorth AGP based on UniNorthAGPDriver.c
28 * by Fernando Urbina, Kent Miller.
32 #include <IOKit/system.h>
33 #include <ppc/proc_reg.h>
35 #include <libkern/c++/OSContainers.h>
36 #include <libkern/OSByteOrder.h>
38 #include <IOKit/IODeviceMemory.h>
39 #include <IOKit/IORangeAllocator.h>
40 #include <IOKit/IODeviceTreeSupport.h>
41 #include <IOKit/IOPlatformExpert.h>
42 #include <IOKit/IOLib.h>
43 #include <IOKit/assert.h>
45 #include "AppleMacRiscPCI.h"
47 #define ALLOC_AGP_RANGE 0
49 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
51 #define super IOPCIBridge
53 OSDefineMetaClassAndStructors(AppleMacRiscPCI
, IOPCIBridge
)
55 OSDefineMetaClassAndStructors(AppleMacRiscVCI
, AppleMacRiscPCI
)
57 OSDefineMetaClassAndStructors(AppleMacRiscAGP
, AppleMacRiscPCI
)
59 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
61 bool AppleMacRiscPCI::start( IOService
* provider
)
63 IOPCIPhysicalAddress ioAddrCell
;
64 IOPhysicalAddress ioPhys
;
65 IOPhysicalAddress ioPhysLen
;
67 IODeviceMemory::InitElement rangeList
[ 3 ];
68 IORegistryEntry
* bridge
;
71 if( !IODTMatchNubWithKeys(provider
, "('pci', 'vci')"))
74 if( IODTMatchNubWithKeys(provider
, "'uni-north'"))
75 configDataOffsetMask
= 0x7;
77 configDataOffsetMask
= 0x3;
79 if( 0 == (lock
= IOSimpleLockAlloc()))
82 ioAddrCell
.physHi
.bits
= 0;
83 ioAddrCell
.physHi
.s
.space
= kIOPCIIOSpace
;
84 ioAddrCell
.physMid
= 0;
85 ioAddrCell
.physLo
= 0;
86 ioAddrCell
.lengthHi
= 0;
87 ioAddrCell
.lengthLo
= 0x10000;
91 if( ! IODTResolveAddressCell( bridge
, (UInt32
*) &ioAddrCell
,
92 &ioPhys
, &ioPhysLen
) ) {
94 IOLog("%s: couldn't find my base\n", getName());
98 /* define more explicit ranges */
100 rangeList
[0].start
= ioPhys
;
101 rangeList
[0].length
= ioPhysLen
;
102 rangeList
[1].start
= ioPhys
+ 0x00800000;
103 rangeList
[1].length
= 4;
104 rangeList
[2].start
= ioPhys
+ 0x00c00000;
105 rangeList
[2].length
= 4;
107 IORangeAllocator
* platformRanges
;
108 platformRanges
= IOService::getPlatform()->getPhysicalRangeAllocator();
109 assert( platformRanges
);
110 platformRanges
->allocateRange( ioPhys
, 0x01000000 );
112 array
= IODeviceMemory::arrayFromList( rangeList
, 3 );
116 provider
->setDeviceMemory( array
);
118 ioMemory
= (IODeviceMemory
*) array
->getObject( 0 );
122 if( (configAddrMap
= provider
->mapDeviceMemoryWithIndex( 1 )))
123 configAddr
= (volatile UInt32
*) configAddrMap
->getVirtualAddress();
124 if( (configDataMap
= provider
->mapDeviceMemoryWithIndex( 2 )))
125 configData
= (volatile UInt8
*) configDataMap
->getVirtualAddress();
127 if( !configAddr
|| !configData
)
130 busProp
= (OSData
*) bridge
->getProperty("bus-range");
132 primaryBus
= *((UInt32
*) busProp
->getBytesNoCopy());
134 return( super::start( provider
));
137 bool AppleMacRiscPCI::configure( IOService
* provider
)
139 UInt32 addressSelects
;
143 addressSelects
= configRead32( getBridgeSpace(), kMacRISCAddressSelect
);
145 coarseAddressMask
= addressSelects
>> 16;
146 fineAddressMask
= addressSelects
& 0xffff;
148 for( index
= 0; index
< 15; index
++ ) {
149 if( coarseAddressMask
& (1 << index
)) {
150 ok
= addBridgeMemoryRange( index
<< 28, 0x10000000, true );
154 // if( coarseAddressMask & (1 << 15)) // F segment
155 for( index
= 0; index
< 15; index
++ ) {
156 if( fineAddressMask
& (1 << index
)) {
157 ok
= addBridgeMemoryRange( (0xf0 | index
) << 24,
162 ok
= addBridgeIORange( 0, 0x10000 );
164 return( super::configure( provider
));
167 void AppleMacRiscPCI::free()
170 configAddrMap
->release();
172 configDataMap
->release();
174 IOSimpleLockFree( lock
);
179 IODeviceMemory
* AppleMacRiscPCI::ioDeviceMemory( void )
184 IODeviceMemory
* AppleMacRiscVCI::ioDeviceMemory( void )
189 bool AppleMacRiscVCI::configure( IOService
* provider
)
191 addBridgeMemoryRange( 0x90000000, 0x10000000, true );
193 return( AppleMacRiscPCI::configure( provider
));
196 UInt8
AppleMacRiscPCI::firstBusNum( void )
198 return( primaryBus
);
201 UInt8
AppleMacRiscPCI::lastBusNum( void )
203 return( firstBusNum() );
206 IOPCIAddressSpace
AppleMacRiscPCI::getBridgeSpace( void )
208 IOPCIAddressSpace space
;
211 space
.s
.busNum
= primaryBus
;
212 space
.s
.deviceNum
= kBridgeSelfDevice
;
217 inline bool AppleMacRiscPCI::setConfigSpace( IOPCIAddressSpace space
,
223 if( space
.s
.busNum
== primaryBus
) {
225 if( space
.s
.deviceNum
< kBridgeSelfDevice
)
228 // primary config cycle
229 addrCycle
= ( (1 << space
.s
.deviceNum
)
230 | (space
.s
.functionNum
<< 8)
234 // pass thru config cycle
235 addrCycle
= ( (space
.bits
)
241 OSWriteSwapInt32( configAddr
, 0, addrCycle
);
243 } while( addrCycle
!= OSReadSwapInt32( configAddr
, 0 ));
250 UInt32
AppleMacRiscPCI::configRead32( IOPCIAddressSpace space
,
254 IOInterruptState ints
;
256 ints
= IOSimpleLockLockDisableInterrupt( lock
);
258 if( setConfigSpace( space
, offset
)) {
260 offset
= offset
& configDataOffsetMask
& 4;
262 data
= OSReadSwapInt32( configData
, offset
);
268 IOSimpleLockUnlockEnableInterrupt( lock
, ints
);
273 void AppleMacRiscPCI::configWrite32( IOPCIAddressSpace space
,
274 UInt8 offset
, UInt32 data
)
276 IOInterruptState ints
;
278 ints
= IOSimpleLockLockDisableInterrupt( lock
);
280 if( setConfigSpace( space
, offset
)) {
282 offset
= offset
& configDataOffsetMask
& 4;
284 OSWriteSwapInt32( configData
, offset
, data
);
287 (void) OSReadSwapInt32( configData
, offset
);
293 IOSimpleLockUnlockEnableInterrupt( lock
, ints
);
296 UInt16
AppleMacRiscPCI::configRead16( IOPCIAddressSpace space
,
300 IOInterruptState ints
;
302 ints
= IOSimpleLockLockDisableInterrupt( lock
);
304 if( setConfigSpace( space
, offset
)) {
306 offset
= offset
& configDataOffsetMask
& 6;
308 data
= OSReadSwapInt16( configData
, offset
);
314 IOSimpleLockUnlockEnableInterrupt( lock
, ints
);
319 void AppleMacRiscPCI::configWrite16( IOPCIAddressSpace space
,
320 UInt8 offset
, UInt16 data
)
322 IOInterruptState ints
;
324 ints
= IOSimpleLockLockDisableInterrupt( lock
);
326 if( setConfigSpace( space
, offset
)) {
328 offset
= offset
& configDataOffsetMask
& 6;
330 OSWriteSwapInt16( configData
, offset
, data
);
333 (void) OSReadSwapInt16( configData
, offset
);
339 IOSimpleLockUnlockEnableInterrupt( lock
, ints
);
342 UInt8
AppleMacRiscPCI::configRead8( IOPCIAddressSpace space
,
346 IOInterruptState ints
;
348 ints
= IOSimpleLockLockDisableInterrupt( lock
);
350 if( setConfigSpace( space
, offset
)) {
352 offset
= offset
& configDataOffsetMask
;
354 data
= configData
[ offset
];
360 IOSimpleLockUnlockEnableInterrupt( lock
, ints
);
365 void AppleMacRiscPCI::configWrite8( IOPCIAddressSpace space
,
366 UInt8 offset
, UInt8 data
)
368 IOInterruptState ints
;
370 ints
= IOSimpleLockLockDisableInterrupt( lock
);
372 if( setConfigSpace( space
, offset
)) {
374 offset
= offset
& configDataOffsetMask
;
376 configData
[ offset
] = data
;
379 data
= configData
[ offset
];
385 IOSimpleLockUnlockEnableInterrupt( lock
, ints
);
388 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
391 #define super AppleMacRiscPCI
393 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
395 bool AppleMacRiscAGP::configure( IOService
* provider
)
397 if( !findPCICapability( getBridgeSpace(), kIOPCIAGPCapability
, &targetAGPRegisters
))
400 return( super::configure( provider
));
403 IOPCIDevice
* AppleMacRiscAGP::createNub( OSDictionary
* from
)
406 IOPCIAddressSpace space
;
408 UInt8 masterAGPRegisters
;
410 spaceFromProperties( from
, &space
);
412 isAGP
= ( (space
.s
.deviceNum
!= getBridgeSpace().s
.deviceNum
)
413 && findPCICapability( space
, kIOPCIAGPCapability
, &masterAGPRegisters
));
416 nub
= new IOAGPDevice
;
418 ((IOAGPDevice
*)nub
)->masterAGPRegisters
= masterAGPRegisters
;
419 from
->setObject( kIOAGPBusFlagsKey
, getProperty(kIOAGPBusFlagsKey
));
421 nub
= super::createNub( from
);
426 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
428 IOReturn
AppleMacRiscAGP::createAGPSpace( IOAGPDevice
* master
,
429 IOOptionBits options
,
430 IOPhysicalAddress
* address
,
431 IOPhysicalLength
* length
)
434 IOPCIAddressSpace target
= getBridgeSpace();
435 IOPhysicalLength agpLength
;
436 IOPhysicalAddress gartPhys
;
438 enum { agpSpacePerPage
= 4 * 1024 * 1024 };
439 enum { agpBytesPerGartByte
= 1024 };
440 enum { alignLen
= 4 * 1024 * 1024 - 1 };
442 destroyAGPSpace( master
);
446 agpLength
= 32 * 1024 * 1024;
448 agpLength
= (agpLength
+ alignLen
) & ~alignLen
;
450 err
= kIOReturnVMError
;
453 gartLength
= agpLength
/ agpBytesPerGartByte
;
454 gartArray
= (volatile UInt32
*) IOMallocContiguous(
455 gartLength
, 4096, &gartPhys
);
458 // IOMapPages( kernel_map, gartArray, gartPhys, gartLength, kIOMapInhibitCache );
459 bzero( (void *) gartArray
, gartLength
);
462 IORangeAllocator
* platformRanges
463 = getPlatform()->getPhysicalRangeAllocator();
464 for( agpBaseIndex
= 0xf; agpBaseIndex
> 0; agpBaseIndex
--) {
465 systemBase
= agpBaseIndex
* 0x10000000;
466 if( platformRanges
->allocateRange( systemBase
, agpLength
)) {
467 systemLength
= agpLength
;
474 systemLength
= agpLength
;
479 agpRange
= IORangeAllocator::withRange( agpLength
, 4096 );
483 *address
= systemBase
;
484 *length
= systemLength
;
486 coarseAddressMask
|= (1 << agpBaseIndex
);
487 configWrite32( target
, kMacRISCAddressSelect
,
488 (coarseAddressMask
<< 16) | fineAddressMask
);
490 configWrite32( target
, kUniNAGP_BASE
, agpBaseIndex
<< 28 );
492 assert( 0 == (gartPhys
& 0xfff));
493 configWrite32( target
, kUniNGART_BASE
,
494 gartPhys
| (agpLength
/ agpSpacePerPage
));
496 err
= kIOReturnSuccess
;
500 if( kIOReturnSuccess
== err
)
501 setAGPEnable( master
, true, 0 );
503 destroyAGPSpace( master
);
508 IOReturn
AppleMacRiscAGP::getAGPSpace( IOAGPDevice
* master
,
509 IOPhysicalAddress
* address
,
510 IOPhysicalLength
* length
)
515 *address
= systemBase
;
517 *length
= systemLength
;
518 return( kIOReturnSuccess
);
521 return( kIOReturnNotReady
);
524 IOReturn
AppleMacRiscAGP::destroyAGPSpace( IOAGPDevice
* master
)
527 setAGPEnable( master
, false, 0 );
530 IOFreeContiguous( (void *) gartArray
, gartLength
);
539 IORangeAllocator
* platformRanges
540 = getPlatform()->getPhysicalRangeAllocator();
541 platformRanges
->deallocate( systemBase
, systemLength
);
546 return( kIOReturnSuccess
);
549 IORangeAllocator
* AppleMacRiscAGP::getAGPRangeAllocator(
550 IOAGPDevice
* master
)
552 // if( agpRange) agpRange->retain();
556 IOOptionBits
AppleMacRiscAGP::getAGPStatus( IOAGPDevice
* master
,
557 IOOptionBits options
= 0 )
559 IOPCIAddressSpace target
= getBridgeSpace();
561 return( configRead32( target
, kUniNINTERNAL_STATUS
) );
564 IOReturn
AppleMacRiscAGP::resetAGPDevice( IOAGPDevice
* master
,
565 IOOptionBits options
= 0 )
569 if( master
->masterState
& kIOAGPStateEnablePending
) {
570 ret
= setAGPEnable( master
, true, 0 );
571 master
->masterState
&= ~kIOAGPStateEnablePending
;
573 ret
= kIOReturnSuccess
;
578 IOReturn
AppleMacRiscAGP::commitAGPMemory( IOAGPDevice
* master
,
579 IOMemoryDescriptor
* memory
,
580 IOByteCount agpOffset
,
581 IOOptionBits options
= 0 )
583 IOPCIAddressSpace target
= getBridgeSpace();
584 IOReturn err
= kIOReturnSuccess
;
586 IOPhysicalAddress physAddr
;
589 // ok = agpRange->allocate( memory->getLength(), &agpOffset );
591 assert( agpOffset
< systemLength
);
592 agpOffset
/= (page_size
/ 4);
593 while( (physAddr
= memory
->getPhysicalSegment( offset
, &len
))) {
596 len
= (len
+ 0xfff) & ~0xfff;
598 OSWriteLittleInt32( gartArray
, agpOffset
,
599 ((physAddr
& ~0xfff) | 1));
601 physAddr
+= page_size
;
606 flush_dcache( (vm_offset_t
) gartArray
, gartLength
, false);
607 len
= OSReadLittleInt32( gartArray
, agpOffset
- 4 );
612 if( kIOAGPGartInvalidate
& options
) {
613 configWrite32( target
, kUniNGART_CTRL
, kGART_EN
| kGART_INV
);
614 configWrite32( target
, kUniNGART_CTRL
, kGART_EN
);
615 configWrite32( target
, kUniNGART_CTRL
, kGART_EN
| kGART_2xRESET
);
616 configWrite32( target
, kUniNGART_CTRL
, kGART_EN
);
622 IOReturn
AppleMacRiscAGP::releaseAGPMemory( IOAGPDevice
* master
,
623 IOMemoryDescriptor
* memory
,
624 IOByteCount agpOffset
,
625 IOOptionBits options
)
627 IOPCIAddressSpace target
= getBridgeSpace();
628 IOReturn err
= kIOReturnSuccess
;
632 return( kIOReturnBadArgument
);
634 length
= memory
->getLength();
636 if( (agpOffset
+ length
) > systemLength
)
637 return( kIOReturnBadArgument
);
639 // agpRange->deallocate( agpOffset, length );
641 length
= (length
+ 0xfff) & ~0xfff;
642 agpOffset
/= page_size
;
644 gartArray
[ agpOffset
++ ] = 0;
648 flush_dcache( (vm_offset_t
) gartArray
, gartLength
, false);
649 length
= OSReadLittleInt32( gartArray
, 4 * (agpOffset
- 1) );
654 if( kIOAGPGartInvalidate
& options
) {
655 configWrite32( target
, kUniNGART_CTRL
, kGART_EN
| kGART_INV
);
656 configWrite32( target
, kUniNGART_CTRL
, kGART_EN
);
657 configWrite32( target
, kUniNGART_CTRL
, kGART_EN
| kGART_2xRESET
);
658 configWrite32( target
, kUniNGART_CTRL
, kGART_EN
);
664 IOReturn
AppleMacRiscAGP::setAGPEnable( IOAGPDevice
* _master
,
665 bool enable
, IOOptionBits options
)
667 IOReturn err
= kIOReturnSuccess
;
668 IOPCIAddressSpace target
= getBridgeSpace();
669 IOPCIAddressSpace master
= _master
->space
;
671 UInt32 targetStatus
, masterStatus
;
672 UInt8 masterAGPRegisters
= _master
->masterAGPRegisters
;
676 targetStatus
= configRead32( target
,
677 targetAGPRegisters
+ kIOPCIConfigAGPStatusOffset
);
678 masterStatus
= configRead32( master
,
679 masterAGPRegisters
+ kIOPCIConfigAGPStatusOffset
);
681 command
= kIOAGPSideBandAddresssing
682 | kIOAGP4xDataRate
| kIOAGP2xDataRate
| kIOAGP1xDataRate
;
683 command
&= targetStatus
;
684 command
&= masterStatus
;
686 if( command
& kIOAGP4xDataRate
)
687 command
&= ~(kIOAGP2xDataRate
| kIOAGP1xDataRate
);
688 else if( command
& kIOAGP2xDataRate
)
689 command
&= ~(kIOAGP1xDataRate
);
690 else if( 0 == (command
& kIOAGP1xDataRate
))
691 return( kIOReturnUnsupported
);
693 command
|= kIOAGPEnable
;
695 if( targetStatus
> masterStatus
)
696 targetStatus
= masterStatus
;
697 command
|= (targetStatus
& kIOAGPRequestQueueMask
);
700 configWrite32( target
, kUniNGART_CTRL
, kGART_EN
| kGART_INV
);
701 configWrite32( target
, kUniNGART_CTRL
, kGART_EN
);
702 configWrite32( target
, kUniNGART_CTRL
, kGART_EN
| kGART_2xRESET
);
703 configWrite32( target
, kUniNGART_CTRL
, kGART_EN
);
706 configWrite32( target
, targetAGPRegisters
+ kIOPCIConfigAGPCommandOffset
, command
);
707 } while( (command
& kIOAGPEnable
) !=
708 (kIOAGPEnable
& configRead32( target
, targetAGPRegisters
+ kIOPCIConfigAGPCommandOffset
)));
711 configWrite32( master
,
712 masterAGPRegisters
+ kIOPCIConfigAGPCommandOffset
, command
);
713 } while( (command
& kIOAGPEnable
) !=
714 (kIOAGPEnable
& configRead32( master
,
715 masterAGPRegisters
+ kIOPCIConfigAGPCommandOffset
)));
717 configWrite32( target
, kUniNGART_CTRL
, kGART_EN
| kGART_INV
);
718 configWrite32( target
, kUniNGART_CTRL
, kGART_EN
);
719 configWrite32( target
, kUniNGART_CTRL
, kGART_EN
| kGART_2xRESET
);
720 configWrite32( target
, kUniNGART_CTRL
, kGART_EN
);
722 _master
->masterState
|= kIOAGPStateEnabled
;
726 while( 0 == (kIOAGPIdle
& configRead32( getBridgeSpace(),
727 kUniNINTERNAL_STATUS
)))
730 configWrite32( master
, masterAGPRegisters
+ kIOPCIConfigAGPCommandOffset
, 0 );
731 configWrite32( target
, targetAGPRegisters
+ kIOPCIConfigAGPCommandOffset
, 0 );
733 configWrite32( target
, kUniNGART_CTRL
, kGART_EN
| kGART_INV
);
734 configWrite32( target
, kUniNGART_CTRL
, 0 );
735 configWrite32( target
, kUniNGART_CTRL
, kGART_2xRESET
);
736 configWrite32( target
, kUniNGART_CTRL
, 0 );
738 _master
->masterState
&= ~kIOAGPStateEnabled
;
744 IOReturn
AppleMacRiscAGP::saveDeviceState( IOPCIDevice
* device
,
745 IOOptionBits options
= 0 )
748 IOAGPDevice
* agpDev
;
750 IOPCIAddressSpace target
= getBridgeSpace();
752 if( (agpDev
= OSDynamicCast( IOAGPDevice
, device
))) {
753 agpSave
[0] = configRead32( target
, kUniNAGP_BASE
);
754 agpSave
[1] = configRead32( target
, kUniNGART_BASE
);
755 agpSave
[2] = configRead32( target
, targetAGPRegisters
+ kIOPCIConfigAGPCommandOffset
);
756 setAGPEnable( agpDev
, false, 0 );
759 ret
= super::saveDeviceState( device
, options
);
761 if( agpDev
&& (ret
== kIOReturnSuccess
)) {
762 agpDev
->savedConfig
[ kUniNAGP_BASE
/ 4 ] = agpSave
[0];
763 agpDev
->savedConfig
[ kUniNGART_BASE
/ 4 ] = agpSave
[1];
764 agpDev
->savedConfig
[ (targetAGPRegisters
+ kIOPCIConfigAGPCommandOffset
) / 4 ] = agpSave
[2];
770 IOReturn
AppleMacRiscAGP::restoreDeviceState( IOPCIDevice
* device
,
771 IOOptionBits options
= 0 )
774 IOAGPDevice
* agpDev
;
776 IOPCIAddressSpace target
= getBridgeSpace();
778 agpDev
= OSDynamicCast( IOAGPDevice
, device
);
779 if( agpDev
&& device
->savedConfig
) {
780 agpSave
[0] = agpDev
->savedConfig
[ kUniNAGP_BASE
/ 4 ];
781 agpSave
[1] = agpDev
->savedConfig
[ kUniNGART_BASE
/ 4 ];
782 agpSave
[2] = agpDev
->savedConfig
[ (targetAGPRegisters
+ kIOPCIConfigAGPCommandOffset
) / 4 ];
785 ret
= super::restoreDeviceState( device
, options
);
787 if( agpDev
&& (kIOReturnSuccess
== ret
)) {
788 configWrite32( target
, kUniNAGP_BASE
, agpSave
[0] );
789 configWrite32( target
, kUniNGART_BASE
, agpSave
[1] );
791 if( kIOAGPEnable
& agpSave
[2])
792 agpDev
->masterState
|= kIOAGPStateEnablePending
;
794 agpDev
->masterState
&= ~kIOAGPStateEnablePending
;
800 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */