//+++2002-03-03 // Copyright (C) 2001,2002 Mike Rieker, Beverly, MA USA // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; version 2 of the License. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA //---2002-03-03 /************************************************************************/ /* */ /* This module interfaces to WindoesNT SCSI HBA 'miniport' drivers */ /* */ /* This module is just a subroutine library that defines the */ /* ScsiPort... routines, and makes the appropriate calls to OZONE and */ /* the miniport driver. The miniport driver has to be called at its */ /* start address (DriverEntry) with the following two args: */ /* */ /* Argument1 = device name prefix string, eg, "aic78xx" */ /* Argument2 = NULL */ /* */ /* Note that the callee pops the args so the caller must not pop */ /* them, so you will probably need an asm call. */ /* */ /* It only supports PCI-bus based HBA's. */ /* */ /************************************************************************/ #include "ozone.h" #include "oz_dev_pci.h" #include "oz_dev_scsi.h" #include "oz_dev_timer.h" #include "oz_io_scsi.h" #include "oz_knl_devio.h" #include "oz_knl_hw.h" #include "oz_knl_image.h" #include "oz_knl_kmalloc.h" #include "oz_knl_lowipl.h" #include "oz_knl_misc.h" #include "oz_knl_phymem.h" #include "oz_knl_sdata.h" #include "oz_knl_status.h" #include "oz_sys_dateconv.h" #include "oz_sys_xprintf.h" typedef uByte BOOLEAN; typedef Long LONG; typedef uByte *PBOOLEAN; typedef Byte *PCHAR; typedef uByte *PUCHAR; typedef uLong *PULONG; typedef uWord *PUSHORT; typedef void *PVOID; typedef uByte UCHAR; typedef uLong ULONG; typedef uWord USHORT; typedef void VOID; /* An HwDeviceExtension pointer points to memory required by HBA miniport driver */ /* But to get our Devex pointer, we store the Devex struct just before HBA miniport's struct */ #define GETDEVEX(__devex,__HwDeviceExtension) __devex = ((Devex *)(((uByte *)(__HwDeviceExtension)) - (__devex -> HwDeviceExtension - (uByte *)__devex))) /* Since, for the DOIOPP case, we only have a scatter/gather map, we always pretend the virtual */ /* address of the data buffer starts at the beginning of per-process virtual address space */ #define DATAVA (uByte *)(OZ_HW_BASE_PROC_VA) /**********************/ /* PCI specific types */ /**********************/ /* Interface types */ typedef enum _INTERFACE_TYPE { InterfaceTypeUndefined = -1, Internal, Isa, Eisa, MicroChannel, TurboChannel, PCIBus, VMEBus, NuBus, PCMCIABus, CBus, MPIBus, MPSABus, ProcessorInternal, InternalPowerBus, PNPISABus, PNPBus, MaximumInterfaceType } INTERFACE_TYPE, *PINTERFACE_TYPE; typedef enum _BUS_DATA_TYPE { ConfigurationSpaceUndefined = -1, Cmos, EisaConfiguration, Pos, CbusConfiguration, PCIConfiguration, VMEConfiguration, NuBusConfiguration, PCMCIAConfiguration, MPIConfiguration, MPSAConfiguration, PNPISAConfiguration, SgiInternalConfiguration, MaximumBusDataType } BUS_DATA_TYPE, *PBUS_DATA_TYPE; /* Interrupt modes */ typedef enum _KINTERRUPT_MODE { LevelSensitive, Latched } KINTERRUPT_MODE; /* Slot number */ typedef struct _PCI_SLOT_NUMBER { union { struct { ULONG DeviceNumber:5; ULONG FunctionNumber:3; ULONG Reserved:24; } bits; ULONG AsULONG; } u; } PCI_SLOT_NUMBER, *PPCI_SLOT_NUMBER; /* Config space data */ #define PCI_INVALID_VENDOR_ID 0xFFFF #define PCI_TYPE0_ADDRESSES 6 #define PCI_TYPE1_ADDRESSES 2 #define PCI_TYPE2_ADDRESSES 5 typedef struct _PCI_COMMON_CONFIG { USHORT VendorID; USHORT DeviceID; USHORT Command; USHORT Status; UCHAR RevisionID; UCHAR ProgIf; UCHAR SubClass; UCHAR BaseClass; UCHAR CacheLineSize; UCHAR LatencyTimer; UCHAR HeaderType; UCHAR BIST; union { struct _PCI_HEADER_TYPE_0 { ULONG BaseAddresses[PCI_TYPE0_ADDRESSES]; ULONG Reserved1[2]; ULONG ROMBaseAddress; ULONG Reserved2[2]; UCHAR InterruptLine; UCHAR InterruptPin; UCHAR MinimumGrant; UCHAR MaximumLatency; } type0; } u; UCHAR DeviceSpecific[192]; } PCI_COMMON_CONFIG, *PPCI_COMMON_CONFIG; /* Not PCI as such, but the GetDeviceBase routine returns */ /* virtual addresses in this range to indicate I/O ports */ /* So, FFFFC000 means to access I/O port C000 */ #define FAKE_IO_BASE_VA 0xFFFF0000 asm ("FAKE_IO_BASE_VA = 0xFFFF0000"); /***********************/ /* SCSI Specific Types */ /***********************/ #define SCSI_PHYSICAL_ADDRESS uQuad #define SP_UNINITIALIZED_VALUE ((ULONG) ~0) #define SCSI_MAXIMUM_TARGETS 8 #define SCSI_MAXIMUM_LOGICAL_UNITS 8 typedef struct _PORT_CONFIGURATION_INFORMATION PORT_CONFIGURATION_INFORMATION; typedef struct _PORT_CONFIGURATION_INFORMATION *PPORT_CONFIGURATION_INFORMATION; typedef struct _SCSI_REQUEST_BLOCK SCSI_REQUEST_BLOCK; typedef struct _SCSI_REQUEST_BLOCK *PSCSI_REQUEST_BLOCK; typedef BOOLEAN (*PHW_INITIALIZE) (PVOID HwDeviceExtension); typedef BOOLEAN (*PHW_STARTIO) (PVOID HwDeviceExtension, PSCSI_REQUEST_BLOCK Srb); typedef BOOLEAN (*PHW_INTERRUPT) (PVOID HwDeviceExtension); typedef ULONG (*PHW_FIND_ADAPTER) (PVOID HwDeviceExtension, PVOID Context, PVOID BusInformation, PCHAR ArgumentString, PPORT_CONFIGURATION_INFORMATION ConfigInfo, PBOOLEAN Again); typedef BOOLEAN (*PHW_RESET_BUS) (PVOID HwDeviceExtension, ULONG PathId); typedef VOID (*PHW_DMA_STARTED) (void); typedef VOID (*PHW_ADAPTER_STATE) (void); typedef VOID (*PHW_TIMER) (PVOID DeviceExtension); typedef VOID *PHW_ADAPTER_CONTROL; typedef struct _ACCESS_RANGE { SCSI_PHYSICAL_ADDRESS RangeStart; ULONG RangeLength; BOOLEAN RangeInMemory; } ACCESS_RANGE, *PACCESS_RANGE; /* DMA Width and Speed */ typedef enum _DMA_WIDTH { Width8Bits, Width16Bits, Width32Bits, MaximumDmaWidth } DMA_WIDTH, *PDMA_WIDTH; typedef enum _DMA_SPEED { Compatible, TypeA, TypeB, TypeC, TypeF, MaximumDmaSpeed } DMA_SPEED, *PDMA_SPEED; /* This is filled in by the HBA driver's 'DriverEntry' routine */ typedef struct _HW_INITIALIZATION_DATA { ULONG HwInitializationDataSize; INTERFACE_TYPE AdapterInterfaceType; PHW_INITIALIZE _HwInitialize; PHW_STARTIO _HwStartIo; PHW_INTERRUPT _HwInterrupt; PHW_FIND_ADAPTER _HwFindAdapter; PHW_RESET_BUS _HwResetBus; PHW_DMA_STARTED _HwDmaStarted; PHW_ADAPTER_STATE _HwAdapterState; ULONG DeviceExtensionSize; ULONG SpecificLuExtensionSize; ULONG SrbExtensionSize; ULONG NumberOfAccessRanges; PVOID Reserved; BOOLEAN MapBuffers; BOOLEAN NeedPhysicalAddresses; BOOLEAN TaggedQueuing; BOOLEAN AutoRequestSense; BOOLEAN MultipleRequestPerLu; BOOLEAN ReceiveEvent; USHORT VendorIdLength; PVOID VendorId; USHORT ReservedUshort; USHORT DeviceIdLength; PVOID DeviceId; PHW_ADAPTER_CONTROL _HwScsiAdapterControl; } HW_INITIALIZATION_DATA, *PHW_INITIALIZATION_DATA; struct _PORT_CONFIGURATION_INFORMATION { ULONG Length; // 00 ULONG SystemIoBusNumber; // 04 INTERFACE_TYPE AdapterInterfaceType; // 08 ULONG BusInterruptLevel; // 0C ULONG BusInterruptVector; // 10 KINTERRUPT_MODE InterruptMode; // 14 ULONG MaximumTransferLength; // 18 ULONG NumberOfPhysicalBreaks; // 1C ULONG DmaChannel; // 20 ULONG DmaPort; // 24 DMA_WIDTH DmaWidth; // 28 DMA_SPEED DmaSpeed; // 2C ULONG AlignmentMask; // 30 ULONG NumberOfAccessRanges; // 34 ACCESS_RANGE *AccessRanges; // 38 PVOID Reserved; // 3C UCHAR NumberOfBuses; // 40 UCHAR InitiatorBusId[8]; // 41 BOOLEAN ScatterGather; // 49 BOOLEAN Master; // 4A BOOLEAN CachesData; // 4B BOOLEAN AdapterScansDown; // 4C BOOLEAN AtdiskPrimaryClaimed; // 4D BOOLEAN AtdiskSecondaryClaimed; // 4E BOOLEAN Dma32BitAddresses; // 4F BOOLEAN DemandMode; // 50 BOOLEAN MapBuffers; // 51 BOOLEAN NeedPhysicalAddresses; // 52 BOOLEAN TaggedQueuing; // 53 BOOLEAN AutoRequestSense; // 54 BOOLEAN MultipleRequestPerLu; // 55 BOOLEAN ReceiveEvent; // 56 BOOLEAN RealModeInitialized; // 57 BOOLEAN BufferAccessScsiPortControlled; // 58 UCHAR MaximumNumberOfTargets; // 59 UCHAR ReservedUchars[2]; // 5A ULONG SlotNumber; // 5C ULONG BusInterruptLevel2; ULONG BusInterruptVector2; KINTERRUPT_MODE InterruptMode2; ULONG DmaChannel2; ULONG DmaPort2; DMA_WIDTH DmaWidth2; DMA_SPEED DmaSpeed2; ULONG DeviceExtensionSize; ULONG SpecificLuExtensionSize; ULONG SrbExtensionSize; // // Used to determine whether the system and/or the miniport support // 64-bit physical addresses. See SCSI_DMA64_* flags below. // UCHAR Dma64BitAddresses; /* New */ // // Indicates that the miniport can accept a SRB_FUNCTION_RESET_DEVICE // to clear all requests to a particular LUN. // BOOLEAN ResetTargetSupported; /* New */ // // Indicates that the miniport can support more than 8 logical units per // target (maximum LUN number is one less than this field). // UCHAR MaximumNumberOfLogicalUnits; /* New */ // // Supports WMI? // BOOLEAN WmiDataProvider; }; struct _SCSI_REQUEST_BLOCK { USHORT Length; // 00 length of the request block UCHAR Function; // 02 function to be performed UCHAR SrbStatus; // 03 completion status (set by HBA driver) UCHAR ScsiStatus; // 04 scsi status byte UCHAR PathId; // 05 scsi port or bus for the request UCHAR TargetId; // 06 target controller on the bus UCHAR Lun; // 07 logical unit UCHAR QueueTag; // 08 UCHAR QueueAction; // 09 UCHAR CdbLength; // 0A command data block length UCHAR SenseInfoBufferLength; // 0B request-sense buffer length ULONG SrbFlags; // 0C ULONG DataTransferLength; // 10 data transfer length, if underrun, updated with actual transfer length ULONG TimeOutValue; // 14 timeout, in seconds PVOID DataBuffer; // 18 virtual address of data buffer PVOID SenseInfoBuffer; // 1C points to request-sense buffer (physically contiguous) PSCSI_REQUEST_BLOCK NextSrb; // 20 PVOID OriginalRequest; // 24 points to the Iopex PVOID SrbExtension; // 28 points to HBA's extension area (physically contiguous) ULONG QueueSortKey; // 2C UCHAR Cdb[16]; // 30 command data block }; typedef struct _SRB_IO_CONTROL { ULONG HeaderLength; UCHAR Signature[8]; ULONG Timeout; ULONG ControlCode; ULONG ReturnCode; ULONG Length; } SRB_IO_CONTROL, *PSRB_IO_CONTROL; // // SRB Functions // #define SRB_FUNCTION_EXECUTE_SCSI 0x00 #define SRB_FUNCTION_CLAIM_DEVICE 0x01 #define SRB_FUNCTION_IO_CONTROL 0x02 #define SRB_FUNCTION_RECEIVE_EVENT 0x03 #define SRB_FUNCTION_RELEASE_QUEUE 0x04 #define SRB_FUNCTION_ATTACH_DEVICE 0x05 #define SRB_FUNCTION_RELEASE_DEVICE 0x06 #define SRB_FUNCTION_SHUTDOWN 0x07 #define SRB_FUNCTION_FLUSH 0x08 #define SRB_FUNCTION_ABORT_COMMAND 0x10 #define SRB_FUNCTION_RELEASE_RECOVERY 0x11 #define SRB_FUNCTION_RESET_BUS 0x12 #define SRB_FUNCTION_RESET_DEVICE 0x13 #define SRB_FUNCTION_TERMINATE_IO 0x14 #define SRB_FUNCTION_FLUSH_QUEUE 0x15 #define SRB_FUNCTION_REMOVE_DEVICE 0x16 #define SRB_FUNCTION_WMI 0x17 #define SRB_FUNCTION_LOCK_QUEUE 0x18 #define SRB_FUNCTION_UNLOCK_QUEUE 0x19 // // SRB Status // #define SRB_STATUS_PENDING 0x00 #define SRB_STATUS_SUCCESS 0x01 #define SRB_STATUS_ABORTED 0x02 #define SRB_STATUS_ABORT_FAILED 0x03 #define SRB_STATUS_ERROR 0x04 #define SRB_STATUS_BUSY 0x05 #define SRB_STATUS_INVALID_REQUEST 0x06 #define SRB_STATUS_INVALID_PATH_ID 0x07 #define SRB_STATUS_NO_DEVICE 0x08 #define SRB_STATUS_TIMEOUT 0x09 #define SRB_STATUS_SELECTION_TIMEOUT 0x0A #define SRB_STATUS_COMMAND_TIMEOUT 0x0B #define SRB_STATUS_MESSAGE_REJECTED 0x0D #define SRB_STATUS_BUS_RESET 0x0E #define SRB_STATUS_PARITY_ERROR 0x0F #define SRB_STATUS_REQUEST_SENSE_FAILED 0x10 #define SRB_STATUS_NO_HBA 0x11 #define SRB_STATUS_DATA_OVERRUN 0x12 #define SRB_STATUS_UNEXPECTED_BUS_FREE 0x13 #define SRB_STATUS_PHASE_SEQUENCE_FAILURE 0x14 #define SRB_STATUS_BAD_SRB_BLOCK_LENGTH 0x15 #define SRB_STATUS_REQUEST_FLUSHED 0x16 #define SRB_STATUS_INVALID_LUN 0x20 #define SRB_STATUS_INVALID_TARGET_ID 0x21 #define SRB_STATUS_BAD_FUNCTION 0x22 #define SRB_STATUS_ERROR_RECOVERY 0x23 #define SRB_STATUS_NOT_POWERED 0x24 static const Long srbsts2oztable[] = { OZ_PENDING, // SRB_STATUS_PENDING 0x00 OZ_SUCCESS, // SRB_STATUS_SUCCESS 0x01 OZ_ABORTED, // SRB_STATUS_ABORTED 0x02 -OZ_ABORTED, // SRB_STATUS_ABORT_FAILED 0x03 OZ_SUCCESS, // SRB_STATUS_ERROR 0x04 -OZ_DEVICEBUSY, // SRB_STATUS_BUSY 0x05 -OZ_BUGCHECK, // SRB_STATUS_INVALID_REQUEST 0x06 -OZ_SCSISELECTERR, // SRB_STATUS_INVALID_PATH_ID 0x07 -OZ_SCSISELECTERR, // SRB_STATUS_NO_DEVICE 0x08 OZ_TIMEDOUT, // SRB_STATUS_TIMEOUT 0x09 OZ_SCSISELECTERR, // SRB_STATUS_SELECTION_TIMEOUT 0x0A -OZ_TIMEDOUT, // SRB_STATUS_COMMAND_TIMEOUT 0x0B -OZ_BUGCHECK, -OZ_FATALDEVERR, // SRB_STATUS_MESSAGE_REJECTED 0x0D -OZ_FATALCTRLERR, // SRB_STATUS_BUS_RESET 0x0E -OZ_FATALCTRLERR, // SRB_STATUS_PARITY_ERROR 0x0F -OZ_FATALCTRLERR, // SRB_STATUS_REQUEST_SENSE_FAILED 0x10 -OZ_FATALCTRLERR, // SRB_STATUS_NO_HBA 0x11 OZ_BUFFEROVF, // SRB_STATUS_DATA_OVERRUN 0x12 -OZ_FATALDEVERR, // SRB_STATUS_UNEXPECTED_BUS_FREE 0x13 -OZ_FATALDEVERR, // SRB_STATUS_PHASE_SEQUENCE_FAILURE 0x14 -OZ_BUGCHECK, // SRB_STATUS_BAD_SRB_BLOCK_LENGTH 0x15 -OZ_ABORTED, // SRB_STATUS_REQUEST_FLUSHED 0x16 -OZ_BUGCHECK, -OZ_BUGCHECK, -OZ_BUGCHECK, -OZ_BUGCHECK, -OZ_BUGCHECK, -OZ_BUGCHECK, -OZ_BUGCHECK, -OZ_BUGCHECK, -OZ_BUGCHECK, -OZ_SCSISELECTERR, // SRB_STATUS_INVALID_LUN 0x20 -OZ_SCSISELECTERR, // SRB_STATUS_INVALID_TARGET_ID 0x21 -OZ_BUGCHECK, // SRB_STATUS_BAD_FUNCTION 0x22 -OZ_FATALDEVERR // SRB_STATUS_ERROR_RECOVERY 0x23 }; // // This value is used by the port driver to indicate that a non-scsi-related // error occured. Miniports must never return this status. // #define SRB_STATUS_INTERNAL_ERROR 0x30 // // Srb status values 0x38 through 0x3f are reserved for internal port driver // use. // // // SRB Status Masks // #define SRB_STATUS_QUEUE_FROZEN 0x40 #define SRB_STATUS_AUTOSENSE_VALID 0x80 #define SRB_STATUS(Status) (Status & ~(SRB_STATUS_AUTOSENSE_VALID | SRB_STATUS_QUEUE_FROZEN)) // // SRB Flag Bits // #define SRB_FLAGS_QUEUE_ACTION_ENABLE 0x00000002 #define SRB_FLAGS_DISABLE_DISCONNECT 0x00000004 #define SRB_FLAGS_DISABLE_SYNCH_TRANSFER 0x00000008 #define SRB_FLAGS_BYPASS_FROZEN_QUEUE 0x00000010 #define SRB_FLAGS_DISABLE_AUTOSENSE 0x00000020 #define SRB_FLAGS_DATA_IN 0x00000040 #define SRB_FLAGS_DATA_OUT 0x00000080 #define SRB_FLAGS_NO_DATA_TRANSFER 0x00000000 #define SRB_FLAGS_UNSPECIFIED_DIRECTION (SRB_FLAGS_DATA_IN | SRB_FLAGS_DATA_OUT) #define SRB_FLAGS_NO_QUEUE_FREEZE 0x00000100 #define SRB_FLAGS_ADAPTER_CACHE_ENABLE 0x00000200 #define SRB_FLAGS_IS_ACTIVE 0x00010000 #define SRB_FLAGS_ALLOCATED_FROM_ZONE 0x00020000 #define SRB_FLAGS_SGLIST_FROM_POOL 0x00040000 #define SRB_FLAGS_BYPASS_LOCKED_QUEUE 0x00080000 #define SRB_FLAGS_NO_KEEP_AWAKE 0x00100000 #define SRB_FLAGS_PORT_DRIVER_RESERVED 0x0F000000 #define SRB_FLAGS_CLASS_DRIVER_RESERVED 0xF0000000 // // Queue Action // #define SRB_SIMPLE_TAG_REQUEST 0x20 #define SRB_HEAD_OF_QUEUE_TAG_REQUEST 0x21 #define SRB_ORDERED_QUEUE_TAG_REQUEST 0x22 #define SRB_WMI_FLAGS_ADAPTER_REQUEST 0x01 // // Port driver error codes - passed to ScsiPortLogError // #define SP_BUS_PARITY_ERROR 0x0001 #define SP_UNEXPECTED_DISCONNECT 0x0002 #define SP_INVALID_RESELECTION 0x0003 #define SP_BUS_TIME_OUT 0x0004 #define SP_PROTOCOL_ERROR 0x0005 #define SP_INTERNAL_ADAPTER_ERROR 0x0006 #define SP_REQUEST_TIMEOUT 0x0007 #define SP_IRQ_NOT_RESPONDING 0x0008 #define SP_BAD_FW_WARNING 0x0009 #define SP_BAD_FW_ERROR 0x000a #define SP_LOST_WMI_MINIPORT_REQUEST 0x000b // // Return values for SCSI_HW_FIND_ADAPTER. // #define SP_RETURN_NOT_FOUND 0 #define SP_RETURN_FOUND 1 #define SP_RETURN_ERROR 2 #define SP_RETURN_BAD_CONFIG 3 // // Notification Event Types // typedef enum _SCSI_NOTIFICATION_TYPE { RequestComplete, NextRequest, NextLuRequest, ResetDetected, CallDisableInterrupts, CallEnableInterrupts, RequestTimerCall, BusChangeDetected, /* New */ WMIEvent, WMIReregister } SCSI_NOTIFICATION_TYPE, *PSCSI_NOTIFICATION_TYPE; /************************************************************************/ /* */ /* OZONE device and request structs */ /* */ /************************************************************************/ typedef struct Chnex Chnex; typedef struct Devex Devex; typedef struct Iopex Iopex; typedef struct Lu Lu; typedef struct Mbase Mbase; typedef struct Uce Uce; /* One of these per channel assigned to the controller */ struct Chnex { Lu *lu; // logical unit open on the channel, NULL if closed OZ_Lockmode lockmode; // lockmode the unit was opened with }; /* One of these per scsi controller */ /* Instead of being in the OZ_Devunit struct, these are just before the HwDeviceExtension */ /* The OZ_Devunit's devex is just a pointer to this struct */ /* This is so we can have a devex struct during HwFindAdapter without having to create the devunit */ struct Devex { OZ_Devunit *devunit; // device independent data pointer const char *devname; // device name string pointer uLong scsi_id; // controller's scsi-id OZ_Smplock *smplock_dv; // irq level smplock int hba_busy; // 0: controller will accept a request // 1: controller busy, and won't accept requests Iopex *iopex_qh; // list of requests not passed to HBA driver Iopex **iopex_qt; // tail of iopex_qh Iopex *abort_qh; // in-progress iopex's being aborted Iopex **abort_qt; Lu *lus; // list of logical units defined OZ_Hw486_irq_many irq_many; // shared irq link struct PHW_INTERRUPT _HwInterrupt; // HBA's interrupt routine, NULL if disabled int int_pending; // set if an interrupt happened while disabled ULONG SpecificLuExtensionSize; // sizeof Lu->hbaextension ULONG SrbExtensionSize; // sizeof *(Iopex->Srb->SrvExtension) PHW_STARTIO _HwStartIo; // start I/O routine entrypoint OZ_Lowipl *lowipl; OZ_Timer *timeout_timer; // request timeout timer OZ_Datebin timeout_when; // when the timer will expire Mbase *mbases; // list of mapped memory bases Uce *uces; // list of uncached extensions PORT_CONFIGURATION_INFORMATION ConfigInfo; // HBA's configuration info uByte HwDeviceExtension[1]; // HBA driver's device data (must be last) }; /* One of these per I/O request */ #define IOPEX_BUSY_RUNNING 0x1 // queued for execution but hasn't been completed yet #define IOPEX_BUSY_ABORTING 0x2 // an abort has been queued but hasn't completed yet struct Iopex { Iopex *next; // next on devex -> iopex_qh or lu -> inprog_q Iopex **prev; Iopex *abnext; // next on devex -> abort_qh Devex *devex; // corresponding controller Lu *lu; // corresponding logical unit OZ_Ioop *ioop; // corresponding ioop OZ_Procmode procmode; // procmode of requestor OZ_IO_scsi_doiopp doiopp; // i/o request parameter block int busy; // request is busy somehow OZ_Datebin timeout_when; // when it will time out (or -1 if never) OZ_Mempage bufremapped; // set to number of pages iff mappedbuffer is a re-map void *mappedbuffer; // virt address of data (if ConfigInfo -> MapBuffers set) SCSI_REQUEST_BLOCK Srb; // HBA request param block SCSI_REQUEST_BLOCK abSrb; // abort HBA request param block }; /* One of these per 'logical unit' that we know about */ /* Logical unit is unique path-id/target-id/lun */ struct Lu { Lu *next; // next in devex->lus list UCHAR PathId; // logical unit's path id UCHAR TargetId; // logical unit's target id UCHAR Lun; // lugical unit's lun UCHAR lu_busy; // 0: controller will accept a request on this lu // 1: controller won't accept any requests on this lu Iopex *inprog_q; // queue of requests passed to HBA that it hasn't completed yet Long refcount; // channel refcounts Long refc_read; Long refc_write; Long deny_read; Long deny_write; UCHAR hbaextension[1]; // hba's per-lu extension area (size SpecificLuExtensionSize) }; /* Mapped memory bases (mapped by ScsiPortGetDeviceBase) */ struct Mbase { Mbase *next; OZ_Mempage npages; OZ_Mempage svpage; }; /* Uncached extensions */ struct Uce { Uce *next; SCSI_PHYSICAL_ADDRESS PhysicalAddress; PUCHAR VirtualAddress; ULONG Length; }; /* Function table */ static int scsiport_shutdown (OZ_Devunit *devunit, void *devexv); static uLong scsiport_assign (OZ_Devunit *devunit, void *devexv, OZ_Iochan *iochan, void *chnexv, OZ_Procmode procmode); static int scsiport_deassign (OZ_Devunit *devunit, void *devexv, OZ_Iochan *iochan, void *chnexv); static void scsiport_abort (OZ_Devunit *devunit, void *devexv, OZ_Iochan *iochan, void *chnexv, OZ_Ioop *ioop, void *iopexv, OZ_Procmode procmode); static uLong scsiport_start (OZ_Devunit *devunit, void *devexv, OZ_Iochan *iochan, void *chnexv, OZ_Procmode procmode, OZ_Ioop *ioop, void *iopexv, uLong funcode, uLong as, void *ap); static const OZ_Devfunc scsiport_functable = { sizeof (Devex *), sizeof (Chnex), sizeof (Iopex), 0, scsiport_shutdown, NULL, NULL, scsiport_assign, scsiport_deassign, scsiport_abort, scsiport_start, NULL }; /* Internal routines */ static uLong qiopp (Chnex *chnex, Iopex *iopex, void *databuff); static int got_interrupt (void *devexv, OZ_Mchargs *mchargs); static Lu *get_lu (Devex *devex, UCHAR PathId, UCHAR TargetId, UCHAR Lun); static void dequeuereq (void *devexv, OZ_Lowipl *lowipl); static void startreq (Iopex *iopex); static void requesttimedout (void *devexv, OZ_Timer *timer); static void abortreq (Iopex *iopex); static uLong srbsts2oz (Iopex *iopex); static void finishup (void *iopexv, int finok, uLong *status_r); /************************************************************************/ /* */ /* Call this routine to perform initialization */ /* */ /* Input: */ /* */ /* DriverEntry = the .sys file's entrypoint (start address) */ /* devnameprefix = device name prefix string (eg, "aic78xx") */ /* image = NULL : this is statically linked driver */ /* else : image pointer */ /* smplevel = softint */ /* */ /* Output: */ /* */ /* scsiport_init = number of devices created */ /* */ /************************************************************************/ int scsiport_init (void *DriverEntry, const char *devnameprefix, OZ_Image *image) { int ndevs; oz_knl_printk ("scsiport_init (%s)\n", devnameprefix); oz_knl_nppmlvalid (); if (image != NULL) oz_knl_image_lockinmem (image, OZ_PROCMODE_KNL); ndevs = 0; // (*DriverEntry) (devnameprefix, &ndevs); asm ("pushl %2\n" " pushl %1\n" " movl %0,%%eax\n" " call *%%eax\n" : : "m" (DriverEntry), "g" (devnameprefix), "g" (&ndevs) : "eax", "ecx", "edx", "cc", "memory"); if ((image != NULL) && (ndevs != 0)) oz_knl_image_increfc (image, 1); oz_knl_nppmlvalid (); oz_knl_printk ("scsiport_init (%s) ndevs %d\n", devnameprefix, ndevs); return (ndevs); } /************************************************************************/ /* */ /* OZONE is being shut down, shut the controller off */ /* */ /************************************************************************/ static int scsiport_shutdown (OZ_Devunit *devunit, void *devexv) { } /************************************************************************/ /* */ /* A new channel is being assigned to the device. Clear out the */ /* extension struct. */ /* */ /************************************************************************/ static uLong scsiport_assign (OZ_Devunit *devunit, void *devexv, OZ_Iochan *iochan, void *chnexv, OZ_Procmode procmode) { memset (chnexv, 0, sizeof (Chnex)); return (OZ_SUCCESS); } /************************************************************************/ /* */ /* A channel is being deassigned. Close it out. */ /* */ /************************************************************************/ static int scsiport_deassign (OZ_Devunit *devunit, void *devexv, OZ_Iochan *iochan, void *chnexv) { Chnex *chnex; Devex *devex; Lu *lu; OZ_Lockmode lockmode; uLong dv; chnex = chnexv; devex = *(Devex **)devexv; dv = oz_hw_smplock_wait (devex -> smplock_dv); lu = chnex -> lu; if (lu != NULL) { lockmode = chnex -> lockmode; if (!OZ_LOCK_ALLOW_TEST (lockmode, OZ_LOCK_ALLOWS_OTHERS_READ)) lu -> deny_read --; if (!OZ_LOCK_ALLOW_TEST (lockmode, OZ_LOCK_ALLOWS_OTHERS_WRITE)) lu -> deny_write --; if (OZ_LOCK_ALLOW_TEST (lockmode, OZ_LOCK_ALLOWS_SELF_READ)) lu -> refc_read --; if (OZ_LOCK_ALLOW_TEST (lockmode, OZ_LOCK_ALLOWS_SELF_WRITE)) lu -> refc_write --; lu -> refcount --; chnex -> lu = NULL; } oz_hw_smplock_clr (devex -> smplock_dv, dv); return (0); } /************************************************************************/ /* */ /* Abort an I/O */ /* */ /************************************************************************/ static void scsiport_abort (OZ_Devunit *devunit, void *devexv, OZ_Iochan *iochan, void *chnexv, OZ_Ioop *ioop, void *iopexv, OZ_Procmode procmode) { Chnex *chnex; Devex *devex; Iopex *iopex, **liopex, *xiopex; Lu *lu; uLong dv; chnex = chnexv; devex = *(Devex **)devexv; xiopex = NULL; dv = oz_hw_smplock_wait (devex -> smplock_dv); /* See what logical unit, if any, is open on the channel */ lu = chnex -> lu; if (lu != NULL) { /* Tell HBA driver to abort any matching requests in progress */ for (iopex = lu -> inprog_q; iopex != NULL; iopex = iopex -> next) { if (oz_knl_ioabortok (iopex -> ioop, iochan, procmode, ioop)) abortreq (iopex); } /* Abort stuff that has yet to be passed to HBA driver */ for (liopex = &(devex -> iopex_qh); (iopex = *liopex) != NULL;) { if (oz_knl_ioabortok (iopex -> ioop, iochan, procmode, ioop)) { // see if ok to abort it *(iopex -> prev) = iopex -> next; // remove from devex->iopex_q if (iopex -> next != NULL) iopex -> next -> prev = liopex; iopex -> next = xiopex; // put on xiopex list xiopex = iopex; } else { liopex = &(iopex -> next); } } devex -> iopex_qt = liopex; } oz_hw_smplock_clr (devex -> smplock_dv, dv); while ((iopex = xiopex) != NULL) { xiopex = iopex -> next; if (iopex -> Srb.SrbExtension != NULL) OZ_KNL_NPPFREE (iopex -> Srb.SrbExtension); oz_knl_iodone (iopex -> ioop, OZ_ABORTED, NULL, finishup, iopex); } } /************************************************************************/ /* */ /* Start processing an I/O request */ /* */ /************************************************************************/ static uLong scsiport_start (OZ_Devunit *devunit, void *devexv, OZ_Iochan *iochan, void *chnexv, OZ_Procmode procmode, OZ_Ioop *ioop, void *iopexv, uLong funcode, uLong as, void *ap) { Chnex *chnex; Devex *devex; Iopex *iopex; chnex = chnexv; devex = *(Devex **)devexv; iopex = iopexv; iopex -> devex = devex; iopex -> ioop = ioop; iopex -> procmode = procmode; switch (funcode) { /* Declare what scsi_id and what lockmode is to be associated with the channel */ case OZ_IO_SCSI_OPEN: { Lu *lu, *newlu; OZ_IO_scsi_open scsi_open; OZ_Lockmode iochlkm, lockmode; uLong dv, scsi_id, sts; /* Retrieve and validate parameters */ movc4 (as, ap, sizeof scsi_open, &scsi_open); scsi_id = scsi_open.scsi_id; lockmode = scsi_open.lockmode; if (scsi_id >= devex -> ConfigInfo.MaximumNumberOfTargets) return (OZ_BADSCSIID); if (scsi_id == devex -> ConfigInfo.InitiatorBusId[0]) return (OZ_BADSCSIID); iochlkm = oz_knl_iochan_getlockmode (iochan); if (OZ_LOCK_ALLOW_TEST (lockmode, OZ_LOCK_ALLOWS_SELF_WRITE) && !OZ_LOCK_ALLOW_TEST (iochlkm, OZ_LOCK_ALLOWS_SELF_WRITE)) { return (OZ_NOWRITEACCESS); } if (OZ_LOCK_ALLOW_TEST (lockmode, OZ_LOCK_ALLOWS_SELF_READ) && !OZ_LOCK_ALLOW_TEST (iochlkm, OZ_LOCK_ALLOWS_SELF_READ)) { return (OZ_NOREADACCESS); } /* Mark the channel open and lock access to the logical unit */ newlu = OZ_KNL_NPPMALLOC (devex -> SpecificLuExtensionSize + sizeof *lu); // in case a new lu struct is needed dv = oz_hw_smplock_wait (devex -> smplock_dv); // lock device state if (chnex -> lu != NULL) sts = OZ_FILEALREADYOPEN; // maybe the channel is already open else { lu = get_lu (devex, 0, scsi_id, 0); // see if there already is matching lu struct if (lu == NULL) { lu = newlu; // if not, set up the new one memset (lu, 0, devex -> SpecificLuExtensionSize + sizeof *lu); lu -> next = devex -> lus; lu -> PathId = 0; lu -> TargetId = scsi_id; lu -> Lun = 0; devex -> lus = lu; } if ((!OZ_LOCK_ALLOW_TEST (lockmode, OZ_LOCK_ALLOWS_OTHERS_READ) && (lu -> refc_read != 0)) || (!OZ_LOCK_ALLOW_TEST (lockmode, OZ_LOCK_ALLOWS_OTHERS_WRITE) && (lu -> refc_write != 0)) || (OZ_LOCK_ALLOW_TEST (lockmode, OZ_LOCK_ALLOWS_SELF_READ) && (lu -> deny_read != 0)) || (OZ_LOCK_ALLOW_TEST (lockmode, OZ_LOCK_ALLOWS_SELF_WRITE) && (lu -> deny_write != 0))) { sts = OZ_ACCONFLICT; } else { if (!OZ_LOCK_ALLOW_TEST (lockmode, OZ_LOCK_ALLOWS_OTHERS_READ)) lu -> deny_read ++; if (!OZ_LOCK_ALLOW_TEST (lockmode, OZ_LOCK_ALLOWS_OTHERS_WRITE)) lu -> deny_write ++; if (OZ_LOCK_ALLOW_TEST (lockmode, OZ_LOCK_ALLOWS_SELF_READ)) lu -> refc_read ++; if (OZ_LOCK_ALLOW_TEST (lockmode, OZ_LOCK_ALLOWS_SELF_WRITE)) lu -> refc_write ++; lu -> refcount ++; chnex -> lu = lu; chnex -> lockmode = lockmode; sts = OZ_SUCCESS; } } oz_hw_smplock_clr (devex -> smplock_dv, dv); if (newlu != lu) OZ_KNL_NPPFREE (newlu); return (sts); } /* Queue an I/O request to the scsi_id open on the channel */ case OZ_IO_SCSI_DOIO: { OZ_IO_scsi_doio scsi_doio; uLong sts; movc4 (as, ap, sizeof scsi_doio, &scsi_doio); // get param block sts = oz_dev_scsi_cvtdoio2pp (ioop, procmode, &scsi_doio, &(iopex -> doiopp)); // convert to DOIOPP request if (sts == OZ_SUCCESS) sts = qiopp (chnex, iopex, scsi_doio.databuff); // then process it that way return (sts); } /* - This one already has the buffers locked in memory */ case OZ_IO_SCSI_DOIOPP: { if (procmode != OZ_PROCMODE_KNL) return (OZ_KERNELONLY); // can only be called from kernel mode movc4 (as, ap, sizeof iopex -> doiopp, &(iopex -> doiopp)); // ok, get the parameter block return (qiopp (chnex, iopex, NULL)); // go process it } /* Get info, part 1 */ case OZ_IO_SCSI_GETINFO1: { Lu *lu; OZ_IO_scsi_getinfo1 scsi_getinfo1; uLong sts; sts = oz_knl_ioop_lockw (ioop, as, ap, NULL, NULL, NULL); if (sts == OZ_SUCCESS) { memset (&scsi_getinfo1, 0, sizeof scsi_getinfo1); scsi_getinfo1.max_scsi_id = devex -> ConfigInfo.MaximumNumberOfTargets; /* max scsi id allowed on this controller+1 */ scsi_getinfo1.ctrl_scsi_id = devex -> ConfigInfo.InitiatorBusId[0]; /* what the controller's scsi id is */ scsi_getinfo1.open_scsi_id = -1; /* assume no scsi id open on channel */ lu = chnex -> lu; if (lu != NULL) { scsi_getinfo1.open_scsi_id = chnex -> lu -> TargetId; /* ok, get the open scsi id */ } movc4 (sizeof scsi_getinfo1, &scsi_getinfo1, as, ap); } return (sts); } } return (OZ_BADIOFUNC); } /************************************************************************/ /* */ /* Queue an I/O request to the controller if it is ready for one. */ /* Otherwise, save it on an internal queue for now. */ /* */ /* Input: */ /* */ /* chnex = channel the request was queued from */ /* iopex = I/O request */ /* databuff = vaddr of data buffer or NULL if not known */ /* smplevel = softint */ /* */ /* Output: */ /* */ /* qiopp = OZ_STARTED : request queued (or started) */ /* else : error status */ /* */ /************************************************************************/ static uLong qiopp (Chnex *chnex, Iopex *iopex, void *databuff) { Devex *devex; Lu *lu; OZ_IO_scsi_doiopp *doiopp; OZ_Mempage i, npages, phypage, sysvpage; SCSI_REQUEST_BLOCK *Srb; uLong dv, sts; void *sysvaddr; devex = iopex -> devex; doiopp = &(iopex -> doiopp); lu = chnex -> lu; iopex -> lu = lu; /* Validate parameters */ if (lu == NULL) return (OZ_FILENOTOPEN); if (!OZ_LOCK_ALLOW_TEST (chnex -> lockmode, OZ_LOCK_ALLOWS_SELF_WRITE)) return (OZ_NOWRITEACCESS); if ((doiopp -> cmdlen == 0) || (doiopp -> cmdlen > sizeof Srb -> Cdb)) return (OZ_BADBUFFERSIZE); /* Lamer controllers need the data buffer mapped to system global virtual addresses */ iopex -> bufremapped = 0; iopex -> mappedbuffer = DATAVA; if ((iopex -> doiopp.datasize != 0) && devex -> ConfigInfo.MapBuffers) { if (oz_s_inloader) { iopex -> mappedbuffer = OZ_HW_VPAGETOVADDR (iopex -> doiopp.dataphypages[0]); // loader has identity mapping (OZ_Pointer)(iopex -> mappedbuffer) += iopex -> doiopp.databyteoffs; // calc starting address of buffer in first page oz_hw_pte_readsys (iopex -> doiopp.dataphypages[0], NULL, &phypage, NULL, NULL); // check it if (phypage != iopex -> doiopp.dataphypages[0]) oz_crash ("scsiport qiopp: %X mapped to %X", iopex -> doiopp.dataphypages[0], phypage); } else { iopex -> mappedbuffer = databuff; if ((databuff == NULL) || !OZ_HW_ISSYSADDR (databuff)) { npages = ((iopex -> doiopp.databyteoffs + iopex -> doiopp.datasize - 1) >> OZ_HW_L2PAGESIZE) + 1; sts = oz_knl_spte_alloc (npages, &(iopex -> mappedbuffer), &sysvpage, NULL); if (sts != OZ_SUCCESS) return (sts); iopex -> bufremapped = npages; // save number of pages being mapped (OZ_Pointer)(iopex -> mappedbuffer) += iopex -> doiopp.databyteoffs; // calc starting address of buffer in first page for (i = 0; i < npages; i ++) oz_hw_pte_writeall (sysvpage ++, // virt page mapped by pte OZ_SECTION_PAGESTATE_VALID_W, // tell pagefaulter it's already valid iopex -> doiopp.dataphypages[i], // physical page to be mapped by pte OZ_HW_PAGEPROT_KW, // it is writable by kernel routines OZ_HW_PAGEPROT_NA); // any pagefaults get denied access } } } if (iopex -> doiopp.datasize != 0) { ///oz_knl_printk ("scsiport qiopp*: MapBuffers %d, datasize %X, phypages[0] %X, mappedbuffer %X, bufremapped %d\n", /// devex -> ConfigInfo.MapBuffers, iopex -> doiopp.datasize, iopex -> doiopp.dataphypages[0], iopex -> mappedbuffer, iopex -> bufremapped); } /* Fill in scsi request block (it's part of the iopex) */ Srb = &(iopex -> Srb); memset (Srb, 0, sizeof *Srb); Srb -> Length = sizeof *Srb; Srb -> Function = SRB_FUNCTION_EXECUTE_SCSI; Srb -> SrbStatus = SRB_STATUS_PENDING; Srb -> PathId = lu -> PathId; Srb -> TargetId = lu -> TargetId; Srb -> Lun = lu -> Lun; Srb -> CdbLength = doiopp -> cmdlen; Srb -> DataTransferLength = doiopp -> datasize; Srb -> TimeOutValue = doiopp -> timeout; Srb -> DataBuffer = iopex -> mappedbuffer; Srb -> OriginalRequest = iopex; Srb -> NextSrb = Srb; if (devex -> SrbExtensionSize != 0) Srb -> SrbExtension = OZ_KNL_PCMALLOC (devex -> SrbExtensionSize); Srb -> SrbFlags = SRB_FLAGS_DISABLE_AUTOSENSE; if (doiopp -> datasize == 0) Srb -> SrbFlags |= SRB_FLAGS_NO_DATA_TRANSFER; else if (doiopp -> optflags & OZ_IO_SCSI_OPTFLAG_WRITE) Srb -> SrbFlags |= SRB_FLAGS_DATA_OUT; else Srb -> SrbFlags |= SRB_FLAGS_DATA_IN; memcpy (Srb -> Cdb, doiopp -> cmdbuf, doiopp -> cmdlen); /* Queue request to controller */ iopex -> busy = 0; // it hasn't been passed to HwStartIo yet iopex -> timeout_when = (OZ_Datebin)-1LL; // we haven't set up timeout timer yet dv = oz_hw_smplock_wait (devex -> smplock_dv); // lock the state ///oz_knl_printk ("scsiport qiopp*: devex %p -> hba_busy %d, lu %p -> lu_busy %d\n", devex, devex -> hba_busy, lu, lu -> lu_busy); if (!(devex -> hba_busy) && !(lu -> lu_busy)) { // see if controller and lu able to accept request devex -> hba_busy = 1; // if so, mark them both busy lu -> lu_busy = 1; startreq (iopex); // ... and start processing the request } else { iopex -> next = NULL; // something busy, put this request on end of queue iopex -> prev = devex -> iopex_qt; *(devex -> iopex_qt) = iopex; devex -> iopex_qt = &(iopex -> next); } oz_hw_smplock_clr (devex -> smplock_dv, dv); // unlock state /* Tell caller it will complete asynchronously */ return (OZ_STARTED); } /************************************************************************/ /* */ /* Got an interrupt from the controller */ /* */ /************************************************************************/ static int got_interrupt (void *devexv, OZ_Mchargs *mchargs) { Devex *devex; devex = devexv; ///oz_knl_printk ("scsiport got_interrput*: devex %p -> _HwInterrupt %p\n", devex, devex -> _HwInterrupt); if (devex -> _HwInterrupt == NULL) devex -> int_pending = 1; else { // (*(devex -> HwInterrupt)) (devex -> HwDeviceExtension); asm ("pushl %1\n" " movl %0,%%eax\n" " call *%%eax\n" : : "m" (devex -> _HwInterrupt), "g" (devex -> HwDeviceExtension) : "eax", "ecx", "edx", "cc", "memory"); } return (0); } /************************************************************************/ /* */ /* Completes ALL of the active requests for the specified logical */ /* unit. It can be called to complete requests after a bus reset, */ /* a device reset, or an abort, rather than calling */ /* ScsiPortNotification for each request. */ /* */ /* Input: */ /* */ /* HwDeviceExtension = HBA's per-device data */ /* PathId,TargetId,Lun = logical unit */ /* SrbStatus = status value for each srb's SrbStatus field */ /* */ /* Output: */ /* */ /* all in-progress requests for the lun are completed */ /* */ /************************************************************************/ asm (".globl ScsiPortCompleteRequest\n" "ScsiPortCompleteRequest:\n" " pushl %ebp\n" " pushl $0\n" " leal 4(%esp),%ebp\n" " pushl 24(%ebp)\n" " pushl 20(%ebp)\n" " pushl 16(%ebp)\n" " pushl 12(%ebp)\n" " pushl 8(%ebp)\n" " call _ScsiPortCompleteRequest\n" " leave\n" " ret $20"); static VOID _ScsiPortCompleteRequest (PVOID HwDeviceExtension, UCHAR PathId, UCHAR TargetId, UCHAR Lun, UCHAR SrbStatus) { Devex *devex; Iopex *iopex; Lu *lu; uLong dv; ///oz_knl_printk ("scsiport ScsiPortCompleteRequest*:\n"); GETDEVEX (devex, HwDeviceExtension); // point to my per-device data dv = oz_hw_smplock_wait (devex -> smplock_dv); // lock the queues lu = get_lu (devex, PathId, TargetId, Lun); // point to my per-unit data while ((iopex = lu -> inprog_q) != NULL) { // see if any request in prog on the unit ///oz_knl_printk ("scsiport ScsiPortCompleteRequest*: clearing %p -> BUSY_RUNNING\n", iopex); if (!(iopex -> busy & IOPEX_BUSY_RUNNING)) oz_crash ("scsiport_486 ScsiPortCompleteRequest: request not running"); iopex -> busy &= ~IOPEX_BUSY_RUNNING; // if so, it's no longer running lu -> inprog_q = iopex -> next; // unlink it iopex -> Srb.SrbStatus = SrbStatus; // set the completion status if (iopex -> busy == 0) { // make sure an abort isn't in progress oz_knl_iodonehi (iopex -> ioop, srbsts2oz (iopex), NULL, finishup, iopex); // post it } } // repeat for all on the unit oz_hw_smplock_clr (devex -> smplock_dv, dv); // unlock the queue } // ULONG ScsiPortConvertPhysicalAddressToUlong (SCSI_PHYSICAL_ADDRESS Address) asm (".globl ScsiPortConvertPhysicalAddressToUlong\n" "ScsiPortConvertPhysicalAddressToUlong:\n" " movl 8(%esp),%edx\n" " movl 4(%esp),%eax\n" " testl %edx,%edx\n" " jne phyaddrtoulongbad_jmp\n" " ret $8\n" "phyaddrtoulongbad_jmp:\n" " pushl %ebx\n" " pushl $0\n" " leal 4(%esp),%ebp\n" " pushl %eax\n" " pushl %edx\n" " pushl $phyaddrtoulongbad_msg\n" " call oz_crash\n" "phyaddrtoulongbad_msg: .string \"ScsiPortConvertPhysicalAddressToUlong: bad phyaddr %8.8X:%8.8X\""); // SCSI_PHYSICAL_ADDRESS ScsiPortConvertUlongToPhysicalAddress (ULONG UlongAddress) asm (".globl ScsiPortConvertUlongToPhysicalAddress\n" "ScsiPortConvertUlongToPhysicalAddress:\n" " movl 4(%esp),%eax\n" " xorl %edx,%edx\n" " ret $4"); /************************************************************************/ /* */ /* Frees a block of I/O addresses or memory space previously mapped */ /* into the system address space */ /* */ /* Input: */ /* */ /* HwDeviceExtension = points to HBA device extension struct */ /* MappedAddress = address that device registers were mapped to */ /* */ /* Output: */ /* */ /* MappedAddress = no longer points to valid memory */ /* */ /************************************************************************/ asm (".globl ScsiPortFreeDeviceBase\n" "ScsiPortFreeDeviceBase:\n" " pushl %ebp\n" " pushl $0\n" " leal 4(%esp),%ebp\n" " pushl 12(%ebp)\n" " pushl 8(%ebp)\n" " call _ScsiPortFreeDeviceBase\n" " leave\n" " ret $8"); static VOID _ScsiPortFreeDeviceBase (PVOID HwDeviceExtension, PVOID MappedAddress) { Devex *devex; Mbase **lmbase, *mbase; OZ_Mempage vpage; ///oz_knl_printk ("scsiport ScsiPortFreeDeviceBase*:\n"); if ((OZ_Pointer)MappedAddress < FAKE_IO_BASE_VA) { GETDEVEX (devex, HwDeviceExtension); /* See what section is mapped there and how big it is */ vpage = OZ_HW_VADDRTOVPAGE (MappedAddress); for (lmbase = &(devex -> mbases); (mbase = *lmbase) != NULL; lmbase = &(mbase -> next)) { if ((vpage >= mbase -> svpage) && (vpage < mbase -> svpage + mbase -> npages)) break; } if (mbase == NULL) oz_crash ("scsiport_486: ScsiPortFreeDeviceBase: no section at vpage %x", vpage); *lmbase = mbase -> next; /* Mark the pages invalid and free the section block so the virtual addresses are available for re-use */ oz_knl_spte_free (mbase -> npages, mbase -> svpage); OZ_KNL_NPPFREE (mbase); } } /************************************************************************/ /* */ /* Gets bus-type-specific information */ /* The only type of info we support is from PCI Configuration space */ /* */ /* Input: */ /* */ /* DeviceExtension = as passed to HwScsiFindAdapter */ /* BusDataType = type of bus-specific config data to return */ /* SystemIoBusNumber = as passed to HwScsiFindAdapter in */ /* ConfigInfo -> SystemIoBusNumber */ /* SlotNumber = slot number location of the device */ /* if BusDataType == PCIConfiguration, */ /* this param is PCI_SLOT_NUMBER type */ /* */ /* Output: */ /* */ /* ScsiPortGetBusData = 0 : the PCI bus does not exist */ /* 2 : there is no device in the slot */ /* (Buffer -> VendorId == PCI_INVALID_VENDOR_ID) /* else : # of bytes of Buffer filled in */ /* */ /* Note: */ /* */ /* This can be called only from HBA's HwFindAdapter routine */ /* (although this implementation can be called from any smplevel) */ /* */ /************************************************************************/ asm (".globl ScsiPortGetBusData\n" "ScsiPortGetBusData:\n" " pushl %ebp\n" " pushl $0\n" " leal 4(%esp),%ebp\n" " pushl 28(%ebp)\n" " pushl 24(%ebp)\n" " pushl 20(%ebp)\n" " pushl 16(%ebp)\n" " pushl 12(%ebp)\n" " pushl 8(%ebp)\n" " call _ScsiPortGetBusData\n" " leave\n" " ret $24"); static ULONG _ScsiPortGetBusData (PVOID DeviceExtension, ULONG BusDataType, // must be PCIConfiguration ULONG SystemIoBusNumber, // from ConfigInfo -> SystemIoBusNumber ULONG SlotNumber, // from ConfigInfo -> SlotNumber PVOID Buffer, ULONG Length) { OZ_Dev_pci_conf_p pciconfp; PCI_SLOT_NUMBER pcislotnumber; uByte *buf; uLong i; uWord vid; ///oz_knl_printk ("scsiport ScsiPortGetBusData*: (%p, %u, %u, %X, %p, %u)\n", /// DeviceExtension, BusDataType, SystemIoBusNumber, SlotNumber, Buffer, Length); /* We only do PCI bus */ if (BusDataType != PCIConfiguration) { oz_knl_printk ("scsiport ScsiPortGetBusData: not PCI bus\n"); return (0); } /* Make sure PCIBIOS is present, if not, return 0 */ if (!oz_dev_pci_present ()) { oz_knl_printk ("scsiport ScsiPortGetBusData: no PCI BIOS\n"); return (0); } /* Config space is at most 256 bytes int */ if (Length > 256) Length = 256; /* Set up an OZONE-style PCI address block */ pcislotnumber.u.AsULONG = SlotNumber; memset (&pciconfp, 0, sizeof pciconfp); pciconfp.pcibus = SystemIoBusNumber; pciconfp.pcidev = pcislotnumber.u.bits.DeviceNumber; pciconfp.pcifunc = pcislotnumber.u.bits.FunctionNumber; /* Make sure there is something in slot, if not, return 2 */ vid = oz_dev_pci_conf_inw (&pciconfp, 0); if (vid == PCI_INVALID_VENDOR_ID) { i = 2; if (i > Length) i = Length; memcpy (Buffer, &vid, i); } /* Read config space into Buffer */ else { i = 0; buf = Buffer; while (Length > 3) { Length -= 4; *((uLong *)buf) = oz_dev_pci_conf_inl (&pciconfp, i); buf += 4; i += 4; } if (Length > 1) { Length -= 2; *((uWord *)buf) = oz_dev_pci_conf_inw (&pciconfp, i); buf += 2; i += 2; } if (Length > 0) { *buf = oz_dev_pci_conf_inb (&pciconfp, i); i ++; } } ///oz_knl_printk ("scsiport ScsiPortGetBusData*: %u,%u,%u config:\n", pciconfp.pcibus, pciconfp.pcidev, pciconfp.pcifunc); ///oz_knl_dumpmem (i, Buffer); return (i); } /************************************************************************/ /* */ /* Returns a mapped system address that must be used to access device */ /* address ranges in that HBA's ACCESS_RANGE elements */ /* */ /* Input: */ /* */ /* HwDeviceExtension = points to HBA device extension */ /* BusType = type of bus, retrieved from PORT_CONFIGURATION_INFORMATION struct /* SystemIoBusNumber = specifies on which bus the HBA is connected */ /* retrieved from PORT_CONFIGURATION_INFORMATION struct /* IoAddress = specifies the starting physical base address for the HBA /* NumberOfBytes = specifies the number of bytes that the mapping covers /* InIoSpace = FALSE : IoAddress is physical memory address of registers /* TRUE : IoAddress is starting I/O port number of registers /* */ /* Output: */ /* */ /* ScsiPortGetDeviceBase = NULL : can't be mapped */ /* else : virt address it is mapped to */ /* */ /************************************************************************/ asm (".globl ScsiPortGetDeviceBase\n" "ScsiPortGetDeviceBase:\n" " pushl %ebp\n" " pushl $0\n" " leal 4(%esp),%ebp\n" " pushl 32(%ebp)\n" " pushl 28(%ebp)\n" " pushl 24(%ebp)\n" " pushl 20(%ebp)\n" " pushl 16(%ebp)\n" " pushl 12(%ebp)\n" " pushl 8(%ebp)\n" " call _ScsiPortGetDeviceBase\n" " leave\n" " ret $28"); static PVOID _ScsiPortGetDeviceBase (PVOID HwDeviceExtension, // as passed to HwFindAdapter INTERFACE_TYPE BusType, // from ConfigInfo -> AdapterInterfaceType ULONG SystemIoBusNumber, // from ConfigInfo -> SystemIoBusNumber SCSI_PHYSICAL_ADDRESS IoAddress, // from accessRange -> RangeStart ULONG NumberOfBytes, // from accessRange -> RangeLength BOOLEAN InIoSpace) // ?? from !(accessRange -> RangeInMemory) { Devex *devex; Mbase *mbase; OZ_Mempage i, npages, phypage, sysvpage; uByte *vaddr; uLong sts; void *sysvaddr; ///oz_knl_printk ("scsiport ScsiPortGetDeviceBase*: (%p, %d, %u, %X.%X, %u, %d)\n", /// HwDeviceExtension, BusType, SystemIoBusNumber, IoAddress, NumberOfBytes, InIoSpace); ///oz_knl_printk ("scsiport ScsiPortGetDeviceBase*: "); ///for (i = 1; i <= 3; i ++) { /// oz_knl_printk (" "); /// oz_knl_printkaddr (oz_hw_getrtnadr (i)); ///} ///oz_knl_printk ("\n"); /* If I/O space, use our fake virtual addresses for it */ /* Read/Write port routines recognize this and use in/out instructions */ if (InIoSpace) { if ((IoAddress > 65535) || (IoAddress + NumberOfBytes > 65535)) { // we can only do 16-bit I/O port addresses oz_knl_printk ("scsiport ScsiPortGetDeviceBase: IO address %X @ %X too big\n", NumberOfBytes, IoAddress); return (NULL); } ///oz_knl_printk ("scsiport ScsiPortGetDeviceBase*: = %8.8X\n", FAKE_IO_BASE_VA + IoAddress); return ((PVOID)(FAKE_IO_BASE_VA + (uWord)IoAddress)); // return 0xFFFF0000 + port_number } /* If not, allocate some spte's to give it a system virtual address */ phypage = IoAddress >> OZ_HW_L2PAGESIZE; npages = ((IoAddress + NumberOfBytes - 1) >> OZ_HW_L2PAGESIZE) - phypage + 1; sts = oz_knl_spte_alloc (npages, &sysvaddr, &sysvpage, NULL); if (sts != OZ_SUCCESS) { oz_knl_printk ("scsiport ScsiPortGetDeviceBase: error %u allocating %u sptes\n", sts, npages); return (NULL); } /* Map the physical pages to those virtual addresses (with all caching disabled) */ vaddr = sysvaddr; // get virtual address for first physical page for (i = 0; i < npages; i ++) { // loop through all pages oz_hw_map_iopage (phypage, vaddr); // map physical page to virtual address phypage ++; // on to next physical page vaddr += 1 << OZ_HW_L2PAGESIZE; // on to virt address at beg of next page } /* Remember how many pages and where they are so we can properly free them */ mbase = OZ_KNL_NPPMALLOC (sizeof *mbase); mbase -> npages = npages; mbase -> svpage = OZ_HW_VADDRTOVPAGE (sysvaddr); GETDEVEX (devex, HwDeviceExtension); mbase -> next = devex -> mbases; devex -> mbases = mbase; /* Return the virtual address the controller's physical pages got mapped to */ vaddr = sysvaddr; // get virt address at beg of page vaddr += IoAddress & ((1 << OZ_HW_L2PAGESIZE) - 1); // add offset in the page ///oz_knl_printk ("scsiport ScsiPortGetDeviceBase*: = %8.8X\n", vaddr); return (vaddr); } /************************************************************************/ /* */ /* Provides access to logical-unit-specific data for a device on the */ /* SCSI bus */ /* */ /************************************************************************/ asm (".globl ScsiPortGetLogicalUnit\n" "ScsiPortGetLogicalUnit:\n" " pushl %ebp\n" " pushl $0\n" " leal 4(%esp),%ebp\n" " pushl 20(%ebp)\n" " pushl 16(%ebp)\n" " pushl 12(%ebp)\n" " pushl 8(%ebp)\n" " call _ScsiPortGetLogicalUnit\n" " leave\n" " ret $16"); static PVOID _ScsiPortGetLogicalUnit (PVOID HwDeviceExtension, UCHAR PathId, UCHAR TargetId, UCHAR Lun) { Devex *devex; Lu *lu; ///oz_knl_printk ("scsiport ScsiPortGetLogicalUnit*: (%p, %u, %u, %u)\n", HwDeviceExtension, PathId, TargetId, Lun); GETDEVEX (devex, HwDeviceExtension); ///oz_knl_printk ("scsiport ScsiPortGetLogicalUnit*: devex %p\n", devex); lu = get_lu (devex, PathId, TargetId, Lun); return (lu -> hbaextension); } /************************************************************************/ /* */ /* Get pointer to internal logical unit struct */ /* */ /* Input: */ /* */ /* devex = our controller device struct */ /* PathId = scsi bus number on the controller */ /* TargetId = scsi-id number of device */ /* Lun = logical unit within the device */ /* */ /* Output: */ /* */ /* get_lu = NULL : no struct defined for the lu yet */ /* else : pointer to internal per-lu struct */ /* */ /************************************************************************/ static Lu *get_lu (Devex *devex, UCHAR PathId, UCHAR TargetId, UCHAR Lun) { Lu *lu; uLong dv; dv = oz_hw_smplock_wait (devex -> smplock_dv); for (lu = devex -> lus; lu != NULL; lu = lu -> next) { if ((lu -> PathId == PathId) && (lu -> TargetId == TargetId) && (lu -> Lun == Lun)) break; } oz_hw_smplock_clr (devex -> smplock_dv, dv); return (lu); } /************************************************************************/ /* */ /* Translates a virtual address range to a physical address range for */ /* a DMA operation. */ /* */ /* Input: */ /* */ /* VirtualAddress = virtual address to be translated: */ /* UncachedExtension : Srb must be NULL */ /* SrbExtension : Srb must be NULL */ /* DataBuffer : Srb must be valid srb pointer */ /* SenseInfoBuffer : Srb can be NULL or valid srb pointer */ /* */ /* Output: */ /* */ /* ScsiPortGetPhysicalAddress = corresponding PCI address */ /* *Length = number of physically contiguous bytes */ /* */ /************************************************************************/ asm (".globl ScsiPortGetPhysicalAddress\n" "ScsiPortGetPhysicalAddress:\n" " pushl %ebp\n" " pushl $0\n" " leal 4(%esp),%ebp\n" " pushl 20(%ebp)\n" " pushl 16(%ebp)\n" " pushl 12(%ebp)\n" " pushl 8(%ebp)\n" " call _ScsiPortGetPhysicalAddress\n" " leave\n" " ret $16"); static SCSI_PHYSICAL_ADDRESS _ScsiPortGetPhysicalAddress (PVOID HwDeviceExtension, PSCSI_REQUEST_BLOCK Srb, PVOID VirtualAddress, ULONG *Length) { Iopex *iopex; OZ_Mempage ppn; uLong offset, ppo; ///oz_knl_printk ("scsiport ScsiPortGetPhysicalAddress*: (%p, %p, %p, %p)\n", HwDeviceExtension, Srb, VirtualAddress, Length); /* If virtual address is in system space, it is a real virtual address, be it */ /* original or re-mapped, so we get the physical address by reading the pte's */ #if OZ_HW_BASE_SYSC_VA > OZ_HW_BASE_PROC_VA error : code assumes system space is lower than process space #endif if ((uByte *)VirtualAddress < DATAVA) { *Length = oz_knl_misc_sva2pax (VirtualAddress, &ppn, &ppo, 1 << OZ_HW_L2PAGESIZE); // get starting phys page number and offset in the page // return number of physically contiguous bytes // but spec says return only one page worth } /* It is in per-process space, so it is the Srb's data buffer mapped by the scatter/gather array */ else { iopex = Srb -> OriginalRequest; offset = (uByte *)VirtualAddress - DATAVA; // get offset in buffer they are requesting *Length = 0; // assume offset is too large ppn = 0; ppo = 0; if (offset < iopex -> doiopp.datasize) { // see if offset is in range offset += iopex -> doiopp.databyteoffs; // ok, increment for offset in first page ppn = iopex -> doiopp.dataphypages[offset>>OZ_HW_L2PAGESIZE]; // get physical page number ppo = offset % (1 << OZ_HW_L2PAGESIZE); // get offset in the page *Length = (1 << OZ_HW_L2PAGESIZE) - ppo; offset = iopex -> doiopp.datasize + DATAVA - (uByte *)VirtualAddress; // get number of bytes at that address if (*Length > offset) *Length = offset; // make sure we aren't returning too big a length } } ///oz_knl_printk ("scsiport ScsiPortGetPhysicalAddress*: phyaddr %X, length %X\n", (ppn << OZ_HW_L2PAGESIZE) + ppo, *Length); return ((ppn << OZ_HW_L2PAGESIZE) + ppo); // return the PCI bus address of the VirtualAddress } /************************************************************************/ /* */ /* Allocates memory that can be used by both the CPU and host bus */ /* adapter for DMA or shared data. The memory must be marked by the */ /* CPU as not being cacheable. */ /* */ /* Input: */ /* */ /* HwDeviceExtension = HBA driver's extension area */ /* ConfigInfo = DMA config info */ /* NumberOfBytes = number of bytes to allocate */ /* smplevel = softint */ /* */ /* Output: */ /* */ /* ScsiPortGetUncachedExtension = NULL : allocation failed */ /* else : virt address of */ /* physically contig memory */ /* */ /* Note: */ /* */ /* This shall be called only from HBA's HwFindAdapter routine */ /* (so we know we're at softint level) */ /* */ /************************************************************************/ asm (".globl ScsiPortGetUncachedExtension\n" "ScsiPortGetUncachedExtension:\n" " pushl %ebp\n" " pushl $0\n" " leal 4(%esp),%ebp\n" " pushl 24(%ebp)\n" " pushl 20(%ebp)\n" " pushl 16(%ebp)\n" " pushl 12(%ebp)\n" " pushl 8(%ebp)\n" " call _ScsiPortGetUncachedExtension\n" " leave\n" " ret $20"); static PVOID *_ScsiPortGetUncachedExtension (PVOID HwDeviceExtension, PPORT_CONFIGURATION_INFORMATION ConfigInfo, ULONG NumberOfBytes) { Devex *devex; OZ_Mempage i, npages, phypage, sysvpage; OZ_Pointer sysva; Uce *uce; uLong pm, sts; void *sysvaddr; ///oz_knl_printk ("scsiport ScsiPortGetUncachedExtension*: (%p, %p, %X)\n", HwDeviceExtension, ConfigInfo, NumberOfBytes); GETDEVEX (devex, HwDeviceExtension); /* See how many pages are required */ npages = (NumberOfBytes + (1 << OZ_HW_L2PAGESIZE) - 1) >> OZ_HW_L2PAGESIZE; ///oz_knl_printk ("scsiport ScsiPortGetUncachedExtension*: npages %u\n", npages); if (npages == 0) return (NULL); /* Allocate some system pagetable entries for them */ sts = oz_knl_spte_alloc (npages, &sysvaddr, &sysvpage, NULL); if (sts != OZ_SUCCESS) return (NULL); /* Allocate contiguous physical pages */ pm = oz_hw_smplock_wait (&oz_s_smplock_pm); if (npages == 1) phypage = oz_knl_phymem_allocpage (OZ_PHYMEM_PAGESTATE_ALLOCSECT); else phypage = oz_knl_phymem_allocontig (npages, OZ_PHYMEM_PAGESTATE_ALLOCSECT); oz_hw_smplock_clr (&oz_s_smplock_pm, pm); ///oz_knl_printk ("scsiport ScsiPortGetUncachedExtension*: phypage %X\n", phypage); if (phypage == OZ_PHYPAGE_NULL) { oz_knl_spte_free (npages, sysvpage); return (NULL); } /* Save virtual and physical addresses in case they call that horrid ScsiPortGetVirutalAddress */ uce = OZ_KNL_NPPMALLOC (sizeof *uce); uce -> next = devex -> uces; uce -> PhysicalAddress = ((SCSI_PHYSICAL_ADDRESS)phypage) << OZ_HW_L2PAGESIZE; uce -> VirtualAddress = sysvaddr; uce -> Length = NumberOfBytes; devex -> uces = uce; /* Write their pagetable entries as non-cacheable */ for (i = 0; i < npages; i ++) { oz_hw_map_iopage (phypage, OZ_HW_VPAGETOVADDR (sysvpage)); phypage ++; sysvpage ++; } /* Return base virtual address */ ///oz_knl_printk ("scsiport ScsiPortGetUncachedExtension*: sysvaddr %p\n", sysvaddr); return (sysvaddr); } /************************************************************************/ /* */ /* Returns a virtual address associated with a physical address if */ /* the physical address was obtained by a call to */ /* ScsiPortGetPhysicalAddress */ /* */ /* Input: */ /* */ /* HwDeviceExtension = pointer to HBA miniport driver's dev ext */ /* PhysicalAddress = physical address to reverse map */ /* */ /* Output: */ /* */ /* ScsiPortGetVirtualAddress = NULL : couldn't reverse-translate */ /* else : it's virtual address */ /* */ /* Note: */ /* */ /* The PhysicalAddress is from an uncached device extension or */ /* SRB extension */ /* */ /************************************************************************/ asm (".globl ScsiPortGetVirtualAddress\n" "ScsiPortGetVirtualAddress:\n" " pushl %ebp\n" " pushl $0\n" " leal 4(%esp),%ebp\n" " pushl 16(%ebp)\n" " pushl 12(%ebp)\n" " pushl 8(%ebp)\n" " call _ScsiPortGetVirtualAddress\n" " leave\n" " ret $12"); static PVOID _ScsiPortGetVirtualAddress (PVOID HwDeviceExtension, SCSI_PHYSICAL_ADDRESS PhysicalAddress) { Devex *devex; Iopex *iopex; Lu *lu; OZ_Mempage phypage; SCSI_PHYSICAL_ADDRESS srbextphyaddr; Uce *uce; ULONG pageoffs, srbcontigsize; GETDEVEX (devex, HwDeviceExtension); /* First search uncached extensions */ for (uce = devex -> uces; uce != NULL; uce = uce -> next) { if (uce -> PhysicalAddress > PhysicalAddress) continue; if (uce -> PhysicalAddress + uce -> Length <= PhysicalAddress) continue; return (uce -> VirtualAddress + (PhysicalAddress - uce -> PhysicalAddress)); } /* Now search all SRB extensions */ if (devex -> SrbExtensionSize > 0) { for (lu = devex -> lus; lu != NULL; lu = lu -> next) { for (iopex = lu -> inprog_q; iopex != NULL; iopex = iopex -> next) { srbcontigsize = oz_knl_misc_sva2pax (iopex -> Srb.SrbExtension, &phypage, &pageoffs, devex -> SrbExtensionSize); srbextphyaddr = (((SCSI_PHYSICAL_ADDRESS)phypage) << OZ_HW_L2PAGESIZE) + pageoffs; if (srbextphyaddr > PhysicalAddress) continue; if (srbextphyaddr + srbcontigsize <= PhysicalAddress) continue; return (((PUCHAR)(iopex -> Srb.SrbExtension)) + (PhysicalAddress - srbextphyaddr)); } } } /* Nowhere to be found */ return (NULL); } /************************************************************************/ /* */ /* Sets up system objects on behalf of the HBA miniport driver */ /* */ /* Input: */ /* */ /* Argument1 = device name prefix, eg, "aic78xx" */ /* Argument2 = points to an 'int' initialized to zero */ /* used to count number of devunits created */ /* HwInitializationData = stuff about the controller type */ /* (gets wiped out on return) */ /* HwContext = pass to HwFindAdapter as is */ /* */ /* Output: */ /* */ /* OZONE devunit's created */ /* */ /************************************************************************/ static int caztob (int len, const char *str); static int inipcidev (OZ_Dev_pci_conf_p *pciconfp, char *nameprefix, HW_INITIALIZATION_DATA *HwInitializationData, PVOID HwContext); asm (".globl ScsiPortInitialize\n" "ScsiPortInitialize:\n" " pushl %ebp\n" " pushl $0\n" " leal 4(%esp),%ebp\n" " pushl 20(%ebp)\n" " pushl 16(%ebp)\n" " pushl 12(%ebp)\n" " pushl 8(%ebp)\n" " call _ScsiPortInitialize\n" " leave\n" " ret $16"); static ULONG _ScsiPortInitialize (PVOID Argument1, PVOID Argument2, HW_INITIALIZATION_DATA *HwInitializationData, PVOID HwContext) { int deviceid, first, vendorid; OZ_Dev_pci_conf_p pciconfp; /* We only do PCI Bus */ if (HwInitializationData -> AdapterInterfaceType != PCIBus) { oz_knl_printk ("scsiport ScsiPortInitialize: %s non-PCI AdapterInterfaceType %d\n", Argument1, HwInitializationData -> AdapterInterfaceType); return (0); } /* Use PCI BIOS to scan for SCSI controllers. Call the miniport driver for any that are found. */ ///oz_knl_printk ("scsiport ScsiPortInitialize*: vendor-id <%*.*s> device-id <%*.*s>\n", /// HwInitializationData -> VendorIdLength, HwInitializationData -> VendorIdLength, HwInitializationData -> VendorId, /// HwInitializationData -> DeviceIdLength, HwInitializationData -> DeviceIdLength, HwInitializationData -> DeviceId); vendorid = caztob (HwInitializationData -> VendorIdLength, HwInitializationData -> VendorId); if ((vendorid <= 0) || (vendorid >= 65535)) { oz_knl_printk ("scsiport ScsiPortInitialize: %s invalid vendor-id <%*.*s>\n", Argument1, HwInitializationData -> VendorIdLength, HwInitializationData -> VendorIdLength, HwInitializationData -> VendorId); vendorid = -1; } deviceid = caztob (HwInitializationData -> DeviceIdLength, HwInitializationData -> DeviceId); if ((deviceid <= 0) || (deviceid >= 65535)) { oz_knl_printk ("scsiport ScsiPortInitialize: %s invalid device-id <%*.*s>\n", Argument1, HwInitializationData -> DeviceIdLength, HwInitializationData -> DeviceIdLength, HwInitializationData -> DeviceId); deviceid = -1; } if ((vendorid < 0) || (deviceid < 0)) return (0); for (first = 1; oz_dev_pci_conf_scan_didvid (&pciconfp, first, (deviceid << 16) | vendorid); first = 0) { *(int *)Argument2 += inipcidev (&pciconfp, Argument1, HwInitializationData, HwContext); } /* We don't really use the return code for anything, so just return zero */ return (0); } /* Counted ascii hex string to binary */ static int caztob (int len, const char *str) { char c; int i, v; v = 0; for (i = 0; i < len; i ++) { c = str[i]; if (c == 0) break; if ((c >= '0') && (c <= '9')) c -= '0'; else if ((c >= 'A') && (c <= 'F')) c -= 'A' - 10; else if ((c >= 'a') && (c <= 'f')) c -= 'a' - 10; else return (-1); v = v * 16 + c; } return (v); } /************************************************************************/ /* */ /* Perform initialization for a PCI device */ /* */ /* Input: */ /* */ /* pciconfp = pointer to OZONE-style pci address block */ /* nameprefix = OZONE device name prefix string, eg, "aic78xx" */ /* HwInitializationData = miniport's hw init data info */ /* HwContext = passed on to HwFindAdapter */ /* smplevel = softint */ /* */ /* Output: */ /* */ /* inipcidev = 0 : no devunit created */ /* 1 : devunit was created */ /* */ /************************************************************************/ static int inipcidev (OZ_Dev_pci_conf_p *pciconfp, char *nameprefix, HW_INITIALIZATION_DATA *HwInitializationData, PVOID HwContext) { ACCESS_RANGE *accessranges; BOOLEAN Again, hwinitrc; char unitdesc[OZ_DEVUNIT_DESCSIZE], unitname[OZ_DEVUNIT_NAMESIZE]; int i, j; Devex *devex; OZ_Devclass *devclass; OZ_Devdriver *devdriver; OZ_Devunit *devunit; OZ_Smplock *smplock_dv; PCI_SLOT_NUMBER pcislotnumber; ULONG basadr, basmsk, dv, sts; pcislotnumber.u.AsULONG = 0; pcislotnumber.u.bits.DeviceNumber = pciconfp -> pcidev; pcislotnumber.u.bits.FunctionNumber = pciconfp -> pcifunc; /* Create unitname for the device and make sure it doesn't already exist in the system */ if (pciconfp -> pcifunc == 0) oz_sys_sprintf (sizeof unitname, unitname, "%s_%u_%u", nameprefix, pciconfp -> pcibus, pciconfp -> pcidev); else oz_sys_sprintf (sizeof unitname, unitname, "%s_%u_%u_%u", nameprefix, pciconfp -> pcibus, pciconfp -> pcidev, pciconfp -> pcifunc); devunit = oz_knl_devunit_lookup (unitname); if (devunit != NULL) { oz_knl_devunit_increfc (devunit, -1); return (0); } /* Mallocate memory for the following: */ /* 1) our devex struct */ /* 2) miniport driver's HwDeviceExtension struct */ /* 4) AccessRanges struct array */ j = (HwInitializationData -> DeviceExtensionSize + 3) & -4; i = (sizeof *devex + 3) & -4; i += j; i += HwInitializationData -> NumberOfAccessRanges * sizeof *accessranges; devex = OZ_KNL_NPPMALLOC (i); memset (devex, 0, i); /* The accessranges array comes just after the HwDeviceExtension struct */ accessranges = (void *)(devex -> HwDeviceExtension + j); ///oz_knl_printk ("scsiport inipcidev*: devex %p, ConfigInfo %p, HwDeviceExtension %p, accessranges %p\n", /// devex, &(devex -> ConfigInfo), devex -> HwDeviceExtension, accessranges); /* Mallocate the BusInformation and fill it in */ /* ?? not used by ini910u driver, maybe not used by aic78xx one either ?? */ #define BusInformation NULL /* Mallocate and fill in the ConfigInfo */ /* http://www.osr.com/ddk/k306_2h4i.htm */ devex -> ConfigInfo.Length = sizeof (devex -> ConfigInfo); devex -> ConfigInfo.BusInterruptLevel = oz_dev_pci_conf_inb (pciconfp, OZ_DEV_PCI_CONF_B_INTLINE); ///oz_knl_printk ("scsiport inipcidev*: devex -> ConfigInfo.BusInterruptLevel %u\n", devex -> ConfigInfo.BusInterruptLevel); devex -> ConfigInfo.AdapterInterfaceType = HwInitializationData -> AdapterInterfaceType; devex -> ConfigInfo.MaximumTransferLength = SP_UNINITIALIZED_VALUE; devex -> ConfigInfo.NumberOfPhysicalBreaks = SP_UNINITIALIZED_VALUE; devex -> ConfigInfo.DmaChannel = SP_UNINITIALIZED_VALUE; devex -> ConfigInfo.DmaPort = SP_UNINITIALIZED_VALUE; devex -> ConfigInfo.NumberOfAccessRanges = HwInitializationData -> NumberOfAccessRanges; devex -> ConfigInfo.MapBuffers = HwInitializationData -> MapBuffers; devex -> ConfigInfo.NeedPhysicalAddresses = HwInitializationData -> NeedPhysicalAddresses; devex -> ConfigInfo.TaggedQueuing = HwInitializationData -> TaggedQueuing; devex -> ConfigInfo.AutoRequestSense = HwInitializationData -> AutoRequestSense; devex -> ConfigInfo.MultipleRequestPerLu = HwInitializationData -> MultipleRequestPerLu; devex -> ConfigInfo.ReceiveEvent = HwInitializationData -> ReceiveEvent; devex -> ConfigInfo.MaximumNumberOfTargets = SCSI_MAXIMUM_TARGETS; devex -> ConfigInfo.MaximumNumberOfLogicalUnits = SCSI_MAXIMUM_LOGICAL_UNITS; devex -> ConfigInfo.SlotNumber = pcislotnumber.u.AsULONG; devex -> ConfigInfo.SystemIoBusNumber = pciconfp -> pcibus; devex -> ConfigInfo.InterruptMode = LevelSensitive; // LevelSensitive for PCIBus /* Fill in the access ranges array by scanning the PCI Config space's bus address registers */ ///oz_knl_printk ("scsiport inipcidev*: NumberOfAccessRanges %u\n", devex -> ConfigInfo.NumberOfAccessRanges); if (devex -> ConfigInfo.NumberOfAccessRanges > 0) { if (devex -> ConfigInfo.NumberOfAccessRanges > 6) devex -> ConfigInfo.NumberOfAccessRanges = 6; for (i = 0; i < devex -> ConfigInfo.NumberOfAccessRanges; i ++) { // loop through all possible base address registers basadr = oz_dev_pci_conf_inl (pciconfp, OZ_DEV_PCI_CONF_L_BASADR0 + (i * 4)); // read existing contents basmsk = 0xFFFFFFF0; // mem type has garb in <3:0> if (basadr & 1) basmsk = 0xFFFFFFFC; // IO type hag garb in <1:0> accessranges[i].RangeStart = basadr & basmsk; // save base address (except garbage) if ((basadr & basmsk) != 0) { // ignore if zero oz_dev_pci_conf_outl (basmsk, pciconfp, OZ_DEV_PCI_CONF_L_BASADR0 + (i * 4)); // write all ones (except garbage) basmsk &= oz_dev_pci_conf_inl (pciconfp, OZ_DEV_PCI_CONF_L_BASADR0 + (i * 4)); // read it all back (except garbage) oz_dev_pci_conf_outl (basadr, pciconfp, OZ_DEV_PCI_CONF_L_BASADR0 + (i * 4)); // restore original contents if (basmsk != 0) { // see if it allowed us to set bits accessranges[i].RangeLength = -basmsk; // save length (low bits of mask remained clear) accessranges[i].RangeInMemory = !(basadr & 1); // save 'in memory' flag } } } devex -> ConfigInfo.AccessRanges = accessranges; // set up pointer to array ///oz_knl_printk ("scsiport inipcidev*: devex -> ConfigInfo.AccessRanges %p\n", accessranges); ///for (i = 0; i < devex -> ConfigInfo.NumberOfAccessRanges; i ++) { /// oz_knl_printk ("scsiport inipcidev*: accessrange[%d]: %X.%X %X %u\n", /// i, accessranges[i].RangeStart, accessranges[i].RangeLength, accessranges[i].RangeInMemory); ///} } /* See if this is an adapter it can process */ Again = 0; // sts = (*(HwInitializationData -> HwFindAdapter)) (devex -> HwDeviceExtension, // HwContext, // BusInformation, // "", // ArgumentString // &(devex -> ConfigInfo), // &Again); asm ("pushl %7\n" " pushl %6\n" " pushl %5\n" " pushl %4\n" " pushl %3\n" " pushl %2\n" " movl %1,%%eax\n" " call *%%eax\n" : "=a" (sts) : "m" (HwInitializationData -> _HwFindAdapter), "g" (devex -> HwDeviceExtension), "g" (HwContext), "g" (BusInformation), "g" (""), "g" (&(devex -> ConfigInfo)), "g" (&Again) : "eax", "ecx", "edx", "cc", "memory"); /* If error, free everything off */ if (sts != SP_RETURN_FOUND) { oz_knl_printk ("scsiport: HwFindAdapter (%u,%u,%u) error %u\n", pciconfp -> pcibus, pciconfp -> pcidev, pciconfp -> pcifunc, sts); while (devex -> mbases != NULL) { _ScsiPortFreeDeviceBase (devex -> HwDeviceExtension, OZ_HW_VPAGETOVADDR (devex -> mbases -> svpage)); } OZ_KNL_NPPFREE (devex); return (0); } /* An adapter was found, and the ConfigInfo struct is filled in - create OZONE device */ oz_sys_sprintf (sizeof unitdesc, unitdesc, "scsi_id %u", devex -> ConfigInfo.InitiatorBusId[0]); for (i = 0; i < devex -> ConfigInfo.NumberOfAccessRanges; i ++) { if (accessranges[i].RangeLength != 0) { j = strlen (unitdesc); oz_sys_sprintf (sizeof unitdesc - j, unitdesc + j, ", %s %X", accessranges[i].RangeInMemory ? "mem" : "io", accessranges[i].RangeStart); } } j = strlen (unitdesc); oz_sys_sprintf (sizeof unitdesc - j, unitdesc + j, ", irq %u", devex -> ConfigInfo.BusInterruptLevel); devclass = oz_knl_devclass_create (OZ_IO_SCSI_CLASSNAME, OZ_IO_SCSI_BASE, OZ_IO_SCSI_MASK, "scsiport"); devdriver = oz_knl_devdriver_create (devclass, "scsiport"); devunit = oz_knl_devunit_create (devdriver, unitname, unitdesc, &scsiport_functable, 0, oz_s_secattr_sysdev); *(Devex **)oz_knl_devunit_ex (devunit) = devex; devex -> devunit = devunit; devex -> devname = oz_knl_devunit_devname (devunit); devex -> iopex_qt = &(devex -> iopex_qh); devex -> abort_qt = &(devex -> abort_qh); devex -> SpecificLuExtensionSize = HwInitializationData -> SpecificLuExtensionSize; devex -> SrbExtensionSize = HwInitializationData -> SrbExtensionSize; devex -> _HwStartIo = HwInitializationData -> _HwStartIo; devex -> _HwInterrupt = HwInitializationData -> _HwInterrupt; devex -> lowipl = oz_knl_lowipl_alloc (); devex -> timeout_timer = oz_knl_timer_alloc (); devex -> timeout_when = (OZ_Datebin)-1LL; devex -> irq_many.entry = got_interrupt; devex -> irq_many.param = devex; devex -> irq_many.descr = devex -> devname; devex -> smplock_dv = oz_hw486_irq_many_add (devex -> ConfigInfo.BusInterruptLevel, &(devex -> irq_many)); if (devex -> smplock_dv == NULL) oz_crash ("scsiport_486 inipcidev: interrupt vector %u failed to initialize", devex -> ConfigInfo.BusInterruptLevel); /* Set up an autogen routine to automatically configure devices on the bus */ oz_knl_devunit_autogen (devunit, oz_dev_scsi_auto, NULL); /* Initialize HW controller */ dv = oz_hw_smplock_wait (devex -> smplock_dv); // hwinitrc = (*(HwInitializationData -> HwInitialize)) (devex -> HwDeviceExtension); asm ("pushl %2\n" " movl %1,%%eax\n" " call *%%eax\n" : "=a" (hwinitrc) : "m" (HwInitializationData -> _HwInitialize), "g" (devex -> HwDeviceExtension) : "eax", "ecx", "edx", "cc", "memory"); oz_hw_smplock_clr (devex -> smplock_dv, dv); if (hwinitrc) oz_knl_printk ("scsiport: %s online\n", devex -> devname); else oz_knl_printk ("scsiport: %s offline\n", devex -> devname); return (1); } /************************************************************************/ /* */ /* HBA miniport driver calls this to log errors */ /* */ /************************************************************************/ asm (".globl ScsiPortLogError\n" "ScsiPortLogError:\n" " pushl %ebp\n" " pushl $0\n" " leal 4(%esp),%ebp\n" " pushl 32(%ebp)\n" " pushl 28(%ebp)\n" " pushl 24(%ebp)\n" " pushl 20(%ebp)\n" " pushl 16(%ebp)\n" " pushl 12(%ebp)\n" " pushl 8(%ebp)\n" " call _ScsiPortLogError\n" " leave\n" " ret $28"); static VOID _ScsiPortLogError (PVOID HwDeviceExtension, PSCSI_REQUEST_BLOCK Srb, UCHAR PathId, UCHAR TargetId, UCHAR Lun, LONG ErrorCode, LONG UniqueId) { const char *devname; Devex *devex; devname = ""; if (HwDeviceExtension != NULL) { GETDEVEX (devex, HwDeviceExtension); if (devex -> devname != NULL) devname = devex -> devname; } oz_knl_printk ("scsiport: %s ScsiPortLogError (%p, %p, %u, %u, %u, %d, %d=0x%X)\n", devname, HwDeviceExtension, Srb, PathId, TargetId, Lun, ErrorCode, UniqueId, UniqueId); } /************************************************************************/ /* */ /* Move memory - spec doesn't discuss overlapping areas so we assume */ /* that's possible and do a memmove-type function */ /* */ /************************************************************************/ // VOID ScsiPortMoveMemory (PVOID WriteBuffer, PVOID ReadBuffer, ULONG Length) asm (".globl ScsiPortMoveMemory\n" "ScsiPortMoveMemory:\n" " pushl %ebp\n" " pushl $0\n" " leal 4(%esp),%ebp\n" " pushl %edi\n" " pushl %esi\n" " movl 8(%ebp),%edi\n" " movl 12(%ebp),%esi\n" " movl 16(%ebp),%ecx\n" " movl %esi,%eax\n" " cld\n" " subl %edi,%eax\n" " jecxz memmove_null\n" " jz memmove_null\n" " jnc memmove_copy\n" " std\n" " leal -1(%esi,%ecx),%esi\n" " leal -1(%edi,%ecx),%edi\n" "memmove_copy:\n" " rep\n" " movsb\n" " cld\n" "memmove_null:\n" " popl %esi\n" " popl %edi\n" " leave\n" " ret $12"); /************************************************************************/ /* */ /* Notifies us of several conditions */ /* */ /************************************************************************/ /* Caller pops args with this one, so no asm stub is necessary */ VOID ScsiPortNotification (SCSI_NOTIFICATION_TYPE NotificationType, PVOID HwDeviceExtension, ...) { Devex *devex; Iopex *iopex; Lu *lu; PSCSI_REQUEST_BLOCK Srb; UCHAR Lun, PathId, TargetId; uLong dv, sts; va_list ap; ///oz_knl_printk ("scsiport ScsiPortNotification*: (%d, %p, ...)\n", NotificationType, HwDeviceExtension); GETDEVEX (devex, HwDeviceExtension); va_start (ap, HwDeviceExtension); switch (NotificationType) { /* Indicates the supplied Srb has finished */ case RequestComplete: { Srb = va_arg (ap, PSCSI_REQUEST_BLOCK); // get pointer to completed request ///oz_knl_printk ("scsiport requestcomplete*: Srb %p\n", Srb); ///oz_knl_dumpmem (sizeof *Srb, Srb); iopex = Srb -> OriginalRequest; lu = iopex -> lu; // get the lu it was on dv = oz_hw_smplock_wait (devex -> smplock_dv); // lock device state switch (Srb -> Function) { case SRB_FUNCTION_EXECUTE_SCSI: { ///oz_knl_printk ("scsiport ScsiPortNotification*: clearing %p -> BUSY_RUNNING\n", iopex); if (!(iopex -> busy & IOPEX_BUSY_RUNNING)) oz_crash ("scsiport_486 ScsiPortNotification: request not running"); iopex -> busy &= ~IOPEX_BUSY_RUNNING; *(iopex -> prev) = iopex -> next; // unlink from lu->inprog_q if (iopex -> next != NULL) iopex -> next -> prev = iopex -> prev; if (lu -> inprog_q == NULL) { // maybe the lu is no inter busy lu -> lu_busy = 0; if (!(devex -> hba_busy) && (devex -> lowipl != NULL)) { // maybe start another request oz_knl_lowipl_call (devex -> lowipl, dequeuereq, devex); // ... after resetting stack devex -> lowipl = NULL; } } break; } case SRB_FUNCTION_ABORT_COMMAND: { ///oz_knl_printk ("scsiport ScsiPortCompleteRequest*: clearing %p -> BUSY_ABORTING\n", iopex); if (!(iopex -> busy & IOPEX_BUSY_ABORTING)) oz_crash ("scsiport_486 ScsiPortNotification: request not aborting"); iopex -> busy &= ~IOPEX_BUSY_ABORTING; break; } default: { oz_crash ("scsiport_486: ScsiPortNotification: bad Srb func code %d", Srb -> Function); } } ///oz_knl_printk ("scsiport ScsiPortCompleteRequest*: busy %u\n", iopex -> busy); if (iopex -> busy == 0) { // make sure request is completely idle sts = srbsts2oz (iopex); ///oz_knl_printk ("scsiport ScsiPortCompleteRequest*: sts %u\n", sts); oz_knl_iodonehi (iopex -> ioop, sts, NULL, finishup, iopex); // post request's completion } oz_hw_smplock_clr (devex -> smplock_dv, dv); // release device state break; } /* Indicates the HBA driver is ready for another request on the specified logical unit or any other non-busy LU */ case NextLuRequest: { ///oz_knl_printk ("scsiport ScsiPortNotification*: NextLuRequest\n"); PathId = va_arg (ap, UCHAR); // point to logical unit's struct TargetId = va_arg (ap, UCHAR); Lun = va_arg (ap, UCHAR); lu = get_lu (devex, PathId, TargetId, Lun); if (lu != NULL) lu -> lu_busy = 0; // mark it no inter busy // fall through to get a new request going } /* Indicates the HBA driver is ready for another request on any non-busy logical unit */ case NextRequest: { ///oz_knl_printk ("scsiport ScsiPortNotification*: NextRequest\n"); dv = oz_hw_smplock_wait (devex -> smplock_dv); // lock state devex -> hba_busy = 0; // hba will accept another request now if (devex -> lowipl != NULL) { // maybe start another request oz_knl_lowipl_call (devex -> lowipl, dequeuereq, devex); // ... after resetting stack devex -> lowipl = NULL; } oz_hw_smplock_clr (devex -> smplock_dv, dv); // release device state break; } /* Indicates the SCSI bus has been reset somehow */ case ResetDetected: { break; } /* Indicates the HBA driver requires system interrupts to be re-disabled */ case CallDisableInterrupts: { devex -> _HwInterrupt = NULL; break; } /* Indicates the HBA driver would like system interrupts re-enabled */ case CallEnableInterrupts: { dv = oz_hw_smplock_wait (devex -> smplock_dv); // lock state devex -> _HwInterrupt = va_arg (ap, PHW_INTERRUPT); // get service routine address if (devex -> int_pending) { // see if there is a pending interrupt devex -> int_pending = 0; // if so, say no more // (*(devex -> hba_interrupt)) (devex -> HwDeviceExtension); // process interrupt asm ("pushl %1\n" " movl %0,%%eax\n" " call *%%eax\n" : : "m" (devex -> _HwInterrupt), "g" (devex -> HwDeviceExtension) : "eax", "ecx", "edx", "cc", "memory"); } oz_hw_smplock_clr (devex -> smplock_dv, dv); // release device state break; } /* Indicates the miniport has requested a specified routine be called in the given number of microseconds */ case RequestTimerCall: { PHW_TIMER entrypoint; ULONG timervalue; entrypoint = va_arg (ap, PHW_TIMER); timervalue = va_arg (ap, ULONG); oz_crash ("scsiport_486: RequestTimerCall not supported"); break; } /* Who knows what */ default: oz_crash ("scsiport_486: ScsiPortNotification NotificationType %d invalid", NotificationType); } va_end (ap); } /************************************************************************/ /* */ /* Dequeue a request for a non-busy LU */ /* */ /* Input: */ /* */ /* devex = controller struct pointer */ /* smplevel = softint */ /* */ /* Output: */ /* */ /* if suitable request found, it is dequeued from devex and devex */ /* and the corresponding lu are marked busy, then it is passed to */ /* the hba driver for processing */ /* */ /************************************************************************/ static void dequeuereq (void *devexv, OZ_Lowipl *lowipl) { Devex *devex; Iopex *iopex; Lu *lu; uLong dv; devex = devexv; dv = oz_hw_smplock_wait (devex -> smplock_dv); // lock device state devex -> lowipl = lowipl; // re-arm to be called again ///oz_knl_printk ("scsiport dequeuereq*: devex %p -> hba_busy %d\n", devex, devex -> hba_busy); if (!(devex -> hba_busy)) { // make sure hba will accept a new request iopex = devex -> abort_qh; // check for aborts first if (iopex != NULL) { devex -> abort_qh = iopex -> abnext; // ok, remove from queue if (iopex -> abnext == NULL) devex -> abort_qt = &(devex -> abort_qh); abortreq (iopex); // ... then pass to HBA miniport driver } else for (iopex = devex -> iopex_qh; iopex != NULL; iopex = iopex -> next) { // no aborts, loop through all waiting requests lu = iopex -> lu; // get the corresponding lu if (!(lu -> lu_busy)) { // see if lu is busy *(iopex -> prev) = iopex -> next; // if not, unlink request if (iopex -> next == NULL) devex -> iopex_qt = iopex -> prev; else iopex -> next -> prev = iopex -> prev; devex -> hba_busy = 1; // HBA won't accept another request lu -> lu_busy = 1; // LU won't accept another request startreq (iopex); // pass request on to HBA driver break; // that's it for now } } } oz_hw_smplock_clr (devex -> smplock_dv, dv); } /************************************************************************/ /* */ /* Put request on 'in progress' queue and pass to HwStartIo routine */ /* */ /* Input: */ /* */ /* iopex = request to pass to the HBA miniport driver */ /* known to not be linked to devex -> iopex_q */ /* devex -> hba_busy = known to be zero */ /* lu -> lu_busy = known to be zero */ /* smplevel = devex -> smplock_dv locked */ /* */ /* Output: */ /* */ /* iopex = linked on end of lu's inprog_q */ /* devex -> hba_busy = 1 */ /* lu -> lu_busy = 1 */ /* */ /* Note: */ /* */ /* If HBA miniport driver fails to accept request, it is aborted */ /* with status 'OZ_BUGCHECK' */ /* */ /************************************************************************/ static void startreq (Iopex *iopex) { BOOLEAN queued; Devex *devex; Lu *lu; devex = iopex -> devex; lu = iopex -> lu; /* Link to lu's 'inprog' queue so we can process with ScsiPortCompleteRequest if we need to */ /* It will also let RequestComplete processing know when there are no more requests active on the LU */ ///oz_knl_printk ("scsiport startreq*: setting %p -> BUSY_RUNNING\n", iopex); if (iopex -> busy & IOPEX_BUSY_RUNNING) oz_crash ("scsiport_486 startreq: request already running"); iopex -> busy |= IOPEX_BUSY_RUNNING; iopex -> next = lu -> inprog_q; iopex -> prev = &(lu -> inprog_q); lu -> inprog_q = iopex; if (iopex -> next != NULL) iopex -> next -> prev = &(iopex -> next); /* Pass to HBA for processing */ ///oz_knl_printk ("scsiport startreq*: start Srb %p\n", &(iopex -> Srb)); ///oz_knl_dumpmem (sizeof iopex -> Srb, &(iopex -> Srb)); // queued = (*(devex -> HwStartIo)) (devex -> HwDeviceExtension, &(iopex -> Srb)); asm ("pushl %3\n" " pushl %2\n" " movl %1,%%eax\n" " call *%%eax\n" : "=a" (queued) : "m" (devex -> _HwStartIo), "g" (devex -> HwDeviceExtension), "g" (&(iopex -> Srb)) : "eax", "ecx", "edx", "cc", "memory"); /* If it failed to queue, abort the request (I'm not sure what to wait for) */ if (!queued) { oz_knl_printk ("scsiport: %s startreq request did not queue\n", devex -> devname); // ?? do we re-queue it *(iopex -> prev) = iopex -> next; if (iopex -> next != NULL) iopex -> next -> prev = iopex -> prev; oz_knl_iodonehi (iopex -> ioop, OZ_BUGCHECK, NULL, finishup, iopex); } /* Otherwise, maybe (re)start the timer */ else if (iopex -> doiopp.timeout != 0) { // see if we have a timeout iopex -> timeout_when = oz_hw_timer_getnow (); // ok, get current time iopex -> timeout_when += iopex -> doiopp.timeout * (OZ_TIMER_RESOLUTION / 1000); // see when it will time out if (iopex -> timeout_when < devex -> timeout_when) { // maybe timer needs to be reset devex -> timeout_when = iopex -> timeout_when; // ok, use this earlier time oz_knl_timer_insert (devex -> timeout_timer, devex -> timeout_when, requesttimedout, devex); // ... and reset timer } } } /************************************************************************/ /* */ /* Called to check for timed-out requests */ /* */ /* Input: */ /* */ /* devexv = device that may have timed out requests */ /* smplevel = softint */ /* */ /************************************************************************/ static void requesttimedout (void *devexv, OZ_Timer *timer) { Devex *devex; Iopex *iopex; Lu *lu; OZ_Datebin next, now; uLong dv; devex = devexv; now = oz_hw_timer_getnow (); // anything that times out before 'now' is timed out next = (OZ_Datebin)-1LL; // find the earliest other thing after now dv = oz_hw_smplock_wait (devex -> smplock_dv); // lock the request queues for (lu = devex -> lus; lu != NULL; lu = lu -> next) { // scan all the logical units for (iopex = lu -> inprog_q; iopex != NULL; iopex = iopex -> next) { // scan all the in-progress requests if (iopex -> timeout_when <= now) { // see if it has timed out abortreq (iopex); // if so, abort it } else if (iopex -> timeout_when < next) { // else, see if it has the earliest timeout next = iopex -> timeout_when; // save when that is } } } devex -> timeout_when = next; // save when the next timeout is if (next != (OZ_Datebin)-1LL) oz_knl_timer_insert (devex -> timeout_timer, next, requesttimedout, devex); // start timer oz_hw_smplock_clr (devex -> smplock_dv, dv); // release queues } /************************************************************************/ /* */ /* Queue an abort for and I/O request to the HBA miniport driver */ /* */ /* Input: */ /* */ /* iopex = I/O request to be aborted */ /* smplevel = smplock_dv locked */ /* */ /* Output: */ /* */ /* abort queued */ /* */ /************************************************************************/ static void abortreq (Iopex *iopex) { BOOLEAN queued; Devex *devex; Lu *lu; if (!(iopex -> busy & IOPEX_BUSY_ABORTING)) { // don't do it if abort is currently queued ///oz_knl_printk ("scsiport abortreq*: setting %p -> BUSY_ABORTING\n", iopex); devex = iopex -> devex; // get corresponding devex struct lu = iopex -> lu; // get corresponding lu struct iopex -> busy |= IOPEX_BUSY_ABORTING; // remember we have set it up to abort memset (&(iopex -> abSrb), 0, sizeof iopex -> abSrb); // set up abort scsi request block iopex -> abSrb.Function = SRB_FUNCTION_ABORT_COMMAND; iopex -> abSrb.SrbStatus = SRB_STATUS_PENDING; iopex -> abSrb.PathId = lu -> PathId; iopex -> abSrb.TargetId = lu -> TargetId; iopex -> abSrb.Lun = lu -> Lun; iopex -> abSrb.NextSrb = &(iopex -> Srb); iopex -> abSrb.OriginalRequest = iopex; if (devex -> hba_busy) { // see if HBA is ready to accept a request iopex -> abnext = NULL; // if not, queue this for delivery later *(devex -> abort_qt) = iopex; devex -> abort_qt = &(iopex -> abnext); } else { devex -> hba_busy = 1; // if so, mark it busy so it won't be given another request // queued = (*(devex -> HwStartIo)) (devex -> HwDeviceExtension, &(iopex -> abSrb)); asm ("pushl %3\n" // pass the abort request to the HBA miniport driver " pushl %2\n" " movl %1,%%eax\n" " call *%%eax\n" : "=a" (queued) : "m" (devex -> _HwStartIo), "g" (devex -> HwDeviceExtension), "g" (&(iopex -> abSrb)) : "eax", "ecx", "edx", "cc", "memory"); if (!queued) oz_knl_printk ("scsiport: %s abortreq request did not queue\n", devex -> devname); } } } /************************************************************************/ /* */ /* Translate SRB status value to corresponding OZONE status */ /* */ /************************************************************************/ static uLong srbsts2oz (Iopex *iopex) { Long ozsts; uLong srbsts; srbsts = iopex -> Srb.SrbStatus; ozsts = -OZ_BUGCHECK; if (srbsts < sizeof srbsts2oztable / sizeof srbsts2oztable[0]) ozsts = srbsts2oztable[srbsts]; if (ozsts < 0) { ozsts = -ozsts; oz_knl_printk ("scsiport srbsts2oz: %s srb status %u -> oz status %u\n", iopex -> devex -> devname, srbsts, ozsts); } return (ozsts); } /************************************************************************/ /* */ /* We're back in requestor's process space at softint level, finish */ /* */ /************************************************************************/ static void finishup (void *iopexv, int finok, uLong *status_r) { Iopex *iopex; uLong sts; iopex = iopexv; ///oz_knl_printk ("scsiport finishup*: iopex %p -> SrbExtension %p, bufremapped %u\n", iopex, iopex -> Srb.SrbExtension, iopex -> bufremapped); /* Now that we're below interrupt level, free off the Srb extension block used by the HBA driver */ if (iopex -> Srb.SrbExtension != NULL) OZ_KNL_NPPFREE (iopex -> Srb.SrbExtension); /* Free off any re-mapped data buffer pte's */ if (iopex -> bufremapped != 0) oz_knl_spte_free (iopex -> bufremapped, OZ_HW_VADDRTOVPAGE (iopex -> mappedbuffer)); if (finok) { /* Maybe caller wants scsi status byte */ if (iopex -> doiopp.status != NULL) { sts = oz_knl_section_uput (iopex -> procmode, sizeof *(iopex -> doiopp.status), &(iopex -> Srb.ScsiStatus), iopex -> doiopp.status); if (sts != OZ_SUCCESS) { oz_knl_printk ("scsiport: error %u writing scsi status byte to %p\n", sts, iopex -> doiopp.status); if (*status_r == OZ_SUCCESS) *status_r = sts; } } /* Maybe caller wants actual data transfer length */ if (iopex -> doiopp.datarlen != NULL) { sts = oz_knl_section_uput (iopex -> procmode, sizeof *(iopex -> doiopp.datarlen), &(iopex -> Srb.DataTransferLength), iopex -> doiopp.datarlen); if (sts != OZ_SUCCESS) { oz_knl_printk ("scsiport: error %u writing data length to %p\n", sts, iopex -> doiopp.datarlen); if (*status_r == OZ_SUCCESS) *status_r = sts; } } } } /************************************************************************/ /* */ /* Read from I/O port routines */ /* */ /* Input: */ /* */ /* Port < FAKE_IO_BASE_VA : virtual memory address the device's */ /* register is mapped at */ /* else : use in/out instruction to access */ /* register Port-FAKE_IO_BASE_VA */ /* Buffer = pointer to array to read bytes into */ /* Count = number of elements in Buffer to fill from Port */ /* */ /* Output: */ /* */ /* *Buffer = filled in with data */ /* */ /* Note: */ /* */ /* The same 'Port' is read repeatedly into successive elements of */ /* array pointed to by Buffer */ /* */ /************************************************************************/ // VOID ScsiPortReadBufferUchar (PUchar Port, PUchar Buffer, ULONG Count) // VOID ScsiPortReadBufferUlong (PUlong Port, PUlong Buffer, ULONG Count) // VOID ScsiPortReadBufferUshort (PUshort Port, PUshort Buffer, ULONG Count) #define ScsiPortReadBuffer(dtype,letter,areg) \ asm (".globl ScsiPortReadPortBuffer" #dtype ",ScsiPortReadRegisterBuffer" #dtype "\n" \ "ScsiPortReadPortBuffer" #dtype ":\n" \ "ScsiPortReadRegisterBuffer" #dtype ":\n" \ " pushl %ebp\n" \ " pushl $0\n" \ " leal 4(%esp),%ebp\n" \ " pushl %edi\n" \ " pushl %esi\n" \ " pushl %ebx\n" \ " movl 16(%ebp),%ecx\n" \ " movl 12(%ebp),%edi\n" \ " movl 8(%ebp),%edx\n" \ " jecxz ScsiPortReadBuffer" #dtype "_rtn\n" \ " cld\n" \ " cmpl $FAKE_IO_BASE_VA,%edx\n" \ " jc ScsiPortReadBuffer" #dtype "_mem\n" \ " rep\n" \ " ins" #letter "\n" \ " jmp ScsiPortReadBuffer" #dtype "_rtn\n" \ "ScsiPortReadBuffer" #dtype "_mem:\n" \ " movl %edx,%esi\n" \ "ScsiPortReadBuffer" #dtype "_mem_loop:\n" \ " xorl %eax,%eax\n" \ " cpuid\n" \ " mov" #letter " (%esi),%" #areg "\n" \ " stos" #letter "\n" \ " decl 16(%ebp)\n" \ " jne ScsiPortReadBuffer" #dtype "_mem_loop\n" \ "ScsiPortReadBuffer" #dtype "_rtn:\n" \ " popl %ebx\n" \ " popl %esi\n" \ " popl %edi\n" \ " leave\n" \ " ret $12"); ScsiPortReadBuffer(Uchar,b,al) ScsiPortReadBuffer(Ulong,l,eax) ScsiPortReadBuffer(Ushort,w,ax) /* These just read the 'Port' register once */ // UCHAR ScsiPortReadUchar (PUCHAR Port) // ULONG ScsiPortReadUlong (PULONG Port) // USHORT ScsiPortReadUshort (PUSHORT Port) #define ScsiPortRead(dtype,letter,areg) \ asm (".globl ScsiPortReadPort" #dtype ",ScsiPortReadRegister" #dtype "\n" \ "ScsiPortReadPort" #dtype ":\n" \ "ScsiPortReadRegister" #dtype ":\n" \ " pushl %ebp\n" \ " pushl $0\n" \ " leal 4(%esp),%ebp\n" \ " pushl %ebx\n" \ " movl 8(%ebp),%edx\n" \ " cmpl $FAKE_IO_BASE_VA,%edx\n" \ " jc ScsiPortRead" #dtype "_mem\n" \ " xorl %eax,%eax\n" \ " in" #letter " %dx,%" #areg "\n" \ " jmp ScsiPortRead" #dtype "_rtn\n" \ "ScsiPortRead" #dtype "_mem:\n" \ " xorl %eax,%eax\n" \ " cpuid\n" \ " movl 8(%ebp),%edx\n" \ " xorl %eax,%eax\n" \ " mov" #letter " (%edx),%" #areg "\n" \ "ScsiPortRead" #dtype "_rtn:\n" \ " popl %ebx\n" \ " leave\n" \ " ret $4"); ScsiPortRead(Uchar,b,al) ScsiPortRead(Ulong,l,eax) ScsiPortRead(Ushort,w,ax) /************************************************************************/ /* */ /* Sets bus-type-specific information for the specified bus and slot */ /* This implementation only writes to PCI Configuration space */ /* */ /* Input: */ /* */ /* DeviceExtension = points to HBA device extension data */ /* BusDataType = PCIConfiguration */ /* SystemIoBusNumber = specifies which of the busses it is connected to /* SlotNumber = specifies the slot to be accessed */ /* Buffer = specifies where the data comes from */ /* Offset = offset in config space to start writing at */ /* Length = specifies the amount of data to be stored */ /* */ /* Output: */ /* */ /* ScsiPortSetBusDataByOffset = 0 : parameters not valid */ /* else : number of bytes written */ /* */ /* http://www.osr.com/ddk/k301_2yb6.htm */ /* */ /************************************************************************/ asm (".globl ScsiPortSetBusDataByOffset\n" "ScsiPortSetBusDataByOffset:\n" " pushl %ebp\n" " pushl $0\n" " leal 4(%esp),%ebp\n" " pushl 32(%ebp)\n" " pushl 28(%ebp)\n" " pushl 24(%ebp)\n" " pushl 20(%ebp)\n" " pushl 16(%ebp)\n" " pushl 12(%ebp)\n" " pushl 8(%ebp)\n" " call _ScsiPortGetBusData\n" " leave\n" " ret $28"); static ULONG _ScsiPortSetBusDataByOffset (PVOID DeviceExtension, ULONG BusDataType, ULONG SystemIoBusNumber, ULONG SlotNumber, PVOID Buffer, ULONG Offset, ULONG Length) { OZ_Dev_pci_conf_p pciconfp; PCI_SLOT_NUMBER pcislotnumber; uLong i; /* We only do PCI bus configuration space */ if (BusDataType != PCIConfiguration) return (0); /* Make sure PCIBIOS is present, if not, return 0 */ if (!oz_dev_pci_present ()) return (0); /* Config space is at most 256 bytes int */ if (Offset >= 256) return (0); if (Length + Offset > 256) Length = 256 - Offset; /* Set up an OZONE-style PCI address block */ pcislotnumber.u.AsULONG = SlotNumber; memset (&pciconfp, 0, sizeof pciconfp); pciconfp.pcibus = SystemIoBusNumber; pciconfp.pcidev = pcislotnumber.u.bits.DeviceNumber; pciconfp.pcifunc = pcislotnumber.u.bits.FunctionNumber; /* Write config space from Buffer */ i = Length; switch ((i | Offset) & 3) { case 0: { while (i > 3) { oz_dev_pci_conf_outl (*((uLong *)Buffer), &pciconfp, Offset); i -= 4; Offset += 4; (OZ_Pointer)Buffer += 4; } break; } case 2: { while (i > 1) { oz_dev_pci_conf_outw (*((uWord *)Buffer), &pciconfp, Offset); i -= 2; Offset += 2; (OZ_Pointer)Buffer += 2; } break; } case 1: case 3: { while (i > 0) { oz_dev_pci_conf_outb (*((uByte *)Buffer), &pciconfp, Offset); i --; Offset ++; (OZ_Pointer)Buffer ++; } break; } } return (Length); } /************************************************************************/ /* */ /* Delay execution the indicated number of microseconds */ /* */ /************************************************************************/ // VOID ScsiPortStallExecution (ULONG Delay) asm (".globl ScsiPortStallExecution\n" "ScsiPortStallExecution:\n" " pushl %ebp\n" " pushl $0\n" " leal 4(%esp),%ebp\n" " pushl $0\n" " pushl $0\n" " pushl 8(%ebp)\n" " call oz_hw_stl_microwait\n" " leave\n" " ret $4"); /************************************************************************/ /* */ /* Write to I/O port routines */ /* */ /* Input: */ /* */ /* Port < FAKE_IO_BASE_VA : virtual memory address the device's */ /* register is mapped at */ /* else : use in/out instruction to access */ /* register Port-FAKE_IO_BASE_VA */ /* *Buffer = pointer to array to get bytes from */ /* Count = number of elements in Buffer to write to Port */ /* */ /* Output: */ /* */ /* port written with data from buffer */ /* */ /* Note: */ /* */ /* The same 'Port' is written repeatedly from successive elements */ /* of array pointed to by Buffer */ /* */ /************************************************************************/ // VOID ScsiPortWriteBufferUbyte (PUbyte Port, PUbyte Buffer, ULONG Count) // VOID ScsiPortWriteBufferUlong (PUlong Port, PUlong Buffer, ULONG Count) // VOID ScsiPortWriteBufferUshort (PUshort Port, PUshort Buffer, ULONG Count) #define ScsiPortWriteBuffer(dtype,letter,areg) \ asm (".globl ScsiPortWritePortBuffer" #dtype ",ScsiPortWriteRegisterBuffer" #dtype "\n" \ "ScsiPortWritePortBuffer" #dtype ":\n" \ "ScsiPortWriteRegisterBuffer" #dtype ":\n" \ " pushl %ebp\n" \ " pushl $0\n" \ " leal 4(%esp),%ebp\n" \ " pushl %edi\n" \ " pushl %esi\n" \ " pushl %ebx\n" \ " movl 16(%ebp),%ecx\n" \ " movl 12(%ebp),%esi\n" \ " movl 8(%ebp),%edx\n" \ " jecxz ScsiPortWriteBuffer" #dtype "_rtn\n" \ " cld\n" \ " cmpl $FAKE_IO_BASE_VA,%edx\n" \ " jc ScsiPortWriteBuffer" #dtype "_mem\n" \ " rep\n" \ " outs" #letter "\n" \ " jmp ScsiPortWriteBuffer" #dtype "_rtn\n" \ "ScsiPortWriteBuffer" #dtype "_mem:\n" \ " movl %edx,%edi\n" \ "ScsiPortWriteBuffer" #dtype "_mem_loop:\n" \ " lods" #letter "\n" \ " mov" #letter " %" #areg ",(%edi)\n" \ " xorl %eax,%eax\n" \ " cpuid\n" \ " decl 16(%ebp)\n" \ " jne ScsiPortWriteBuffer" #dtype "_mem_loop\n" \ "ScsiPortWriteBuffer" #dtype "_rtn:\n" \ " popl %ebx\n" \ " popl %esi\n" \ " popl %edi\n" \ " leave\n" \ " ret $12"); ScsiPortWriteBuffer(Uchar,b,al) ScsiPortWriteBuffer(Ulong,l,eax) ScsiPortWriteBuffer(Ushort,w,ax) /* These routines just write one datum to the port */ // VOID ScsiPortWriteUchar (PUCHAR Port, UCHAR Value) // VOID ScsiPortWriteUlong (PULONG Port, ULONG Value) // VOID ScsiPortWriteUshort (PUSHORT Port, USHORT Value) #define ScsiPortWrite(dtype,letter,areg) \ asm (".globl ScsiPortWritePort" #dtype ",ScsiPortWriteRegister" #dtype "\n" \ "ScsiPortWritePort" #dtype ":\n" \ "ScsiPortWriteRegister" #dtype ":\n" \ " pushl %ebp\n" \ " pushl $0\n" \ " leal 4(%esp),%ebp\n" \ " pushl %ebx\n" \ " movl 8(%ebp),%edx\n" \ " movl 12(%ebp),%eax\n" \ " cmpl $FAKE_IO_BASE_VA,%edx\n" \ " jc ScsiPortWrite" #dtype "_mem\n" \ " out" #letter " %" #areg ",%dx\n" \ " jmp ScsiPortWrite" #dtype "_rtn\n" \ "ScsiPortWrite" #dtype "_mem:\n" \ " mov" #letter " %" #areg ",(%edx)\n" \ " xorl %eax,%eax\n" \ " cpuid\n" \ "ScsiPortWrite" #dtype "_rtn:\n" \ " popl %ebx\n" \ " leave\n" \ " ret $8"); ScsiPortWrite(Uchar,b,al) ScsiPortWrite(Ulong,l,eax) ScsiPortWrite(Ushort,w,ax)