n ~ DQDRIVER.BCK3 DQDRIVER.BCKUBACK SRH:[FREEWAREV5.DQDRIVER]*.* BEAVIS"hoffman password"::DQDRIVER.BCK/SAVE/LOG/VER HOFFMAN Ҋ]ӞV7.2 _XDELTA:: AXP72R001 ~ v&*[FREEWAREV5.DQDRIVER]DECW$CDPLAYER.C;1+,.Z/ 4ZZL-ѡ0123KPWO[56XW7xAB89GHJ$/*P *******************************************************************************P * *P * Copyright 2000 Compaq Computer Corporation *P * *P * COMPAQ Registered in U.S. Patent and Trademark Office. *P * *P * Confidential computer software. Valid license from Compaq or *P * authorized sublicensor required for possession, use or copying. *P * Consistent with FAR 12.211 and 12.212, Commercial Computer Software, *P * Computer Software Documentation, and Technical Data for Commercial *P * Items are licensed to the U.S. Government under vendor's standard *P * commercial license. *P * *P ******************************************************************************* *//* * DECW$CDPLAYER.C *K * This program acts as a control panel for a SCSI audio CD player. ItG * creates a workstation window with buttons for playing, pausing,F * selecting tracks, etc. The program uses the DECwindows toolkitB * routines to create and manage the display and its widgets. * * * Note: *N * This program's user interface was created with VUIT. If the interfaceI * is modified with vuit, then this source will have to be modified. *K * THIS APPLICATION IS NOT MOTIF STYLE GUIDE COMPLAINT, and should notG * be used as an example of how to program a style guide compliantK * application. It should be used ONLY as an example of accessing the * SCSI device. *C * The logicial name, DECW$CD_PLAYER is used to identify the CD-ROM0 * drive containing an audio disk. For example: *I * If SCSI CD player is at ID 2 on the second SCSI bus, the logical  * DECW$CD_PLAYER is defined: */ * $ DEFINE DECW$CD_PLAYER DKB200: *O * If IDE/ADAPI CD player is the master on the first IDE bus, the logical  * DECW$CD_PLAYER is defined: *- * $ DEFINE DECW$CD_PLAYER DQA0: * * Written by: *C * Rob Lembree and Jeff Otterson of VMS DECwindows EngineeringD * based on an earlier work by J. Mc., J. K. - VMS engineering., * ATAPI support provided by Paul A. Jacobi. * *; * The commands to build this program on a VAX system are: * * $ CC DECW$CDPLAYER* * $ LINK DECW$CDPLAYER,SYS$INPUT/OPT( * SYS$SHARE:DECW$XLIBSHR.EXE/SHARE) * SYS$SHARE:DECW$XMLIBSHR.EXE/SHARE* * SYS$SHARE:DECW$DXMLIBSHR.EXE/SHARE * $ UIL/MOTIF DECW$CDPLAYER *> * The commands to build this program on an Alpha system are: *@ * $ CC/STANDARD=VAXC/NOWARN/DEFINE=__NEW_STARLET DECW$CDPLAYER% * $ LINK DECW$CDPLAYER,SYS$INPUT/OPT* * SYS$SHARE:DECW$MRMLIBSHR12/SHARE% * SYS$SHARE:DECW$DXMLIBSHR12/SHARE$ * SYS$SHARE:DECW$XMLIBSHR12/SHARE$ * SYS$SHARE:DECW$XTLIBSHRR5/SHARE! * SYS$SHARE:DECW$XLIBSHR/SHARE *+ * $ LINK/debug DECW$CDPLAYER,SYS$INPUT/OPT% * SYS$SHARE:DECW$XLIBSHR.EXE/SHARE& * SYS$SHARE:DECW$XMLIBSHR.EXE/SHARE' * SYS$SHARE:DECW$DXMLIBSHR.EXE/SHARE * * $ UIL/MOTIF DECW$CDPLAYER *@ * You need PHY_IO and DIAGNOSE privileges to run this program.A * If you do not have these privileges, you can link the programC * with the "/NOTRACEBACK/NODEBUG" qualifiers and have your system@ * manager INSTALL the program with the appropriate privileges. * */ C#include /* For printf and so on. */#include C#include /* Motif Toolkit and MRM */#include #include #include #include #include #include 9/* The vuit generated application include file. */@/* If the user does not specify a directory for the file */K/* then the vaxc$include logical needs to be defined to point to the */,/* directory of the include file. */#include "DECW$CDPLAYER.H"/*! * Forward routine declarations  */void exit_handler(); void itoa();void reverse();void alloc_cd_channel();void dealloc_cd_channel();int pause_cd();int resume_cd();void Do_CD_Setup_Work();void setup_timer();void house_keeping_timer();void update_display();void update_timer(); void WidgetCreateCallback();void ButtonPressCallback();"static void set_icons_on_shell();void set_icons();#static Pixmap fetch_icon_literal();$static char * get_icon_index_name();static void set_icon_pixmap();"static void set_iconify_pixmap();"static Boolean xui_winmgr = False;#define FALSE 0#define TRUE 1/* L * Define the constants used to interface to the SCSI generic class driver. */#define GOOD_SCSI_STATUS 0#define OPCODE 0#define FLAGS 1 #define COMMAND_ADDRESS 2 #define COMMAND_LENGTH 3#define DATA_ADDRESS 4#define DATA_LENGTH 5#define PAD_LENGTH 6#define PHASE_TIMEOUT 7#define DISCONNECT_TIMEOUT 8#define FLAGS_READ 1#define FLAGS_DISCONNECT 2#define GK_EFN 1#define SCSI_STATUS_MASK 0X3E#define PAUSE_OPCODE 0X4B#define RESUME_OPCODE 0X4B#define STOP_OPCODE 1 #define READ_SUBCHAN_OPCODE 0X42#define READ_TOC_OPCODE 0X43#define PLAY_TRACK_OPCODE 0X48#define PAUSE_CMD_LEN 10#define RESUME_CMD_LEN 10#define STOP_CMD_LEN 6#define READ_SUBCHAN_CMD_LEN 10#define READ_TOC_CMD_LEN 10#define PLAY_TRACK_CMD_LEN 10#define READ_TOC_DATA_LEN 4 #define READ_SUBCHAN_DATA_LEN 48##define ATAPI_READ_TOC_DATA_LEN 804G#define ATAPI_READ_TOC_DATA_LEN_LSB (ATAPI_READ_TOC_DATA_LEN & 0x00FF) C#define ATAPI_READ_TOC_DATA_LEN_MSB (ATAPI_READ_TOC_DATA_LEN >> 8) "#define ATAPI_PLAY_MSF_OPCODE 0x47#define ATAPI_STOP_OPCODE 0x1B&#define ATAPI_READ_SUBCHAN_OPCODE 0X42#define ATAPI_CMD_LEN 12#define STOPPED 0#define PLAYING 1#define PAUSED 2 globalvalue IO$_DIAGNOSE;/* 4 * Variables for saving the state of the CD player. */ 'int total_tracks=0; int saved_total_tracks=0;int current_track=0;int saved_current_track=0;int skip_next_poll=FALSE;int client_data = 1;int cd_mode = 0;int last_cd_mode = -1;int powerflag = -1;int playseconds = 0;Widget *TrackPlayingWidget, *StopButtonWidget, *PlayPauseButtonWidget, *ForwButtonWidget, *BackButtonWidget, *TrackSelectSliderWidget, *StoppedIndicatorWidget, *PausedIndicatorWidget, *PlayingIndicatorWidget, *TotalTracksWidget, *TimerLEDWidget; typedef char unsigned UCHAR; !#pragma __member_alignment __save#pragma __nomember_alignment"typedef struct _track_descriptor { UCHAR session; union _adr_ctrl_u { UCHAR adr_ctrl; struct { unsigned int adr:4; unsigned int ctrl:4; } adr_ctrl_bits; } adr_ctrl_u; UCHAR tno; UCHAR point; UCHAR absolute_time[3]; UCHAR zero; UCHAR start_minute; UCHAR start_second;  UCHAR start_frame;} TRACK_DESCRIPTOR;#define POINT_FIRST_TRACK 0xA0#define POINT_LAST_TRACK 0xA1#define POINT_LEAD_OUT 0xA2#define MAX_TRACK 99#define CTRL_DIGITAL_DATA 4typedef struct _atapi_toc { UCHAR length_msb; UCHAR length_lsb; UCHAR first_session; UCHAR last_session;< TRACK_DESCRIPTOR track_descriptor[MAX_TRACK]; } ATAPI_TOC;$#pragma __member_alignment __restoretypedef struct _track { UCHAR start_minute; UCHAR start_second; UCHAR start_frame; UCHAR end_minute; UCHAR end_second; UCHAR end_frame;} TRACK;TRACK track[MAX_TRACK]; /* * OWN STORAGE ! * A * Variables used to interface to the SCSI generic class driver. */short gk_chan;int  gk_device_desc[2], gk_iosb[2], gk_desc[15]; int atapi;UCHARB pause_command [PAUSE_CMD_LEN] = {PAUSE_OPCODE,0,0,0,0,0,0,0,0,0},E resume_command [RESUME_CMD_LEN] = {RESUME_OPCODE,0,0,0,0,0,0,0,1,0},7 stop_command [STOP_CMD_LEN] = {STOP_OPCODE,0,0,0,0,0},n read_subchan_command [READ_SUBCHAN_CMD_LEN] = {READ_SUBCHAN_OPCODE,0,0x40,1,0,0,0,0,READ_SUBCHAN_DATA_LEN,0},[ read_toc_command [READ_TOC_CMD_LEN] = {READ_TOC_OPCODE,0,0,0,0,0,0,0,READ_TOC_DATA_LEN,0},Q play_track_command [PLAY_TRACK_CMD_LEN] = {PLAY_TRACK_OPCODE,0,0,0,0,1,0,0,1,0}, toc_data[READ_TOC_DATA_LEN],& subchan_data [READ_SUBCHAN_DATA_LEN], * atapi_play_msf_command [ATAPI_CMD_LEN] = : {ATAPI_PLAY_MSF_OPCODE,0,0,0,0,0,0,0,0,0,0,0},* atapi_read_toc_command [ATAPI_CMD_LEN] = ) {READ_TOC_OPCODE,0,2,0,0,0,0,) ATAPI_READ_TOC_DATA_LEN_MSB,0 ATAPI_READ_TOC_DATA_LEN_LSB,0,0,0},) atapi_toc_data[ATAPI_READ_TOC_DATA_LEN],Q atapi_start_command [ATAPI_CMD_LEN] = {ATAPI_STOP_OPCODE,0,0,0,3,0,0,0,0,0,0,0},P atapi_stop_command [ATAPI_CMD_LEN] = {ATAPI_STOP_OPCODE,0,0,0,0,0,0,0,0,0,0,0},m atapi_read_subchan_command [ATAPI_CMD_LEN] = {READ_SUBCHAN_OPCODE,2,0x40,1,0,0,0,0,READ_SUBCHAN_DATA_LEN,0}, # gk_device [] = {"DECW$CD_PLAYER"}; /* * Global data *// 5static MrmType class_id; /* Place to keep class ID*/Astatic MrmType *dummy_class; /* and class variable. */Estatic char *db_filename_vec[] = /* Mrm.hierachy file list. */ { "DECW$CDPLAYER.UID"  };static int db_filename_num =F (sizeof db_filename_vec / sizeof db_filename_vec [0]);=char *vuit_dummy_ident_value = "VUIT dummy identifier value";int i;#define hash_table_limit 500struct HASH_TABLE_STRUCT { char *widget_name; Widget id;' } hash_table[hash_table_limit + 1];/*@ * Names and addresses of callback routines to register with Mrm */$static MrmRegisterArg reglist [] = {8{"WidgetCreateCallback", (caddr_t)WidgetCreateCallback},7{"ButtonPressCallback", (caddr_t)ButtonPressCallback}};>static int reglist_num = (sizeof reglist / sizeof reglist[0]);/*H * Names and addresses of uil identifiers (if any) to register with Mrm.N * These identifiers are registered with a dummy value to allow the generated  * code to run without error.K * You can avoid the registration of these identifiers by simplying editingG * this template file (vuit_main_template_c) and removing the following * special format comments: * ***VUIT ident registration*** * ***VUIT identlist size***" * ***VUIT register identifiers***K * You can provide your own registration of identifiers by calling your ownM * routine in response to a callback (such as the MrmNcreateCallback for yourI * application's main window), or by modifying this template to call your * own registration routine. */ /*G * OS transfer point. The main routine does all the one-time setup and * then calls XtAppMainLoop. */unsigned int main(argc, argv)J unsigned int argc; /* Command line argument count. */L char *argv[]; /* Pointers to command line args. */{ Arg arglist[2]; int n;? MrmInitialize(); /* Initialize MRM before initializing */< /* the X Toolkit. */> DXmInitialize(); /* Initialize additional DEC widgets */ /* M * If we had user-defined widgets, we would register them with Mrm.here.  */ /* F * Initialize the X Toolkit. We get back a top level shell widget. */ XtToolkitInitialize();/ app_context = XtCreateApplicationContext();P display = XtOpenDisplay(app_context, NULL, "DECW$CDPLAYER", "DECW$CDPLAYER",2 NULL, 0, &argc, argv); if (display == NULL)  {> fprintf(stderr, "%s: Can't open display\n", argv[0]); exit(1); } n = 0;: XtSetArg(arglist[n], XmNallowShellResize, True); n++;6 toplevel_widget = XtAppCreateShell(argv[0], NULL, C applicationShellWidgetClass,< display, arglist, n); /* K * Open the UID files (the output of the UIL compiler) in the hierarchy */@ if (MrmOpenHierarchy(db_filename_num, /* Number of files. */E db_filename_vec, /* Array of file names. */F NULL, /* Default OS extenstion. */J &s_MrmHierarchy) /* Pointer to returned MRM ID */ !=MrmSUCCESS)( s_error("can't open hierarchy");(MrmRegisterNames (reglist, reglist_num); n = 0;9 XtSetArg(arglist[n], XtNiconName, "CD Player"); n++;6 XtSetArg(arglist[n], XtNtitle, "CD Player"); n++;+ XtSetValues(toplevel_widget,arglist,n);!VUIT_Manage("MainBulletinBoard"); /* M * Realize the top level widget. All managed children now become visible */B XtAddEventHandler(toplevel_widget, StructureNotifyMask, False,0 set_icons_on_shell, None);% XtRealizeWidget(toplevel_widget); Do_CD_Setup_Work(); /* F * Sit around forever waiting to process X-events. We never leaveK * XtAppMainLoop. From here on, we only execute our callback routines.  6 */ XtAppMainLoop(app_context);} /* * All errors are fatal. */void s_error(problem_string) char *problem_string;{# printf("%s\n", problem_string); exit(0);} void VUIT_Manage(widget_name) char *widget_name;{ Widget id; Window pop_window; XWindowChanges values;% if (HashLookup(widget_name, &id)) if (XtIsManaged(id)) {) pop_window = XtWindow(XtParent(id));9 values.x = values.y = values.width = values.height =. values.border_width = values.sibling = NULL; values.stack_mode = Above;A XConfigureWindow(display, pop_window, CWStackMode, &values); } else XtManageChild(id); else {C MrmFetchWidget(s_MrmHierarchy, widget_name, toplevel_widget, &id,  &class_id); XtManageChild(id); HashRegister(widget_name, id); }} void VUIT_Unmanage(widget_name) char *widget_name;{ Widget id;% if (HashLookup(widget_name, &id)) XtUnmanageChild(id);} "int HashRegister (widget_name, id) char *widget_name; Widget id; { int ndx;; for (ndx = HashFunction(widget_name, hash_table_limit);* ((hash_table[ndx].widget_name != NULL) && (ndx < hash_table_limit)); ndx++);, if (hash_table[ndx].widget_name != NULL) for (ndx = 0;) hash_table[ndx].widget_name != NULL; ndx++); if (ndx > hash_table_limit) return (FALSE); else {D hash_table[ndx].widget_name = XtCalloc(1, strlen(widget_name) + 1);2 strcpy(hash_table[ndx].widget_name, widget_name); hash_table[ndx].id = id; return (TRUE); }} int HashLookup (name, id) char *name; Widget *id;{ int ndx;4 for (ndx = HashFunction(name, hash_table_limit);* ((hash_table[ndx].widget_name != NULL) && (ndx <= hash_table_limit)); ndx++)4 if (strcmp(name, hash_table[ndx].widget_name) == 0) { *id = hash_table[ndx].id; return (TRUE); } if (ndx > hash_table_limit) for (ndx = 0;. ((hash_table[ndx].widget_name != NULL) && (ndx <= hash_table_limit)); ndx++) {8 if (strcmp(name, hash_table[ndx].widget_name) == 0) { *id = hash_table[ndx].id; return (TRUE); } } return (FALSE);} int HashFunction (name, max) char *name; int max;{;#define HashVecSize 20 /* plenty for 31 character names */ typedef union {B short int intname[HashVecSize]; /* name as vector of ints */@ char charname[2*HashVecSize]; /* name as vector of chars */ } HashName;*) HashName locname; /* aligned name */ ' int namelen; /* length of name */ 5 int namelim; /* length limit (fullword size) */r0 int namextra; /* limit factor remainder */) int code = 0; /* hash code value */ int ndx; /* loop index */ /*2 * Copy the name into the local aligned union.P * Process the name as a vector of integers, with some remaining characters.H * The string is copied into a local union in order to force correct4 * alignment for alignment-sensitive processors. */e$ strcpy (locname.charname, name);( namelen = strlen (locname.charname);. namelim = namelen >> 1; /* divide by 2 */, namextra = namelen & 1; /* remainder */ /*K * XOR each integer part of the name together, followed by the trailing  * 0/1 character */c' for ( ndx=0 ; ndx 0 ) 6 code = code ^ ((locname.intname[ndx])&0x00FF); return (code&0x7FFF) % max;*}* **void WidgetCreateCallback (w, tag, reason) Widget w; int *tag;unsigned long *reason;{a switch (*tag)e { case StopButtonWidgetID:! StopButtonWidget = w;g break;% case PlayPauseButtonWidgetID:w& PlayPauseButtonWidget = w; break; case ForwButtonWidgetID:! ForwButtonWidget = w;s break; case BackButtonWidgetID:! BackButtonWidget = w;t break;' case TrackSelectSliderWidgetID:A( TrackSelectSliderWidget = w; break;& case StoppedIndicatorWidgetID:' StoppedIndicatorWidget = w;  break;% case PausedIndicatorWidgetID:e& PausedIndicatorWidget = w; break;& case PlayingIndicatorWidgetID:' PlayingIndicatorWidget = w;. break;! case TotalTracksWidgetID:2" TotalTracksWidget = w; break;" case TrackPlayingWidgetID:# TrackPlayingWidget = w; break; case TimerLEDWidgetID: TimerLEDWidget = w;D break; default:8 printf("bad widget value, widget is 0x%lx, \F tag is 0x%lx, reason is 0x%lx\n", w, *tag, *reason); }}  )void ButtonPressCallback (w, tag, reason) Widget w; int *tag;unsigned long *reason;{* int last_mode; XmScaleCallbackStruct *s;y last_mode = cd_mode; switch (*tag)* {  case StopButtonWidgetID:T) if (cd_mode != STOPPED) stop_unit();A update_display(); break;X case PlayPauseButtonWidgetID: switch (last_mode) {Y case STOPPED:l. play_track(current_track); break; case PLAYING:R pause_cd();U break; case PAUSED: resume_cd(); break; default:@ fprintf(stderr,"Something is not right!\n"); }W update_display(); break;A case ForwButtonWidgetID:/ if (current_track < total_tracks) {S, play_track(++current_track);! update_display();d }E break;o case BackButtonWidgetID:y$ if (current_track > 1) {, play_track(--current_track);! update_display();r }e break; case TrackSelectSliderWidgetID: s = reason;c% current_track = s->value;F& play_track(current_track); update_display(); break;. case OffButtonWidgetID:t) if (cd_mode != STOPPED) stop_unit();< update_display(); powerflag = 2; break; default:c1 printf("bad widget value, widget is 0x%lx, \o6 tag is 0x%lx, reason is 0x%lx\n", w, *tag, *reason); }i}t void Do_CD_Setup_Work()t{ /*& * Go assign a channel to the device. */n alloc_cd_channel(); stop_unit ();e update_display(); setup_timer ();}"void itoa (int n, char s[])l/*: * This routine changes an integer to an ascii character. */e{( int t=0;l do {a s[t++] = n % 10 + '0'; } while ((n /= 10) > 0);; s[t] = '\0';d reverse(s);}_void reverse(char s[])/*" * This is a subroutine for ITOA. */d{p int t, j; char c;* for (t=0, j=strlen(s)-1; t < j; t++, j--) { c = s[t]; s[t] = s[j];b s[j] = c; }}  tvoid exit_handler ()/*H * Callback routine for exit button. Stop the CD from playing, deassign( * the channel to the player, and exit. */p{1 stop_unit (); /* spin down the unit */e2 dealloc_cd_channel(); /* blow away the channel */ sys$exit(1); /* go away */}* evoid alloc_cd_channel ()/* r4 * This routine assigns a channel to the CD player. */D{S int status; int return_length; unsigned int devchar2; ILE3 item_list[2];( gk_device_desc[0] = strlen (gk_device);# gk_device_desc[1] = &gk_device[0];n: status = sys$assign (&gk_device_desc[0], &gk_chan, 0, 0); if (!(status & 1)) {_C fprintf (stderr,"Unable to assign channel to %s", &gk_device[0]);n sys$exit (status); }1 item_list[0].ile3$w_code = DVI$_DEVCHAR2;n6 item_list[0].ile3$w_length = sizeof(devchar2);1 item_list[0].ile3$ps_bufaddr = &devchar2;_: item_list[0].ile3$ps_retlen_addr = &return_length;% item_list[1].ile3$w_code = 0;n' item_list[1].ile3$w_length = 0;KM status = sys$getdvi(0, 0, &gk_device_desc[0], item_list, 0, 0, 0, 0);d" if (devchar2 & DEV$M_SCSI) atapi = FALSE; else atapi = TRUE;}dvoid dealloc_cd_channel ()/*6 * This routine deassigns a channel to the CD player  */D{x int status; status = sys$dassgn (gk_chan);_ if (!(status & 1)) {2G fprintf (stderr,"Unable to deassign channel from %s", &gk_device[0]);f sys$exit (status); }}void setup_timer () /*J * This routine sets up the time to run one second from the current time. */c{0? /* set to 998 ms, takes about 2 ms to reschedule... */ rH XtAppAddTimeOut(app_context, 998, &house_keeping_timer, &client_data);}t/*L * This is the timer routine which runs once a second. It reads the current3 * state of the CD player and updates the display.S */tvoid house_keeping_timer ()n/* tH * this routine is acitivated every 1 second. It will re-schedule the H * timer, then increment the number of seconds that this track has been * playinga */d{, setup_timer ();g if (cd_mode == PLAYING) {  update_timer();e playseconds++; }e' if (!skip_next_poll) get_status ();r skip_next_poll = FALSE;_}cvoid update_timer()A6/* this routine updates the fake track playing time */{l int minutes,seconds; char tstr[100];a XmString timestring; Arg argList[2];  minutes = playseconds / 60;u) seconds = playseconds - minutes * 60;C. sprintf(tstr,"%02d:%02d",minutes,seconds);, timestring = XmStringCreateSimple(tstr);3 XtSetArg(argList[0],XmNlabelString,timestring);n* XtSetValues(TimerLEDWidget,argList,1); XmStringFree(timestring);D}Tint pause_cd ()_/*9 * This routine sends a pause command to the CD player. _ */{  cd_mode=PAUSED;= return execute_command (pause_command, PAUSE_CMD_LEN, 0, 0); }tint resume_cd ()/*: * This routine sends a resume command to the CD player.  */{a cd_mode=PLAYING;e? return execute_command (resume_command, RESUME_CMD_LEN, 0, 0);} int read_toc() /*G * This routine reads the table of contents on the CD to determine the  * total number of tracks. */C{r int track, status, length;*O c total_tracks = 0; if (atapi) { if (!execute_command (atapi_read_toc_command, ATAPI_CMD_LEN, atapi_toc_data, ATAPI_READ_TOC_DATA_LEN)) return FALSE;, atapi_parse_toc(atapi_toc_data); } else {q if (!execute_command (read_toc_command, READ_TOC_CMD_LEN, toc_data, READ_TOC_DATA_LEN)) return FALSE;r5 total_tracks = toc_data[3] - toc_data[2] + 1;H },5/* printf("This CD has %d tracks\n",total_tracks); */n return TRUE;_:} void atapi_parse_toc(){R int toc_length;T int number_toc_descriptors;O ATAPI_TOC *atapi_toc; int i; int cnt; int first_track, last_track;; UCHAR lead_out_minute, lead_out_second, lead_out_frame;_O atapi_toc = &atapi_toc_data; G toc_length = (atapi_toc->length_msb << 16) | atapi_toc->length_lsb;BI number_toc_descriptors = (toc_length - 2) / sizeof(TRACK_DESCRIPTOR);[ cnt = 0; last_track = MAX_TRACK;,/ for (i=0; i< number_toc_descriptors; i++) {N7 switch (atapi_toc->track_descriptor[i].point) {a# case POINT_FIRST_TRACK:RP first_track = (int) atapi_toc->track_descriptor[i].start_minute;. break; " case POINT_LAST_TRACK:O last_track = (int) atapi_toc->track_descriptor[i].start_minute;y break; case POINT_LEAD_OUT:N lead_out_minute = atapi_toc->track_descriptor[i].start_minute;N lead_out_second = atapi_toc->track_descriptor[i].start_second;L lead_out_frame = atapi_toc->track_descriptor[i].start_frame; break; default:A if ((atapi_toc->track_descriptor[i].point > 0) &&dC (atapi_toc->track_descriptor[i].point < 100) &&rj (atapi_toc->track_descriptor[i].adr_ctrl_u.adr_ctrl_bits.ctrl < CTRL_DIGITAL_DATA)) {Z track[cnt].start_minute = atapi_toc->track_descriptor[i].start_minute;Z track[cnt].start_second = atapi_toc->track_descriptor[i].start_second;X track[cnt].start_frame = atapi_toc->track_descriptor[i].start_frame; cnt++; }i break; }e H /* If this was the last track, then break out of the loop. ThisN * assumes only a single-session disk. For multi-session audio, more G * code is needed here to parse the second session descriptors.v */w% if (cnt == last_track) break;* }i total_tracks = cnt;*' for (i=0; i< total_tracks-1; i++) { 6 track[i].end_minute = track[i+1].start_minute;6 track[i].end_second = track[i+1].start_second;4 track[i].end_frame = track[i+1].start_frame; }7 track[total_tracks-1].end_minute = lead_ou7t_minute;e7 track[total_tracks-1].end_second = lead_out_second;5 track[total_tracks-1].end_frame = lead_out_frame;t}tint play_track (track_num)/*5 * This routine plays the specified track on the CD.n */dchar track_num; /{ int status;(  if (atapi) {D atapi_play_msf_command [3] = track[track_num - 1].start_minute;D atapi_play_msf_command [4] = track[track_num - 1].start_second;C atapi_play_msf_command [5] = track[track_num - 1].start_frame;aE atapi_play_msf_command [6] = track[total_tracks - 1].end_minute; E atapi_play_msf_command [7] = track[total_tracks - 1].end_second;iD atapi_play_msf_command [8] = track[total_tracks - 1].end_frame;L status = execute_command (atapi_play_msf_command, ATAPI_CMD_LEN, 0, 0); } else {( play_track_command [4] = track_num;2 play_track_command [7] = total_tracks; M status = execute_command (play_track_command, PLAY_TRACK_CMD_LEN, 0, 0); }  skip_next_poll = TRUE;; cd_mode = PLAYING;}" return status; } int stop_unit()o/*8 * This routine sends a stop command the the CD player. */ {  cd_mode = STOPPED; n if (atapi) {M return execute_command (atapi_stop_command, ATAPI_CMD_LEN, 0, 0);O } else {F return execute_command (stop_command, STOP_CMD_LEN, 0, 0); }c}dDint execute_command (command_addr, command_len, data_addr, data_len)/*F * This routine sends the specified command to the CD player. It doesD * so by filling in the generic class driver descriptor and issuing$ * an IO$_DIAGNOSE QIO to GKDRIVER. */ 5int *command_addr, command_len, *data_addr, data_len;h{; int i, status;m char scsi_status; gk_desc[OPCODE] = 1; 0 gk_desc[FLAGS] = FLAGS_READ + FLAGS_DISCONNECT;) gk_desc[COMMAND_ADDRESS] = command_addr;D' gk_desc[COMMAND_LENGTH] = command_len;l# gk_desc[DATA_ADDRESS] = data_addr;M! gk_desc[DATA_LENGTH] = data_len;  gk_desc[PAD_LENGTH] = 0;w gk_desc[PHASE_TIMEOUT] = 0;" gk_desc[DISCONNECT_TIMEOUT] = 60;A for (i=9; i<15; i++) gk_desc[i] = 0; /* Clear reserved fields */ /*K * Issue the QIO to send the inquiry command and receive the inquiry data.e */B status = sys$qiow (GK_EFN, gk_chan, IO$_DIAGNOSE, gk_iosb, 0, 0, % &gk_desc[0], 15*4, 0, 0, 0, 0);e/*- * Check the various returned status values.  */a& if (!(status & 1)) sys$exit (status);7 if (!(gk_iosb[0] & 1)) sys$exit (gk_iosb[0] & 0xffff);a5 scsi_status = (gk_iosb[1] >> 24) & SCSI_STATUS_MASK;e2 if (scsi_status == GOOD_SCSI_STATUS) return TRUE; return FALSE;}int get_status ()w/*K * This routine reads the current status of the CD player to determine thewJ * total number of tracks, whether the player is playing, and, if so, theM * current track number being played. If any of this information has changedvJ * since the last time the screen was updated, then the screen is updated * again. */={L int a;v int read_toc_status; int read_subchan_status;% read_toc_status = read_toc();e if (atapi) read_subchan_status = execute_command(atapi_read_subchan_command, ATAPI_CMD_LEN, subchan_data, READ_SUBCHAN_DATA_LEN); else read_subchan_status = execute_command(read_subchan_command, READ_SUBCHAN_CMD_LEN, subchan_data, READ_SUBCHAN_DATA_LEN);p d0 if (!read_toc_status || !read_subchanIJ~ DQDRIVER.BCKѡ&[FREEWAREV5.DQDRIVER]DECW$CDPLAYER.C;1Zaw>_status) { cd_mode = STOPPED; current_track = 0; total_tracks = 0; } else {  if (atapi)i current_track = atapi_find_track(subchan_data[9],subchan_data[10], subchan_data[11]);  else& current_track = subchan_data[6];. if (subchan_data[1] == 0x13) stop_unit ();  }- if (current_track != saved_current_track ||  cd_mode != last_cd_mode ||8 total_tracks != saved_total_tracks) update_display (); if (powerflag > -1) {t powerflag--;' if (powerflag < 1) exit(1);h }d}i =int atapi_find_track(UCHAR minute, UCHAR second, UCHAR frame) {h int i;1 int current_second, start_second, end_second;x; current_second = (((int) minute * 60) + (int) second);d& for (i=0; i < total_tracks; i++) { _^ start_second = (((int)track[i].start_minute * (int) 60) + (int)track[i].start_second);X end_second = (((int)track[i].end_minute * (int) 60) + (int)track[i].end_seco@nd);P if ((current_second >= start_second) && (current_second < end_second)) { return(i+1); }s }a return(1);}i mvoid update_display()={s Arg argList[3];n$ int min=1, max=total_tracks;5 char total_tracks_char[4], track_num_char[4];r% XmString track_str, total_track_str; /*F * If a new CD has been inserted which contains a different number ofG * tracks, update the slider to indicate the total number of tracks on  * the new CD.H */c r1 if (saved_total_tracks != total_tracks) { 2 saved_total_tracks = total_tracks;7 itoa (total_tracks, total_tracks_char);*J total_track_str = XmStringCreateSimple(total_tracks_char);D XtSetArg(argList[0],XmNlabelString,total_track_str);9 XtSetValues(TotalTracksWidget,argList,1);o XmStringFree(total_track_str);( if (total_tracks == 0) { min=0; max=1; }e6 XtSetArg(argList[0], XmNminimum, min);6 XtSetArg(argList[1], XmNmaximum, max);= XtSetArg(argList[2],XmNvalue, current_track);/A XtSetValues(TrackSelectSliderWidget, argList, 3);R }e/*F * If the player is now playing a different track than before, update * the slider to indicate this. */ 3 if (saved_current_track != current_track) {]4 saved_current_track = current_track; playseconds = 0; update_timer();x3 itoa(current_track,track_num_char);)A track_str = XmStringCreateSimple(track_num_char);(> XtSetArg(argList[0],XmNlabelString,track_str);: XtSetValues(TrackPlayingWidget,argList,1);( XmStringFree(track_str);= XtSetArg(argList[0],XmNvalue, current_track);d? XtSetValues(TrackSelectSliderWidget,argList,1); }B& if (cd_mode != last_cd_mode) { switch (cd_mode) { case STOPPED:r> if (!XtIsManaged(StoppedIndicatorWidget)) > XtManageChild(StoppedIndicatorWidget);< if (XtIsManaged(PlayingIndicatorWidget))@ XtUnmanageChild(PlayingIndicatorWidget);; if (XtIsManaged(PausedIndicatorWidget)) ? XtUnmanageChild(PausedIndicatorWidget);  break; case PLAYING: = if (XtIsManaged(StoppedIndicatorWidget)) @ XtUnmanageChild(StoppedIndicatorWidget);= if (!XtIsManaged(PlayingIndicatorWidget)) > XtManageChild(PlayingIndicatorWidget);; if (XtIsManaged(PausedIndicatorWidget))n? XtUnmanageChild(PausedIndicatorWidget);  break; case PAUSED:= if (XtIsManaged(StoppedIndicatorWidget)) @ XtUnmanageChild(StoppedIndicatorWidget);< if (XtIsManaged(PlayingIndicatorWidget))@ XtUnmanageChild(PlayingIndicatorWidget);< if (!XtIsManaged(PausedIndicatorWidget))= XtManageChild(PausedIndicatorWidget);  break; default:D fprintf(stderr,"Unknown CD_MODE: %d\n",cd_mode); break; } # last_cd_mode = cd_mode;e }}  /*@ * Callback routine which sets the icon pixmaps for Reparenting * window managers. */t9static void set_icons_on_shell( shell, user_data, event )bWidget shell; caddr_t user_data; /* unused */XEvent *event; {a XIconSize *size_list;y int num_sizes; + Display *dpy = XtDisplay( shell ); 9 Window root_window = XDefaultRootWindow( dpy ); E XReparentEvent *reparent = (XReparentEvent *) &event->xreparent;( if ( event->type != ReparentNotify ) return;0 /* Ignore reparents back to the root window. */ * if ( reparent->parent == root_window ) return;G /* Only take the performance hit to see which window manager is now C * running when we get the reparent event. XmIsMotifWMRunning if ( icon_size == 17 ) /* Don't fetch icon twice */& iconify_pixmap = icon_pixmap; else if ( icon_size > 17 ) {! if (iconify_pixmap == 0)kG iconify_pixmap = fetch_icon_literal( hierarchy_id, dpy, scr, c/ "ICON_PIXMAP_17X17" );t } }l( /* Set the icon pixmap on the shell. */e if ( icon_pixmap )) set_icon_pixmap(shell, icon_pixmap);.9 /* Set the iconify pixmap for the XUI window manager b */o if ( iconify_pixmap )n1 set_iconify_pixmap( shell, iconify_pixmap ); }; /* end of set_icons */ /*? * Fetches a bitmap from a UID hierachy to be used as an icon.p */gAPixmap fetch_icon_literal( hierarchy_id, dpy, scr, index_string )eMrmHierarchy hierarchy_id;Display *dpy; Screen *scr;/String index_string;e{d int status; Pixmap pixmap_rtn;# Dimension width, height; y if ( MrmFetchBitmapLiteral(  hierarchy_id, 6 index_string, /* name of icon literal */1 scr, /* screen pointer */g dpy, &pixmap_rtn, &width, $ &height ) != MrmSUCCESS ) return (Pixmap) 0; return (Pixmap) pixmap_rtn;n$}; /* end of fetch_icon_literal */ /*F * Finds the largest icon supported by the window manager and returns/ * a string which represents that icon in UIL.u */sHstatic char * get_icon_index_name( dpy, root_index_name, icon_size_rtn, 5 supported_icon_sizes, num_supported_sizes )0Display *dpy;char *root_index_name;unsigned int *icon_size_rtn;h$char **supported_icon_sizes;!int num_supported_sizes;k{t XIconSize *size_list;  int num_sizes;  int cursize;  int i;c char *icon_index = NULL; int icon_size;( char *icon_size_ptr;% Boolean found_icon_size = False;t/ *icon_size_rtn = 0; /* Initial value */ 8 if ( XGetIconSizes( dpy, XDefaultRootWindow( dpy ), + &size_list, &num_sizes ) )d {T> /* Find the largest icon supported by the window manager. */c cursize = 0;t& for ( i = 1; i < num_sizes; i++ ) {H if ( ( size_list[i].max_width >= size_list[cursize].max_width )M && ( size_list[i].max_height >= size_list[cursize].max_height ) )  cursize = i; }- /* Find the largest icon we can support.  *// if ( ( size_list[cursize].max_width > 0 ) ;3 || ( size_list[cursize].max_height > 0 ) )  {4 for ( i = 0; i < num_supported_sizes; i++ ) {6 icon_size = atoi( supported_icon_sizes[i] );< if ( ( icon_size <= size_list[cursize].max_width )A && ( icon_size <= size_list[cursize].max_height ) )- {T6 icon_size_ptr = supported_icon_sizes[i];% found_icon_size = True;e break; }( } } XFree( size_list ); } = /* Default to something that both XUI and Mwm can except.p */a if ( !found_icon_size ); {  icon_size = 32; icon_size_ptr = "32"; }e! /* Build the icon index namea * N * format: root_index_name + "_" + icon_size_ptr + "X" + icon_size_ptr */ D icon_index = (char *) XtMalloc( strlen( root_index_name ) +0 sizeof( "_" ) +9 ( 2 * sizeof( icon_size_ptr ) ) +;1 1 ); /* for \0 char */_* strcpy( icon_index, root_index_name ); strcat( icon_index, "_" );( strcat( icon_index, icon_size_ptr ); strcat( icon_index, "X" );( strcat( icon_index, icon_size_ptr );. *icon_size_rtn = (unsigned int) icon_size; return( icon_index );t$}; /* end of get_icon_index_name */ +static void set_icon_pixmap( shell, pixmap)tWidget shell;Pixmap pixmap;]{a if ( XtWindow(shell) != 0 )r {] XWMHints *wmhints = NULL;k+ Display *dpy = XtDisplay( shell ); ) Window win = XtWindow( shell );cD /* HACK: Under Motif 1.1 changing iconPixmap will cause the window C * to go to its intial state. This appears to be a side-effect s? * of ICCCM-compliant behavior, and doing XtSetValues in the*= * X toolkit, so we need to call Xlib directly instead of m * setting XtNiconPixmap.  */t# wmhints = XGetWMHints( dpy, win );s if ( wmhints != NULL )  { if (xui_winmgr)0 wmhints->flags &= ~StateHint; /* clear it */ elset0 wmhints->flags |= StateHint; /* reset it */& wmhints->flags |= IconPixmapHint;# wmhints->icon_pixmap = pixmap;s& XSetWMHints( dpy, win, wmhints ); XFree( wmhints ); }  elser {9 wmhints = (XWMHints *)XtCalloc(1, sizeof(XWMHints));o" wmhints->flags &= ~StateHint;& wmhints->flags |= IconPixmapHint;# wmhints->icon_pixmap = pixmap;& XSetWMHints( dpy, win, wmhints ); XtFree( wmhints );i } }  else {h Arg arglist[1];- XtSetArg(arglist[0], XmNiconPixmap, pixmap); XtSetValues(shell, arglist, 1); } } /* end of set_icon_pixmap */ a/*A * Sets the iconify pixmap in the DEC_WM_HINTS property for the a * given shell widget.  */[7static void set_iconify_pixmap( shell, iconify_pixmap )  Widget shell;u Pixmap iconify_pixmap;{i"typedef unsigned long int INT32;typedef struct { INT32 value_mask;_ INT32 iconify_pixmap;s INT32 icon_box_x;a INT32 icon_box_y;r INT32 tiled; INT32 sticky; INT32 no_iconify_button; INT32 no_lower_button; INT32 no_resize_button;_-} internalDECWmHintsRec, *internalDECWmHints;nU#define WmNumDECWmHintsElements ( sizeof( internalDECWmHintsRec ) / sizeof( INT32 ) )C! internalDECWmHintsRec prop;_+ static Atom decwmhints = None;t2 prop.value_mask = DECWmIconifyPixmapMask; prop.icon_box_x = -1; prop.icon_box_y = -1;! prop.tiled = False;_! prop.sticky = False;($ prop.no_iconify_button = False;$ prop.no_lower_button = False;$ prop.no_resize_button = False;- prop.iconify_pixmap = iconify_pixmap;P if (decwmhints == None)dL decwmhints = XmInternAtom( XtDisplay( shell ), "DEC_WM_HINTS", False ); if (decwmhints != None)eI XChangeProperty( XtDisplay( shell ), XtWindow( shell ), decwmhints, c1 decwmhints, 32, PropModeReplace, NC (unsigned char *) &prop, WmNumDECWmHintsElements );r#}; /* end of set_iconify_pixmap */a(*[FREEWAREV5.DQDRIVER]DECW$CDPLAYER.EXE;1+,".6/ 461-ѡ0123 KPWO156D%<1B7LB89GHJ@L h( 1B0@p#0U 1B DECW$CDPLAYERV1.0A12-01$  $  $* $ $J!$( @ DECW$MRMLIBSHR12_001@p DECW$DXMLIBSHR12_001< DECW$XMLIBSHR12_001< DECW$XTLIBSHRR5_001<peDECW$XLIBSHR_0018 DECC$SHR_0014& LIBOTS_001@ 0oxSYS$PUBLIC_VECTORS_00100@P0 0 0$ '0 0 @ Fp==PMF[@`0@ `$ @0"P0X 0" ( 0008 @`( p 0 `   08"`4 &0"@@$ 0"0$ &00 P @p0p0 @ , Fp` 0 #P(  P00"P( 0 # (0 @!  @00X !8 &0, 00@0 0( .00"  0 `0"0 0" $  000 `  0 p0 00   0  L @ 0  < `0 0  0 @$ @7`0``@ `@%s 7550321732_XXDECW$CDPLAYER.UIDVUIT dummy identifier valueWidgetCreateCallbackButtonPressCallbackDECW$CDPLAYERDECW$CDPLAYER%s: Can't open display can't open hierarchyCD PlayerCD PlayerMainBulletinBoardSomething is not right! Unable to assign channel to %sUnable to deassign channel from %s%02d:%02dUnknown CD_MODE: %d ICON_PIXMAPICON_PIXMAP_17X17DEC_WM_HINTSbad widget value, widget is 0x%lx, tag is 0x%lx, reason is 0x%lx bad widget value, widget is 0x%lx, tag is 0x%lx, reason is 0x%lx  (GB@0C$DECW$CD_PLAYERKHB@0CKp# ? ~4G0^8^@cG("#G$" "0[8b=/ yZk$=(b# @BbG4G7XZkcG0]8]@P#k#G ^0[(^0~8~@GG#G8bBJZkBbG /tZk@BHbGZZkBbGC~GGGGG/pZk ""PBXb C $"tG/FZkpBxb4G4GWZk (b BStb 4G=" =G4GGlZkBHbpBbxbx$"GGuZkBb8D 8"@$"TGHZkpBxbG4GWZk"Bb"!TG1vZk "BbX d"hD" ="]TGtG=eZkx"b#8<@Bb`b?&GGGzZkPBXb4GnfZkb#@ӈb#@Ӑb#d@b#J@ӰBb4G@ZkG ](]0}8@4GP#k#GTG^0[@"~^GG8bHZk (bG]]G4G #pk#~0^(>"8^@~HGGG{#@(=&PF$AP@J@Ԣ֢F`4`5@0B G8b}"G wxZkB(b4G}vZk h"PBXbA`G(}"G/wZkB(b4GkvZk(=Gb#@G0]8]@}HP#k#G~>"^^ GG{#/\@Bb4GuZkG]] 0#k#~^> ^?"(~0GGG{#@0"P@0 @"" @x0@ "0@ТW@G Gy0@9#`90@? @"@GBb@@G4G@Zk B(b0@4GTGZk=BbGGTG@Zk}]4GbG] ](}0@#k#~^^>?" ~(GGG{#;@¦GW@7 (B0b A"G@70 @Zk"/=4G !  G(B0b 8"G 60ChZk "G/G]] }(0#k/֢=4G/ٲ//#G8^"0[TG0>G~@^H~PGGG8bZkPBXb"4G<@Zk7H0DG ="/q.Q"1"SrJfJ3pJ0B/C BsDBC.JFvD B0=(b"wD~ZkG8]@]H}P`#kGQGwABm\\B#~C^Gk(ۦ6G] #k/ 7G] #k/88G] #k/0;9G] #k/H[:G] #k/ G] #k/0G] #k/;1G] #k/4G] #k/P[2G] #k/x{3G] #k/8s{G]`"GG #k8s{G`"Gk GQ;G7@BԢBf\B#~C^^GGk///b#@G]b#] #/`8FXF8b#@`"Bb1"TGDZk`b#@b#@///"0b#! B"0 B~@/b#=B 01Bt@///b#k@Ӿ//b#@b#@"TGG]] #k/`s{G"Gk#~^^GG{#o@xb#@Ӏb#@G]b#] # [GG/3C.F0 @VJrNvdJ4CFBUB/5BG0"/wJ F>!BG{#.UJ>#4G^([~ ^~GGG0{~Zk 1@@@..0`@ 1@J `@JuJTJB wAJF>.//VJ! F>G]]} 0#k#~^^GG{#7@b#/X@bG]]4G4G #=k//(#8^4G[~@^H~PGG# {b~Zk(@B"HbGGGG}|ZkP"Bb0"0 (B1"tG2CZkB0b4G|Zk0B8b$ " (]" G]GG}"GGG~Zk }"4GcHDG8]@]H}P`#k(;#~^^ GG.J! (B0J0bJ4G|Zk"B"tG bB1"BZk bG]] 4G0#wJ_F2.Q#J8F2>G2,A"H7D2<G]]} (0#k;0#!~^^GG+ 0JJP;B@pB`@p"  @."/3@ .J!/X KA/a/!K@"2JF6J#XKF[ xKF[G04HFвGuq`JF aJ(0bTGPs2MJ3gJrFAF0GGb#'@0"4G01G]] #k ;#G!~^^GG (/0GG]b#]GG #/ #4GtGG $ 4G @`~^^~ GG#0B! B_Hb3.3J*`(bXB4G1&J`bGhB&JGG G(zZk8B@bG4GzZk#8B@b4G 0v HzZkcG]cH]gD@} 0#k.V J6F#~^^ ~(GG{#Z_X"G08TG B0bGb#_= ("pBXb b#s . . " 4/`J #J "4K<@ ".! J#8b# Br.R"rJxBF@U_(" Bp"c!1R`D0DD2FD@b#@(b (b4G !1B4G# G]] }(0#/kG]] }(0#k/{PBG pBPB4G 0B1q @ !Bih .".#J.JVBJ/TBvBtB3#KTBVBBB B B"Fm3/ " /0@.7KK /3#WBJVBwBvB 3#WBKVBBB B BFU 3/ "/"7K .K/WBVBKwB 3#vBJWBVBBB B B0@F=3/"/".7KK/3#WBJVBwBvB3#WBKVBBB B B0@F$@B0@s" QB? AB/".3#K3.J.9JXCTBxCtB"JXCTBCB 0B B0@B4F 0@s" AB?4Gk;#~4G@^H^P~XGG4~{#0{!00F4 >"_pBxb"4GOZkBb="4G8tG_ZkB8b4G=uZkc4G`04="B0]p"4ݢbW"2 $]tG =B(tG,ݲ^ZkbPHb(b##PAF)@0_="b#_pBxb"4GOZk0=""x4GtG=Bb^ZkB0b4GtZkc"pBb=="4G}tG^Zkb(b0@B#Hb4G1D P 8 D1@X D% Т֢PF$PJnZk@BHb4G01P F $PJnZk 01P F $PJX`Bhb4GoZkR`"BCbtG1"ZkH(@BHb4G01P F $PJ|nZk01P F $PJ#`Bhb4GoZk(01/P F $PJ`Bhb4GoZk@BHb4G01P F $PJPnZk @BHb4G01P F $PJBnZkc`G@]H]P}X`#k0#~^ ^(^0~8GGG1 F Xp#X!/Bb4GC/eZk=(BC0b4G/F!G2D _iZk"B=b@]"8}"G OZkB  b4GQZkG8b# @G ](]0}8@#kP#~X^`^h~px޴>^GG#GPD=R@FC@X@GX@XCX@BbŠGH]4GPdZkPBXbGG@]"8}"GNZk8=@G := @@4G "/V?BvBBV Cu 6 yC3G0`B aB"_Q?Bg1BBP@ G8!B b4G}`Zk@= G'@! @  @0A)! A_)4GP}B@4Gb[QZk¤0BP8b("4GJyZkBbp@4Gh wZkBbDDG&"GTGxZk B(b&"GTGc^Zk B(bGGTG]^Zk B(b&"GTGW^Zk B(bGGTGQ^Zk %'%0B8b*E?DGTG  JZk%0B8bG EDGTG JZk GGpb# H]D E@GBb4GwZk4%8BE ?E  /H]7BA@Df"Gpb#@%a DC`M#`1J XC`#cXs`#C`*BbGGTGtMZk4EG _E@QFHT@FHGG (G@BtGHbFusZkBbG4GPZk%@BHb4GGTGwZk @BHb D G @ GGGtG\sZkBbG4GwZk %BbG!00="4=4GtG\ZkE4G >@ ] $(GX#XpBxb&"GtGWuZkG DCXC `# àXƠ`##//`)E`Bhb= G>4G>GGGOZkGX]/`]h}pxݤ=]#k#G~G ^G(G #" [({"GfEZk8DG ](G0#kG ](0#k2w~ DQDRIVER.BCK"ѡ([FREEWAREV5.DQDRIVER]DECW$CDPLAYER.EXE;16Y !TX(IP//p0 :  `0 00@0 p p@P`p`@@`P @DECW$MRMLIBSHR12@DECW$DXMLIBSHR12@DECW$XMLIBSHR12@DECW$XTLIBSHRR5@ DECW$XLIBSHR@DECC$SHR@LIBOTS@SYS$PUBLIC_VECTORS- DECW$CDPLAYERCompaq C V6.2-005TRANSFER$BREAK$GOmain } } }  }  } } } )} )} (} (} )}u )} 0}) 0}) 5}$ 7} :} :} ;} :} >} P}$  eK}  6}v$,4 8@@s_error@' W} W} W} [}p` VUIT_Manage\ ^} ^} e} i} i} i} j} i} @ VUIT_Unmanage y} y} ~} (`@` HashRegister@| } } } } } } } } } } } } }l d@ HashLookup } } } } } } } } } } } } } } }  }  } }  `   HashFunctionK } } }  } } } } t!WidgetCreateCallback } } } } }# }  } } } ~ ~ ~ ~  ~) ~ ~ ~ ~4  8 X x       ButtonPressCallback  ~ ~ ~ ~ ~ ~ ~ ~. #~- &~$ ,~ )~  7~ =~ D~ N~  $ ` Do_CD_Setup_Work`  S~ ^~| P itoa - i~ f~ i~ i~ k~0 Hreverse0 \ o~ o~ o~ y~ w~ w~ w~ z~ w~ w~X    ` exit_handler  ~ ~ `@alloc_cd_channel@z ~ ~  ~  ~ ~ ~  ~ ~ ~ ~ ~ ~t T0p(dealloc_cd_channelp8 ~ ~ ~ ~ ~ ~ 0 setup_timer0 ~0 `house_keeping_timer` ~ ~|  update_timerh ~ ~ ~ ~ ~ ~  ~ ~ ~ ~ ~$ pause_cd ~ ~ ~0 resume_cd ~ ~ ~0 read_toc 8   ~ ~  ~T atapi_parse_toc     ,     * / 1 1 1 / 2 2 2 2 3 ' ! A D D D D C C C4 C I J I J (  play_track Y Q Q Z Z Z Z ] Z [ Z Z ] [ [G [ Z Z b b f @x stop_unitA q p k q k t t  pPexecute_commandPq  x      x     p@@ get_status                @  @atapi_find_trackD                                 K          Pupdate_display                           _   ( (          set_icons_on_shell o @ 9 @  A D  D I Q R Y  ! !@ set_icons! f f j i wi ku ݀  wi  wz w|    v   v v  v  x             } $ % . - 5 4 4 < ; =  F F  g & h t < " h" wWv h"t"|"8"p,#8#l# ###l T% vT%X&( & v& & 'P (fetch_icon_literal (3       À ŀL((( get_icon_index_nameset_icon_pixmapset_iconify_pixmapp__main8 $ SYS$IODEF AMAC V4.0-00b6(@ H@  @` P@0 0pPpp@`PR< `00$b)(*[FREEWAREV5.DQDRIVER]DECW$CDPLAYER.UID;1+,.-/ 4-)-ѡ0123KPWO)567B89GHJHro|URM 1.2AXMotif Uil CompilerPLAYER.UIL;V2.0-00019-APR-2000 15:51:29.26DECW$CDPLAYER$'D|P  L   hTx`0hH 0XMainBulletinBoardH I 4MainBulletinBoard$c   p`T@0DisplayBulletinBoardOffButtonTrackSelectLabelMainTitleLabelTrackSelectSliderStopButtonPlayPauseButtonForwardspaceButtonBackspaceButtonH DI 0BackspaceButtonK  hf pButtonPressCallback BackButtonWidgetIDH I8v  ) H $ΏH DI 4ForwardspaceButtonK,  fhf ButtButtonPressCallback ForwButtonWidgetIDH I|8v  ) ÏǟϿߟϏLJÃH |I 0PlayPauseButtonK|  hff ButtonPressCallback%PlayPauseButtonWidgetIDH i8v   )   ~    x    ~   H I ,StopButtonK  |hff ButtonPressCallback StopButtonWidgetIDWidH IX8v  )  H xPIx 4TrackSelectSlider hf X pWidgetCreateCallback'TrackSelectSliderWidgetID  (  d  hfac< PelleLabButtonPressCallback'TrackSelectSliderWidgetIDardsH I 0MainTitleLabelea | l[ckTrrWH *8 ߀& ISO8859-1Compact Disc PlayerH V smkISO8859-1-*-ITC AVANT GARDE GOTHIC-DEMI-O-*--*-240-*-*-*-*-ISO8859-1H  I 4TrackSelectLabelIC- 2 t dH #| ߀ ISO8859-1 Track SelectH V smkISO8859-1-*-ITC AVANT GARDE GOTHIC-DEMI-R-*--*-180-*-*-*-*-ISO8859-1H L I ,OffButtonDEK   |hf getIDButtonPressCallbackOffButtonWidgetID  H  ߀ ISO8859-1OffH | I| 8DisplayBulletinBoard > DhXsD8u$DTimerLEDWidgetStoppedIndicator PausedIndicatorPlayingIndicatorlTotalTracksTotalTracksLabelTrackPlayingSliTrackPlayingLabelceH  I 4TrackPlayingLabelrd t  d  ro|$8 ` 8 D D  0  | @  | l h Dh8 H T ,8D   \ X ( ( hxh hP T| x  >ClassTable>ResourceTableOffButtonWidgetIDICON_PIXMAP_75X75ICON_PIXMAP_50X50ICON_PIXMAP_32X32ICON_PIXMAP_17X17StopButtonWidgetIDPlayPauseButtonWidgetIDForwButtonWidgetIDBackButtonWidgetIDTrackSelectSliderWidgetIDStoppedIndicatorWidgetIDPausedIndicatorWidgetIDPlayingIndicatorWidgetIDTotalTracksWidgetIDTrackPlayingWidgetIDTimerLEDWidgetStoppedIndicatorPausedIndicatorPlayingIndicatorTotalTracksTotalTracksLabelTrackPlayingTrackPlayingLabelDisplayBulletinBoardOffButtonTrackSelectLabelMainTitleLabelTrackSelectSliderStopButtonPlayPauseButtonForwardspaceButtonBackspaceButtonMainBulletinBoardDECW$CDPLAYERro| l  H VsmkISO8859-1-*-ITC AVANT GARDE GOTHIC-DEMI-R-*--*-180-*-*-*-*-ISO8859-1H :߀6 ISO8859-1Track ISO8859-1PlayingH hI 0TrackPlayingPlhfT l0-*-*8859-WidgetCreateCallback"TrackPlayingWidgetID  H ߀ ISO8859-100H I 4TotalTracksLabelf tT  d  WiH 9߀5 ISO8859-1Total ISO8859-1TracksH TI ,TotalTrackshfksP h tTWidgetCreateCallback!TotalTracksWidgetIDetID H ߀ ISO8859-100H xI 4PlayingIndicatorkshfhX pgetCrWidgetCreateCallback&PlayingIndicatorWidgetIDm  H ߀ ISO8859-1PlayingH VsmkISO8859-1-*-ITC AVANT GARDE GOTHIC-DEMI-R-*--*-120-*-*-*-*-ISO8859-1H I 0PausedIndicatorhfMIT l0-*-*8859-WidgetCreateCallbackack%PausedIndicatorWidgetIDetID^n  H 8߀ ISO8859-1PausedH V0smkISO8859-1-*-ITC AVANT GARDE GOTHIC-DEMI-R-*--*-120-*-*-*-*-ISO8859-1H `I 4StoppedIndicatorIC-hf-*X p*-*-*-1dgWidgetCreateCallback&StoppedIndicatorWidgetIDn  LmeH ߀ ISO8859-1StoppedH VsmkISO8859-1-*-ITC AVANT GARDE GOTHIC-DEMI-R-*--*-120-*-*-*-*-ISO8859-1H I l0TimerLEDWidgetThfMIT 0-*-*8859-WidgetCreateCallbackack U H h߀ ISO8859-100:00H  H P H h H  H  H  H  H  H  H  H \( 8v  ) ||H @ 8v  ) UUUUUUUaUUUqUUUqUUUqUUUq`pQuPuwwqutqq}tq芸qEtqq`4phQuTuUUUUH  8v 22 ) UUUUUUUUUUUUUUUUUUUUUUqUUUUUqUUUUUqUUUUUqUUUUUqUUUUUq pꢪEUEUuEq⢫EWUq⢫EWUqEWUqEWUUqEWUUqEWUqEWUq⢫EWUq⢫EpEUEUuꢪUUUUUUUUUUUUro| ro|, H 8v KK ) UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU*UUUUUUUp*UUUUUUUu*UUUUUUUu*UUUUUUUu*UUUUUUUu*UUUUUUUu*UUUUUUUu*UUUUUUUu*UUUUUUUu*\u .UUU]TUUu.UUU]TUUu.?]?u.*]UE]tUu.*]UE]tUu.*]UE]tUu.*]UE]tUu/*]U_tUu**]UUUtUu**]UUUtUu**]UUUtUu ,*]U\tUu.*]UE]tUu.*]UE]tUu.*]UE]tUu.*@]tu.jUUU]TUUu.UUU]TUUuWUUUUUUUUUUUUUUUUUUH  H W,Zfr{->borderWidthfontListheightlabelPixmaplabelStringlabelTypemaximumminimumnoResizeorientationprocessingDirectionresizePolicyshadowThicknessshowValuesubMenuIdvaluewidthxycreateCallbackactivateCallbackvalueChangedCallbackH gDZ4BUXmCreateBulletinBoardXmCreateLabelXmCreatePushButtonXmCreateScale%(*[FREEWAREV5.DQDRIVER]DECW$CDPLAYER.UIL;1+, .6/ 4j60-ѡ0123KPWO156$z7PB89GHJ module DECW$CDPLAYER names = case_sensitive object !***VUIT_Generate_Callback_Tags *** MainBulletinBoard: XmBulletinBoard { arguments { XmNx = 0; XmNy = 1; XmNwidth = 548; XmNheight = 355; XmNborderWidth = 1; XmNresizePolicy = XmRESIZE_NONE; XmNnoResize = true; }; controls { XmPushButton BackspaceButton; XmPushButton ForwardspaceButton; XmPushButton PlayPauseButton; XmPushButton StopButton; XmScale TrackSelectSlider; XmLabel MainTitleLabel; XmLabel TrackSelectLabel; XmPushButton OffButton; XmBulletinBoard DisplayBulletinBoard; }; }; TotalTracks: XmLabel { arguments { XmNx = 438; XmNy = 19; XmNlabelString = compound_string("00"); }; callbacks { MrmNcreateCallback = procedures { WidgetCreateCallback(TotalTracksWidgetID); }; }; }; TrackPlaying: XmLabel { arguments { XmNx = 280; XmNy = 20; XmNlabelString = compound_string("00"); }; callbacks { MrmNcreateCallback = procedures { WidgetCreateCallback(TrackPlayingWidgetID); }; }; }; PlayingIndicator: XmLabel { arguments { XmNx = 278; XmNy = 109; XmNlabelString = compound_string("Playing"); XmNfontList = font_table(font('-*-ITC AVANT GARDE GOTHIC-DEMI-R-*--*-120-*-*-*-*-ISO8859-1')); }; callbacks { MrmNcreateCallback = procedures { WidgetCreateCallback(PlayingIndicatorWidgetID); }; }; }; PausedIndicator: XmLabel { arguments { XmNx = 350; XmNy = 110; XmNlabelString = compound_string("Paused"); XmNfontList = font_table(font('-*-ITC AVANT GARDE GOTHIC-DEMI-R-*--*-120-*-*-*-*-ISO8859-1')); }; callbacks { MrmNcreateCallback = procedures { WidgetCreateCagEN~ DQDRIVER.BCK ѡ([FREEWAREV5.DQDRIVER]DECW$CDPLAYER.UIL;1j6fllback(PausedIndicatorWidgetID); }; }; }; StoppedIndicator: XmLabel { arguments { XmNx = 420; XmNy = 110; XmNlabelString = compound_string("Stopped"); XmNfontList = font_table(font('-*-ITC AVANT GARDE GOTHIC-DEMI-R-*--*-120-*-*-*-*-ISO8859-1')); XmNwidth = 76; }; callbacks { MrmNcreateCallback = procedures { WidgetCreateCallback(StoppedIndicatorWidgetID); }; }; }; BackspaceButton: XmPushButton { arguments { arguments Button_ResourceList; XmNx = 220; XmNy = 215; XmNlabelPixmap = icon(color_table=icon_ct, ' ', ' ', '** * *', '** ** **', '** *** ***', '** **** ****', '** ***** *****', '** ****** ******', '****************', '** ****** ******', '** ***** *****', '** **** ****', '** *** ***', '** ** **', '** * *', ' '); }; callbacks { XmNactivateCallback = procedures { ButtonPressCallback(BackButtonWidgetID); }; }; }; ForwardspaceButton: XmPushButton { arguments { arguments Button_ResourceList; XmNx = 300; XmNy = 215; XmNlabelPixmap = icon(color_table=icon_ct, ' ', ' ', '* * **', '** ** **', '*** *** **',  '**** **** **', '***** ***** **', '****** ****** **', '****************', '****** ****** **', '***** ***** **', '**** **** **', '*** *** **', '** ** **', '* * **', ' '); }; callbacks { XmNactivateCallback = procedures { ButtonPressCallback(ForwButtonWidgetID); }; }; }; PlayPauseButton: XmPushButton { arguments { arguments Button_ResourceList; XmNx = 380; XmNy = 215; XmNlabelPixmap = icon(color_table=icon_ct, ' ', ' ', ' ** ** ** ', ' **** ** ** ', ' ****** ** ** ', ' ** **** ** ** ', ' ** **** ** ** ', ' ** **** ** ** ', ' ** **** ** ** ', ' ** **** ** ** ', ' ** **** ** ** ', ' ** **** ** ** ', ' ****** ** ** ', ' **** ** ** ', ' ** ** ** ', ' '); }; callbacks { XmNactivateCallback = procedures { ButtonPressCallback(PlayPauseButtonWidgetID); }; }; }; StopButton: XmPushButton { arguments { arguments Button_ResourceList; XmNx = 460; XmNy = 215; XmNlabelPixmap = icon(color_table=icon_ct, ' ', ' ', ' **************', ' **************', ' ** **', ' ** **', ' ** **', ' ** **', ' ** **', ' ** **', ' ** **', ' ** **', ' ** **', ' **************', ' **************', ' '); }; callbacks { XmNactivateCallback = procedures { ButtonPressCallback(StopButtonWidgetID); }; }; }; TrackSelectSlider: XmScale { arguments { XmNx = 10; XmNy = 262; XmNborderWidth = 0; XmNwidth = 520; XmNheight = 40; XmNprocessingDirection = XmMAX_ON_RIGHT; XmNorientation = XmHORIZONTAL; XmNmaximum = 100; XmNshowValue = true; XmNvalue = 1; XmNminimum = 0; }; callbacks { MrmNcreateCallback = procedures { WidgetCreateCallback(TrackSelectSliderWidgetID); }; XmNvalueChangedCallback = procedures { ButtonPressCallback(TrackSelectSliderWidgetID); }; }; }; TrackSelectLabel: XmLabel { arguments { XmNx = 10; XmNy = 306; XmNlabelString = compound_string("Track Select"); XmNfontList = font_table(font('-*-ITC AVANT GARDE GOTHIC-DEMI-R-*--*-180-*-*-*-*-ISO8859-1')); }; }; MainTitleLabel: XmLabel { arguments { XmNx = 97; XmNy = 7; XmNlabelString = compound_string("Compact Disc Player"); XmNfontList = font_table(font('-*-ITC AVANT GARDE GOTHIC-DEMI-O-*--*-240-*-*-*-*-ISO8859-1')); XmNwidth = 347; }; }; TrackPlayingLabel: XmLabel { arguments { arguments Label_ResourceList; XmNx = 180; XmNy = 10; XmNlabelString = compound_string("Track",separate=true) &  compound_string("Playing",separate=true); }; }; TotalTracksLabel: XmLabel { arguments { arguments Label_ResourceList; XmNx = 340; XmNy = 10; XmNlabelString = compound_string("Total",separate=true) & compound_string("Tracks",separate=true); }; }; DisplayBulletinBoard: XmBulletinBoard { arguments { XmNx = 10; XmNy = 62; XmNwidth = 520; XmNheight = 143; XmNborderWidth = 1; XmNresizePolicy = XmRESIZE_NONE; XmNshadowThickness = 4; }; controls { XmLabel TrackPlayingLabel; XmLabel TrackPlaying; XmLabel TotalTracksLabel; XmLabel TotalTracks; XmLabel PlayingIndicator; XmLabel PausedIndicator; XmLabel StoppedIndicator; XmLabel TimerLEDWidget; }; }; TimerLEDWidget: XmLabel { arguments { XmNx = 12; XmNy = 85; XmNlabelString = compound_string("00:00"); }; callbacks { MrmNcreateCallback = procedures { WidgetCreateCallback(TimerLEDWidgetID); }; }; }; OffButton: XmPushButton { arguments { arguments PowerButtonResourceList; XmNx = 10; XmNlabelString = compound_string("Off"); XmNy = 215; }; callbacks { XmNactivateCallback = procedures { ButtonPressCallback(OffButtonWidgetID); }; }; }; list Button_ResourceList: arguments { XmNlabelType = XmPIXMAP; XmNheight = 25; XmNwidth = 75; }; list Label_ResourceList: arguments { XmNfontList = font_table(font('-*-ITC AVANT GARDE GOTHIC-DEMI-R-*--*-180-*-*-*-*-ISO8859-1')); }; PowerButtonResourceList: arguments { XmNlabelType = XmSTRING; XmNheight = 25; XmNwidth = 75; }; procedure !***VUIT_Action UserProc PerformWidgetCreateWork *** WidgetCreateCallback(integer); !***VUIT_Action UserProc PerformButtonPressWork *** ButtonPressCallback(integer); grunge; value icon_ct :color_table(background color = ' ',foreground color = '*'); !***VUIT_Generate *** TrackPlayingWidgetID: exported 0; !***VUIT_Generate *** TotalTracksWidgetID: exported 1; !***VUIT_Generate *** PlayingIndicatorWidgetID: exported 2; !***VUIT_Generate *** PausedIndicatorWidgetID: exported 3; !***VUIT_Generate *** StoppedIndicatorWidgetID: exported 4; !***VUIT_Generate *** TrackSelectSliderWidgetID: exported 5; !***VUIT_Generate *** BackButtonWidgetID: exported 6; !***VUIT_Generate *** ForwButtonWidgetID: exported 7; !***VUIT_Generate *** PlayPauseButtonWidgetID: exported 8; !***VUIT_Generate *** StopButtonWidgetID: exported 9; value ICON_PIXMAP_17X17: exported icon(color_table=icon_ct, ' ', ' ** ', ' ** ', ' ** ', ' ** ', ' ***** ****** ', ' ******* ******* ', ' ** ** ** ** ', ' ** ** ** ** ', ' ** ** ** ', ' ** ** ** ', ' ** ** ** ** ', ' ** ** ** ** ', ' ******* ******* ', ' ***** ****** ', ' ', ' '); value ICON_PIXMAP_32X32: exported icon(color_table=icon_ct, '* * * * * * * * * * * * * * * * ', ' * * * * * * * * * * * * ***', '* * * * * * * * * * * * * ** ', ' * * * * * * * * * * * * * ***', '* * * * * * * * * * * * * *** ', ' * * * * * * * * * * * * * ***', '* * * * * * * * * * * * * *** ', ' * * * * * * * * * * * * * ***', '* * * * * * * * * * * * * *** ', ' * * * * * * * * * * * * * ***', '* * * * * * * * * * * * * *** ', ' * * * * * * * * * * * * * ***', '* ** * *** ', ' *** * ***', '* * * * * *** * * * * *** ', ' * * * * * *** * * * * * ***', '* * ***** *** * ******* *** ', ' * ******* *** ********* ***', '* *** * * *** * *** * *** ', ' * *** * ***** *** * * ***', '* *** * ***** * *** * *** ', ' * *** * * * *** * * ***', '* *** * * * * *** * *** ', ' * *** * *** *** * * ***', '* *** ** * ** *** ', ' * ** * *** ** * ***', '* * * * * *** * * * * * *** ', ' * * * * * *** * * * * * ***', '*************** * ************ ', ' *************** * *************', '* * * * * * * * * * * * * * * * ', ' * * * * * * * * * * * * * * * *'); value ICON_PIXMAP_50X50: exported icon(color_table=icon_ct, '* * * * * * * * * * * * * * * * * * * * * * * * * ', ' * * * * * * * * * * * * * * * * * * * * * * * * *', '* * * * * * * * * * * * * * * * * * * * * * * * * ', ' * * * * * * * * * * * * * * * * * * * * * *', '* * * * * * * * * * * * * * * * * * * * * * ', ' * * * * * * * * * * * * * * * * * * * * * *** *', '* * * * * * * * * * * * * * * * * * * * * *** * ', ' * * * * * * * * * * * * * * * * * * * * * *** *', '* * * * * * * * * * * * * * * * * * * * * *** * ', ' * * * * * * * * * * * * * * * * * * * * * *** *', '* * * * * * * * * * * * * * * * * * * * * *** * ', ' * * * * * * * * * * * * * * * * * * * * * *** *', '* * * * * * * * * * * * * * * * * * * * * *** * ', ' * * * * * * * * * * * * * * * * * * * * * *** *', '* * * * * * * * * * * * * * * * * * * * * *** * ', ' * * * * * * * * * * * * * * * * * * * * * *** *', '* * * * * * * * * * * * * * * * * * * * * *** * ', ' * * ** * * * *** *', '* * *** * *** * ', ' * * * * * * * * * *** * * * * * * * * * *** *', '* * * * * * * * * * *** * * * * * * * * *** * ', ' * * * *********** *** * * ********** * *** *', '* * * ************* *** * *********** *** * ', ' * * *** * * * * *** * * *** * * * * *** *', '* * * *** * * * * *** * *** * * * * *** * ', ' * * *** * * * * *** * * *** * * * * *** *', '* * * *** * * * * *** * *** * * * * *** * ', ' * * *** * * * ******* * * *** * * * * *** *', '* * * *** * * * ******* * *** * * * * *** * ', ' * * *** * * * * * * * * * *** * * * * *** *', '* * * *** * * * * * * * * *** * * * * *** * ', ' * * *** * * * * * * * * * *** * * * * *** *', '* * * *** * * * * * * * * *** * * * * *** * ', ' * * *** * * * * * * * * * *** * * * * *** *', '* * * *** * * * * * * *** * * * * *** * ', ' * * *** * * * * * * * *** * * * * *** *', '* * * *** * * * * *** * *** * * * * *** * ', ' * * *** * * * * *** * * *** * * * * *** *', '* * * *** * * * * *** * *** * * * * *** * ', ' * * *** * * * * *** * * *** * * * * *** *', '* * * ** * *** * *** *** * ', ' * * *** *** * * ** * *** *', '* * * * * * * * * * *** * * * * * * * * *** * ', ' * * * * * * * * * *** * * * * * * * * * *** *', '* * ******************* * ****************** * ', ' * * ****************** * * ******************* *', '* * * * * * * * * * * * * * * * * * * * * * * * * ', ' * * * * * * * * * * * * * * * * * * * * * * * * *', '* * * * * * * * * * * * * * * * * * * * * * * * * ', ' * * * * * * * * * * * * * * * * * * * * * * * * *'); value ICON_PIXMAP_75X75: exported icon(color_table=icon_ct, '* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *', ' * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ', '* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *', ' * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ', '* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *', ' * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ', '* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *', ' * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *% * ', '* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *', ' * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ', '* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *', ' * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ', '* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *', ' * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ** * * ', '* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *** * *', ' * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *** * * ', '* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *** * *', ' * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *** * * ', '* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *** * *', ' * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *** * * ', '* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *** * *', ' * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *** * * ', '* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *** * *', ' * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *** * * ', '* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *** * *', ' * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *** * * ', '* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *** * *', ' * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *** * * ', '* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *** * *', ' * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *** * * ', '* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *** * *', ' * * * * * * * * *** * * ', '* * * *** * * * *** * *', ' * * * * * * * * * * * * * * *** * * * * * * * * * * * * * * *** * * ', '* * * * * * * * * * * * * * * * *** * * * * * * * * * * * * * * *** * *', ' * * * * * * * * * * * * * * * *** * * * * * * * * * * * * * * *** * * ', '* * * * * * * * * * * * * * * * *** * * * * * * * * * * * * * * *** * *', ' * * * * ******************* * *** * * * ***************** * *** * * ', '* * * * ********************* * *** * * ****************** * * *** * *', ' * * * * *** * * * * * * * * *** * * * *** * * * * * * * * *** * * ', '* * * * *** * * * * * * * * * *** * * *** * * * * * * * * * *** * *', ' * * * * *** * * * * * * * * *** * * * *** * * * * * * * * *** * * ', '* * * * *** * * * * * * * * * *** * * *** * * * * * * * * * *** * *', ' * * * * *** * * * * * * * * *** * * * *** * * * * * * * * *** * * ', '* * * * *** * * * * * * * * * *** * * *** * * * * * * * * * *** * *', ' * * * * *** * * * * * * * * *** * * * *** * * * * * * * * *** * * ', '* * * * *** * * * * * * * * * *** * * *** * * * * * * * * * *** * *', ' * * * * *** * * * * * * ********* * * * *** * * * * * * * * *** * * ', '* * * * *** * * * * * * * ********* * * *** * * * * * * * * * *** * *', ' * * * * *** * * * * * * * * * * * * * * *** * * * * * * * * *** * * ', '* * * * *** * * * * * * * * * * * * * * *** * * * * * * * * * *** * *', ' * * * * *** * * * * * * * * * * * * * * *** * * * * * * * * *** * * ', '* * * * *** * * * * * * * * * * * * * * *** * * * * * * * * * *** * *', ' * * * * *** * * * * * * * * * * * * * * *** * * * * * * * * *** * * ', '* * * * *** * * * * * * * * * * * * * * *** * * * * * * * * * *** * *', ' * * * * *** * * * * * * * ** * * * *** * * * * * * * * *** * * ', '* * * * *** * * * * * * * *** * * *** * * * * * * * * * *** * *', ' * * * * *** * * * * * * * * *** * * * *** * * * * * * * * *** * * ', '* * * * *** * * * * * * * * * *** * * *** * * * * * * * * * *** * *', ' * * * * *** * * * * * * * * *** * * * *** * * * * * * * * *** * * ', '* * * * *** * * * * * * * * * *** * * *** * * * * * * * * * *.** * *', ' * * * * *** * * * * * * * * *** * * * *** * * * * * * * * *** * * ', '* * * * *** * * * * * * * * * *** * * *** * * * * * * * * * *** * *', ' * * * * *** * * * * * * * * *** * * * *** * * * * * * * * *** * * ', '* * * * *** * * *** * * *** * * *** * *', ' * * * * ** * *** * * * ** * *** * * ', '* * * * * * * * * * * * * * * * *** * * * * * * * * * * * * * * *** * *', ' * * * * * * * * * * * * * * * *** * * * * * * * * * * * * * * *** * * ', '* * * * * * * * * * * * * * * * *** * * * * * * * * * * * * * * *** * *', ' * * * * * * * * * * * * * * * *** * * * * * * * * * * * * * * *** * * ', '* * * ***************************** * * * ***************************** * *', ' * * * *************************** * * * ***************************** * * ', '* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *', ' * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ', '* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *'); OffButtonWidgetID: exported 10; TimerLEDWidgetID: 11; end module; !*[FREEWAREV5.DQDRIVER]DQDRIVER.C;1+,./ 4r-ѡ0123KPWO56Pɱu7:B89GHJ#pragma module DQDRIVER "X-21"M#pragma message disable ignorecallval /* Billions and billions of these... */ #ifdef DEBUGF#define TRACING 4096*4 /* Size of buffer or undefine this if none */V//#define TRACE_DATA_TOO /* If you'd like all the data register read/writes logged */8//#define TRACE_PER_DRIVE /* Per-drive trace buffers */H#define TRACE_COMMON /* One global trace buffer for all four drives */#endif #ifdef DEBUG@#define BREAKPOINTS /* Include or exclude BREAK breakpoints */#endif #ifdef DEBUGE#define EXTRA_STATS /* Include or exclude extra stats in RDSTATS */#endifJ/************************************************************************/ /* */G/* Copyright Digital Equipment Corporation, 1994, 1996, 1998, 1999 */ /* All Rights Reserved. */H/* Unpublished rights reserved under the copyright laws of the United *//* States. */ /* */I/* The software contained on this media is proprietary to and embodies */C/* the confidential technology of Digital Equipment Corporation. */G/* Possession, use, duplication or dissemination of the software and */G/* media is authorized only pursuant to a valid written license from */(/* Digital Equipment Corporation. */ /* */G/* RESTRICTED RIGHTS LEGEND Use, duplication, or disclosure by the */A/* U.S. Government is subject to restrictions as set forth in */I/* Subparagraph (c)(1)(ii) of DFARS 252.227-7013, or in FAR 52.227-19, *//* as applicable. */ /* */J/************************************************************************/J/************************************************************************/ /* *//* Facility: */ /* IDE Disk Driver */ /* *//* Abstract: */A/* This driver controls a standard IDE/ATA/EIDE r/w disk */./* or an ATAPI CD-/DVD-ROM drive. */ /* *//* Author: *///* Benjamin J. Thomas III / May 1994 */ /* */./* Original dedication from Ben Thomas: */ /* */J/* My brother-in-law and nephew were killed in a small plane crash just */D/* off Nantucket island, on June 6, 1994, shortly after I started */E/* writing this driver. This effort is dedicated to the memory of */F/* of Reginald Marden and Christopher Marden. They will be missed. */ /* */ /* *//* Revision History: */ /* */:/* X-21 PAJ1129 Paul A. Jacobi 27-Mar-2000 */>/* Add support for CD-ROM audio via the IO$_DIAGNOSE */>/* function. Add DPT$M_SVP flags as required for */J/* ioc_std$movtouser()/ioc_std$movfromuser(). */J/* Reset module IDENT to match VDE. */J/* */F/* X-24 Atlant G. Schmidt 28-DEC-1999 */ /* */C/* - Modify the ATAPI "Sony bypass", similar to the */I/* removal of the bypass from the ATA/IDE code in X-14. */E/* On our fastest processors, this bypass seems to */I/* hang the system as the driver tries to read the data */D/* with PIO while the drive tries to DMA the data. */C/* This change may be problematic for Sony drives */D/* but they aren't supported by the hardware group */D/* anyway and I haven't seen any Sony drives since */E/* the very earliest one. They are believed to have */F/* not been working correctly with this driver for a */+/* while now, anyway. */ /* */ /* */F/* X-23 Atlant G. Schmidt 27-SEP-1999 */ /* */H/* - What a mess! While we believe that it *IS* possible */E/* to make the Cypress and the Clipper co-exist (by */F/* using buffers that are aligned to a 64KB boundary */G/* in PCI space, thereby allowing a single PRDT entry */E/* to cover the entire buffer, thereby allowing the */F/* Cypress chip to work), we're concerned that there */G/* may be other Cypress weirdnesses, especially given */C/* the problems shown with certain models of disk */5/* drives on the Eiger platform. */ /* */E/* Therefore, we're finally following Fred K's lead */G/* and de-com mitting from any DMA support on Cypress. */C/* For performance reasons, this also means we're */E/* de-committing from hard-disk support on Cypress. */ /* */E/* This change is easily made -- if we see that the */I/* controller is a Cypress, we'll clear the "Controller */6/* is DMA-capable" bit in the UCB. */ /* */H/* Having done this, this version re-instates the X-19 */7/* ("Clipper KZPAC in Hose 0") fix. */ /* */ /* */ /* */F/* X-22 Atlant G. Schmidt 01-SEP-1999 */ /* */C/* - The X-21 correction to the X-19 change was not */A/* completely effective. While it did cure the */D/* problem with the PRDT pointer, the Cypress chip */G/* seemed to be suffering from several other problems */C/* as well. X-22 backs out both the X-19 and X-21 */C/* changes, temporarily re-instating the Clipper */@/* "KZPAC in Hose 0" bug but leaving in force */,/* the X-20 build fix. */ /* */ /* */4/* X-21 (THIS CHANGE HAS BEEN REMOVED!) */ /* */F/* Atlant G. Schmidt 27-AUG-1999 */ /* */C/* - Corrects a problem introduced in X-19 whereby */C/* the Cypress chip doesn't reset its DMA pointer */B/* if the transfer is an exact multiple of 8KB. */ /* */D/* This is believed to be related to the fact that */C/* X-19 made the PRDT windows each 8KB in length. */ /* */D/* The PRDT is accessed by a 32-bit pointer value. */C/* At the start of a block of DMA transfers, this */D/* pointer is broadside loaded by DQDRIVER writing */H/* the four bytes of the register, one byte at a time. */D/* The low 16 bits of this register then increment */E/* (by four) from time-to-time as PRD table entries */G/* are consumed. I'm guessing that, immediately after */H/* the counter has been incremented, a logic bug makes */I/* the broadside-load fail. This leaves the PRD pointer */G/* pointing to wrong entry in the PRD table and makes */E/* our DMA target adresses that are "farther along" */C/* in the buffer than its beginning. (Reading the */F/* PRD pointer doesn't reveal the problem -- perhaps */D/* there's an internal copy. Obvious hacks such as */F/* reading back the bytes or writing the bytes twice */D/* writing them in a different order had no effect */)/* on the problem.) */ /* */F/* The correction employed, at least for the moment, */C/* is to avoid, on Cypress, transfers that are an */E/* exact multiple of 8KB in length. These transfers */C/* are instead fragmented into two transfers: one */E/* of n-1 blocks and one of 1 block. This doubtless */H/* hurts some because paging I/O tends to be multiples */C/* of 8KB, but the subsequent second transfer is */G/* almost certainly satisfied out of the disk drive's */D/* cache so it shouldn't hurt as much as it might. */ /* */ /* */F/* X-20 Atlant G. Schmidt 24-JUN-1999 */ /* */C/* - Corrects a problem building in the V71R stream */G/* (where the EV6'ish symbol IOC$K_BYTE isn't defined */D/* and that I/O subfunction doesn't yet exist). We */D/* now do the byte-laning ourselves (as in the EV4 */>/* through EV56 worlds). We also define the */C/* IOC$K_BYTE_LANED symbol ourselves if it isn't */?/* already defined (which it isn't in V71R). */ /* */ /* */F/* X-19 Atlant G. Schmidt 07-JUN-1999 */ /* */B/* - Corrects the problem whereby the PRDT didn't */C/* handle the situation when the transfer buffer */D/* (xfer_buffer) spanned more than one 64KB region */F/* of PCI-bpf%~ DQDRIVER.BCKѡ![FREEWAREV5.DQDRIVER]DQDRIVER.C;1rus address space. This typically occurred */D/* when a Clipper had a KZPAC in Hose 0; the KZPAC */C/* could allocate some map registers ahead of us, */H/* forcing our map registers out of natural alignment. */ /* */ /* */F/* X-18 Atlant G. Schmidt 29-APR-1999 */ /* */C/* - Allow the SFF-8038 DMA registers to be located */A/* anywhere within a 32MB I/O space. (For some */I/* reason, when there's no video card in some machines, */G/* the Console assigns these registers at *VERY* high */%/* addresses.) */G/* - Cleans up a few warnings from DECC /WARN=ENAB=ALL. */C/* - Removes any reading of the register at offset */C/* 0x3F7/0x377 -- this register really belongs to */F/* the Floppy Disk controller. Just for convenience, */D/* we don't remove the CRAM or the UCB CRAM vector */H/* entries so that all that stuff doesn't shift around */$/* yet again. */)/* - Tracing changes: */E/* o When compiled with NEVER defined, traces the */D/* initial state of all IDE registers whenever */E/* each drive does a PACKACK (or half-packack). */C/* o Traces the call to ioc$kp_reqchan because */H/* this is often the point where the trace changes */5/* from one drive to another. */ /* */ /* */F/* X-17 Atlant G. Schmidt 18-MAR-1999 */ /* */A/* This is the version released into V7.1-2R for */6/* the Clipper/Brick hardware kit. */ /* */F/* - Ignores the REL bit in the ATAPI Interrupt Reason */H/* register. For some reason, when we operate multiple */D/* drive simultaneously, we're seeing that bit set */G/* even though we don't set OVL in the ATAPI Features */#/* register. */D/* - Fixes a problem whereby ATA drives operating in */3/* PIO mode could miss errors. */C/* - Fixes a problem introduced sometime around the */D/* addition of DMA whereby ATAPI devices that used */F/* 2Kbyte sectors transfer only 1/4 as much data per */E/* transfer as they should (owing to a 2048- versus */0/* 512-byte miscalculation. */G/* - Includes a work-around to a UCB corruption problem */E/* discovered by Dave Carlson during Brick testing. */G/* This exhibited itself as VBNMAPFAIL bugchecks when */G/* running QVET while booted from an IDE system disk. */F/* The problem may or may not be within DQDRIVER but */4/* the workaround is effective. */C/* - Allows configuration of all odd-lettered units */E/* (DQAn:, DQCn:, DQEn:, etc.) as using the Primary */C/* IDE bus while all even-lettered units (DQBn:, */C/* DQDn:, DQFn:, etc.) use the Secondary IDE bus. */E/* This change may someday help resolve the problem */H/* of redundant DQan: names in a SCSI cluster (because */G/* the second system could configure DQCn: and DQDn:, */E/* the thrid system DQEn: and DQFn:, and so forth.) */E/* - Changes the tracing to optionally allow a single */</* trace log shared among the four units. */D/* - Changes the tracing to allow STARTIO to capture */J/* the IRP address, the passed-in LBA, and the passed- */E/* in bytecount info and also all four words of the */1/* IOSB eventually returned. */ /* */ /* */F/* X-16 Atlant G. Schmidt 15-MAR-1999 */ /* */F/* - Adds "volatile" to the counter in the Brick delay */@/* kludge so that the kludge will survive the */0/* compiler's optimization. */D/* - Updates the register cheat sheet, corrects some */E/* typos regarding the DMA registers, and re-orders */</* REGDUMP's saving of the DMA registers. */ /* */ /*6 */F/* X-15 Atlant G. Schmidt 04-FEB-1999 */ /* */I/* - Introduces formal support for IDE (ATA) hard drives. */E/* While this support has been latent in the driver */G/* all along (and was the only point of the driver in */D/* X-1 and X-2), only ATAPI drives were officially */I/* supported since X-3. This restriction is now lifted. */C/* - Adds half-DMA. This version will DMA into our */C/* transfer buffer with us still using the CPU to */H/* move data between the user and our transfer buffer. */B/* - Unfortunately, this means we've now got some */C/* code that's controller-chip-dependent. It may */G/* also mean that we can only execute on PCIbus-based */H/* controllers; although I've tried to allow continued */G/* ISA bus operation, I have no ISA bus test platform */C/* into which to stick my DTC2280 interface card. */D/* - Removes a limitation with IDE (ATA) hard drives */F/* larger than ~8.4GB. From Day 1, we were depending */F/* on calculating IDE (ATA) hard drive capacity from */B/* the Cylinder/Head/Sector (C/H/S) information */D/* returned by the drive. Unfortunately, this tops */C/* out at 8.455GB. Now, we look at the total LBN */C/* information returned by the drive and use that */I/* if it's non-zero and the C/H/S info seems to suggest */F/* 0x3FFF cylinders, 16 heads, and 63 sectors. (Unix */I/* experienced problems with an old drive that reported */C/* the total LBN information, but with the words */C/* swapped! Hopefully, our check will avoid that */#/* problem.) */I/* - Consolidates all the very-similar WFIKPCH paragraphs */F/* into a single routine and which now allows all of */C/* the callers to handle unsolicited interrupts. */I/* - Corrects the declaration of ucb$xxx_iohandle so that */G/* its storage doesn't overlap ucb$l_unsolicited_int. */E/* This bug, while latent in previous versions, had */H/* no effects before the centralized interrupt-handler */4/* was added in this baselevel. */F/* - Hacks around a problem found very late in testing */E/* whereby ATAPI drives that are sharing a bus with *//G/* a DQ-controlled system disk don't seem to have the */*H/* expected ATAPI signature in them. (Probably someone */G/* before us has munched the signature already. Pre-) */ G/* viously, this problem ws mostly-masked by the fact */nE/* that such a drive was never auto-configured, but */iI/* Fred K's new SYS$ICBM.EXE will reveal this problem.) */AG/* We probably ought to re-init the drive, but that's */*F/* too risky a change to make just before submitting */F/* the driver so I'm going to just bypass the ready- */H/* test in this one instance. (The drives are known to */5/* operate correctly henceforth.) */oG/* - Always builds the same UCB whether TRACING or not, */tJ/* but marks the UCB distinctively if we're not tracing. */E/* - The formerly-disjoint TRACING and INIBRK schemes */zJ/* were rationalized and merged into one unified scheme. */E/* - Introduces one more supportable hard drive (from */dG/* Fujitsu, a vendor we haven't seeen before) and one */sB/* more supportable CD-ROM drive (from Hitachi, */D/* another vendor we haven't seen before), and one */6/* more supportable DVD-ROM drive. */ /* */* /* *//F/* X-14 Atlant G. Schmidt 14-JAN-1999 */ /* */ F/* - Fixes a bug introduced by the X-13 change whereby */G/* certain drives (such as the most-recent WDC Caviar */ F/* drives) are slow to de-assert DRQ when you've fed */H/* them a sector of write data. They keep DRQ asserted */J/* for a microsecond or two and we were mis-interpreting */I/* that as the signal to bypass the wait-for-interrupt. */lH/* This was causing all sorts of trouble in the drive. */J/* By comparison, the Quantum drives seem to immediately */A/* remove DRQ and didn't provoke this problem. */iF/* - Kludges a problem whereby the Brick processor was */G/* apparently able to over-run the DATA register upon */2H/* writing to the drive. Rather than just losing data, */G/* this seemed to provoke some metastable behavior in */ E/* the ALT_STS register of both the Quantum and WDC */ F/* drives, causing them to occasionally show 0xFF as */D/* their status. The kludge fix adds a small delay */</* after each write in MOVE_SEC_TO_DRIVE. */E/* - Introduces several more supportable hard drives. */y@/* - Introduces support for the DS-10 platform. */ /* */A /* */1F/* X-13 Atlant G. Schmidt 05-JAN-1999 */ /* */ A/* This is the version released into V7.1-2R for */+/* Blizzard Update 2. */i /* */aG/* - Fixes an apparent Day-1 bug whereby a fast IDE/ATA */*C/* drive can beat the loop around to the WFIKPCH, */ C/* generating an apparently unsolicited interrupt */ E/* and a subsequent WFIKPCH timeout. This condition */*E/* is analagous to the situation already handled by */ D/* the ATAPI loop. This problem was detected using */</* the latest WDC 36400 6.4GB IDE drive. */ /* */9 /* */ F/* X-12 Atlant G. Schmidt 23-DEC-1998 */C/* - Forks before calling EXE$KP_RESTART. This fix */tC/* was developed by Stephen Shirron to correct a */bE/* Day-1 problem which manifested itself as Clipper */RA/* multiprocessors hanging during installation */ //* from the V7.1-2 CD-ROM. */tH/* - Adds init-time code to correctly identify the drive */E/* type, removing the confusing "Generic SCSI disk" */sE/* appelation. This is done by using the logic that */iE/* used to pass the IO$_PACKACK for system disks to */eB/* now pass IO$_SENSECHAR for non-system disks. */F/* STARTIO now dispatches this function to the half- */C/* packack logic that's been latent in the driver */tG/* for several versions now. Also, the devtype string */ G/* is pre-loaded to "Generic IDE/ATAPI disk" prior to */ */* doing any sizing. */F/* - Upon failing to get basic information from a unit */H/* during PACKACK or SENSECHAR, we now set its devtype */=/* string to "Nonexistent IDE/ATAPI disk". */iF/* - Corrected an apparent typo in the ATA reset logic */H/* which looks like it would have prevented recovering */8/* from an attempt to reset a drive. */G/* - The words of the ATAPI packet are now written with */ F/* a separate outw_t routine that always traces even */7/* if TRACE_DATA_TOO isn't defined. */ H/* - We now trace the storing of the sense_key, asc, and */D/* ascq. This makes it easier to read traces where */C/* TRACE_DATA_TOO is turned off but the drive has */n7/* passed us a sense code or three. */G/* - The old ucb$l_read_cmd and ucb$l_write_cmd fields, */VG/* which were mostly unused, are now entirely unused. */ F/* We're going to do DMA commands in a different way */C/* than Ben had apparently originally envisioned. */ /* */s /* */tF/* X-11 Atlant G. Schmidt 04-DEC-1998 */G/* - Re-ordered a few routines in the source so related */h5/* routines are closer together. */nF/* - Splits the read/write_atapi_segment routines into */F/* separate routines for 512-byte-sector devices and */F/* 2Kbyte-sector devices. This allows easier support */3/* of multi-block ATAPI reads. */ G/* - Re-introduces ATA transfers larger than 1 512-byte */ D/* block (backs out the X-2 change). This required */E/* correcting a day-1 bug with regard to taking the */ E/* device lock for each sector transferred and also */ C/* correcting an X-10-introduced bug whereby our */ B/* transfer buffer offset was no longer getting */D/* updated after each sector transferred. This bug */A/* was introduced when I took out the level of */nJ/* indirection in the params to move_sec_to/from_drive. */E/* This change appears to *DOUBLE* the PIO transfer *//0/* rate of ATA disk drives. */F/* - Introduces ATAPI transfers larger than 1 512-byte */I/* block for 512-byte sector devices (such as the Zip). */aH/* This change appears to improve the transfer rate of */B/* the Zip drive (which is PIO only, so this is */1/* important!) by about 15%. */aF/* - Introduces ATAPI transfers larger than 1 512-byte */G/* block for 2Kbyte sector devices (such as CD-ROMs). */ E/* This change appears to ??? the PIO transfer rate */ -/* of a typical CD-ROM. */eF/* - wait_ready no longer depends on wait_busy for the */C/* usual case where the drive is actually ready. */cC/* - I now set a ucb$l_media_id of DQ|IDE50 so that */hC/* MSCP will be willingto serve my disks across a */ "/* cluster. */D/* - I now set the DEV$M_NLT bit in DEVCHAR2 so that */A/* $INITIALIZE and ANALYZE/DISK won't look for */ C/* bad block info in the last track. This should */*1/* fix my own PTR 75-3-2500. */tD/* - Introduces some of the underpinnings to support */G/* DMA. At least for the moment, the register mapping */eD/* window is increased to 64Kbytes to allow access */./* to the DMA registers. */ /* */) /* */ F/* X-10 Atlant G. Schmidt 04-DEC-1998 */ /* */1A/* This is the version released into V7.1-2R for */e+/* Blizzard Update 1. */d /* */n//* - Adds Iomega Zip support */ </* - As part of adding Zip support, adds an */B/* write_atapi_segment routine to match up with */=/* the existing write_ata_segment routine. */>/* - As part of adding Zip support, moved the */H/* interrupt-acknowledging RD_STS (almost) exclusively */F/* to the ISR. It probably should have been this way */&/* since Day 1. */I/* - Corrects a customer-reported bug whereby ATAPI discs */tH/* that had a maximum LBN of 0x...1FF were sized 0x100 */A/* 2k blocks (1024 512-byte blocks) too small. */iH/* - Corrects a possible buglette whereby an ATAPI drive */H/* could have told us to transfer an invalid amount of */I/* data and possibly cause us to over-run our allocated */ I/* buffer. The lower-level routines probably would have */-G/* prevented damage, but it's nicer to catch it early */o5/* and flag the error explicitly. */eH/* - Adds a full tracing facility that can be condition- */5/* ally compiled into the driver. */ D/* - Takes out a level of indirection in passing the */I/* buffer_adx parameter to both move_sec_to/from_drive. */D/* - Splits the DEBUG feature into its four separate */7/* components for easier selection. */fB/* - Standardizes a lot of comments and automatic */</* variable names among similar routines. */ /* */c /* */*F/* X-9 Atlant G. Schmidt 18-NOV-1998 */ /* */*E/* This is the version released into V7.1-2 and V7.2. */* /* */*H/* - Fix a Day-1 bug whereby the memory that we allocate */F/* to build our system disk PACKACK IRP wasn't being */F/* cleared (initialized). Sometimes, this led to the */?/* IRP$V_FAST_FINISH bit (and others?) being */F/* 7 inadvertently set. This led to IOC_STD$REQCOM not */B/* cleaning up after us and thereby leaving our */C/* UCB$V_BSY bit set. This led to subsequent I/Os */rC/* assuming we were already busy and thereby not */cE/* calling our STARTIO entry point. This (finally!) */sJ/* led to the system hanging trying to load SYSINIT.EXE. */:/* Now we memset the memory to zeroes. */ /* */h /* */*F/* X-8 Atlant G. Schmidt 13-OCT-1998 */7/* - Fixed-up the build instructions. */ B/* - Adds symbols for many items, removing "magic */"/* numbers" */E/* - Corrects a bug whereby, during packack, a second */ G/* command could be issued to a drive while the first */oC/* command was still executing. This caused many */rB/* very odd flaky effects including most of the */B/* faiures originally blamed on the TEAC drive. */E/* This fix also fixes an interrupt-timeout problem */sB/* the Panasonic SR-8583 DVD-ROM drive and some */B/* intermittent errors with all Toshiba drives. */B/* - Adds support for the Compaq/Panasonic SR8583 */"/* DVD-ROM. */B/* - Substantially modifies and extends my INIBRK */*/* debugging scheme. */ /* */ /* */F/* X-7 Atlant G. Schmidt 08-OCT-1998 */>/* - Changes the device timeout to 15 seconds */F/* to accommodate the Toshiba XM-6302B CD-ROM drive. */ /* */ /* */cF/* X-6 Atlant G. Schmidt 11-JUN-1998 */>/* - Corrects the problem Goldrush was having */E/* autoconfiguring the DQDRIVER. The "hack" that we */eF/* grew from the original Ben Thomas code proved too */E/* clever for its long-term good. Now the code only */*D/* accepts autoconfiguration or a manual, explicit */-/* ISA bus I/O address. */g /* */t /* */ F/* X-5 Atlant G. Schmidt 01-JUN-1998 */F/* - Correct two compile-time diagnostics newly issued */'/* by DECC V5.7. */* /* */ /* */gF/* X-4 Atlant G. Schmidt 27-MAY-1998 */J/* - Supply "F11" as the default ACP prefix at DDB$L_ACPD. */E/* This wasn't necessary for Files-11 ODS2 support, */*C/* but *IS* necessary for ISO-9660 (F11CACP) and */r5/* High Sierra (F11DACP) support. */n /* */ /* */ F/* X-3 Atlant G. Schmidt 30-SEP-1997 */ /* */eI/* This is the version released as the V7.1-1H2 SHIP kit. */l /* */s5/* - Add ATAPI (IDE CD-ROM) support */b /* */t /* */PF/* X-2 CMF378 C M Fariz 19-Feb-1996 */J/* Update the driver with the necessary changes for 64bits.*/ /* */ I/* TEMPORARILY change the driver so that it writes only 1 */ F/* sector at a time. This is to work-around a problem *///* discovered in 3PB testing */n /* */ /* */uD/* X-1 Benjamin J. Thomas III May, 1994 */'/* Initial version. */r /* */ /* */eJ/************************************************************************/ 3/* Miscellaneous tidbits (from Ben, 'way back when)n *A * o Always check the BUSY bit. If set, none of the other bitshD * can be fully trusted. If clear, it's ok to believe the other" * bits and to issue commands. *A * o Make sure the correct drive is selected before any action. A * This means checking the BUSY bit for clear, then selectingsA * the desired driver, then checking BUSY again for clear foreD * that drive. Note that VMS allows a channel concept, and thisD * driver obtains ownership of the channel. Therefore, we don'tA * have to be quite so paranoid about ownership changes. GettA * the channel, then get ownership...release the channel when  * all done. *> * o It's mandatory to keep the sector count to 256 or fewer? * sectors because@ that's the biggest value you can express @ * in the byte-size SSEC_CNT register. And even then, do you9 * trust all drives to interpret 0x00 as 256? [AGS].p *@ * o It's a good idea to keep sector count under 128. This isC * just a caution against drives that may not handle the numbernC * as unsigned. [Well, it's probably a doubly-good idea becauseoA * the ATAPI byte-count is restricted to 65,536 (or 65,535 ifa6 * you're a Zip drive) and 128*512=65,536! -- AGS] *C * o Read ALT_STS for the status bits unless the direct intent isv# * also to clear the interrupt.  *@ * o Read/Write multiple commands don't seem to work very wellC * all the time. Based on advice, those commands seem aimed at D * treating the disk as if it had a different sector size. ThisA * driver uses the simple read/write commands. This means an C * interrupt per sector, which can't be avoided. A DMA versiont@ * could possibly help this (someday). For now, read/writesC * will be done with the simple commands, and not exceeding 127h. * blocks at a time (see previous bullet). * *% * Basic ATA (IDE) transfer algorithm  *, * 1) Use ALT_STATUS to check for BUSY = 07 * Error -> RESET, then retry oncen * 2) Select the proper drive*4 * 3) Check ALT_STATUS again, for BUSY=0, DRDY = 19 * Error -> RESET, retry from step 1f * 4) Write the CSRs * 5) Write the commanda * 6) Wait for interrupt; * Timeout -> RESET, retry from step 3w, * 7) Read STATUS (which clears interrupt)& * 8) Transfer data is DRQ=1, BUSY=0F * Error -> DRQ=0 -> goto step 9 (ERR should =1)9 * BUSY=1-> RESET, retry from step 3l7 * 9) Check that STATUS (saved from step 7) has ERR=0o- * Error -> handle error 8 * 10) If not done, goto step 6 (multisector transfers)4 * If done, goto step 3 (next command/transfer) * * A write would modify as:  ** * 5a) Check ALT_STATUS for BUSY=0, DRQ=1 * 5b) Send all of the data * 8) Remove step 8m * 4 * Powerup algorithm *? * 1) Poll ALT_STATUS for up to 40 seconds for BUSY=0, DRDY=1h& * Error -> Fatal * 2) Select drive 0* * 3) Read ALT_STATUS for BUSY=0, DRDY=1& * Error -> Fatal * 4) Drive 0 exists * 5) Select drive 1* * 6) Read ALT_STATUS for BUSY=0, DRDY=1H * Error->BUSY=1 fatal master/slave incompatability* * DRDY=0 no drive 1 * 7) drive 1 exists * * */  s/* Basic Build instructions: * ========================= * * $ COMPILE_IT:# * $ CC /STANDARD=RELAXED_ANSI89 -t' * /INSTRUCTION=NOFLOATING_POINT -d * /EXTERN=STRICT -M * /DEFINE=(BASEALIGN_SUPPORT) ! Not debuggingI * /DEBUG /NOOPTIMIZE /DEFINE=(DEBUG,BASEALIGN_SUPPORT) ! Debugging* * 'CC_OPT' - * /LIS='LISFILE' - * /MACHINE_CODE - * /OBJ='OBJFILE' -4 * 'SRCFILE'+SYS$LIBRARY:SYS$LIB_C.TLB /LIBRARY * $ * $ LINK_IT:T * $ GOSUB WRITE_OPTFILEt * $ LINK /ALPHA -  * /USERLIB=PROC - * /NATIVE_ONLY -x * /BPAGE=14 - * /SECTION -l * /REPLACE -e * /NODEMAND_ZERO -e * /NOTRACEBACK -_ * /SYSEXE - * /NOSYSSHR -9 * /SHARE='EXEFILE' - ! Driver imager> * /DSF='DSFFILE' - ! Debug symbol file9 * /SYMBOL='STBFILE' - ! Symbol table 8 * /MAP='MAPFILE' /FULL /CROSS - ! Map listing@ * 'OPTFILE' /OPTIONS ! Linker options file * $ * $ FINI: * $ EXIT 01  * $ * $ * $ WRITE_OPTFILE:e" * $ OPEN/WRITE OPTIONS 'OPTFILE'* * $ WRITE OPTIONS "SYMBOL_TABLE=GLOBALS" * $ WRITE OPTIONS ""R * $ WRITE OPTIONS "CLUSTER=VMSDRIVER,,, - ! Cluster_name, base_address, pfc,"B * $ WRITE OPTIONS " ", OBJFILE, ", - ! Now filenames..."i * $ WRITE OPTIONS "! USER$DISK1:[]SYSLOA /INCLUDE=(KPRINTF) /LIB, -" i * $ WRITE OPTIONS " SYS$LIBRARY:VMS$VOLATILE_PRIVATE_INTERFACES /INCLUDE=(BUGCHECK_CODES) /LIB, -" m * $ WRITE OPTIONS " SYS$LIBRARY:STARLET /INCLUDE=(SYS$DRIVER_INIT,SYS$DOINIT)"  * $ WRITE OPTIONS ""O * $ WRITE OPTIONS "COLLECT=NONPAGED_EXECUTE_PSECTS /ATTRIBUTES=RESIDENT, -"r * $ WRITE OPTIONS " $CODE$" * $ WRITE OPTIONIS ""O * $ WRITE OPTIONS "COLLECT=NONPAGED_READWRITE_PSECTS /ATTRIBUTES=RESIDENT, -"i# * $ WRITE OPTIONS " $PLIT$, -" & * $ WRITE OPTIONS " $INITIAL$, -"% * $ WRITE OPTIONS " $GLOBAL$, -"e" * $ WRITE OPTIONS " $OWN$, -", * $ WRITE OPTIONS " $$$105_PROLOGUE, -"( * $ WRITE OPTIONS " $$$110_DATA, -"+ * $ WRITE OPTIONS " $$$115_LINKAGE, -"i" * $ WRITE OPTIONS " $BSS$, -"# * $ WRITE OPTIONS " $DATA$, -"# * $ WRITE OPTIONS " $LINK$, -"H& * $ WRITE OPTIONS " $LITERAL$, -"$ * $ WRITE OPTIONS " $READONLY$" * $ WRITE OPTIONS ""Z * $ WRITE OPTIONS "COLLECT=INITIALIZATION_PSECTS /ATTRIBUTES=INITIALIZATION_CODE, -"* * $ WRITE OPTIONS " EXEC$INIT_000, -"* * $ WRITE OPTIONS " EXEC$INIT_001, -"* * $ WRITE OPTIONS " EXEC$INIT_002, -"+ * $ WRITE OPTIONS " EXEC$INIT_CODE, -" . * $ WRITE OPTIONS " EXEC$INIT_LINKAGE, -"0 * $ WRITE OPTIONS " EXEC$INIT_SSTBL_000, -"0 * $ WRITE OPTIONS " EXEC$INIT_SSTBL_001, -"- * $ WRITE OPTIONS " EXEC$INIT_SSTBL_002"  * $ WRITE OPTIONS ""+ * $ WRITE OPTIONS "PSECT_ATTR=$LINK$,WRT"o. * $ WRITE OPTIONS "PSECT_ATTR=$INITIAL$,WRT": * $ WRITE OPTIONS "PSECT_ATTR=$LITERAL$,NOPIC,NOSHR,WRT"; * $ WRITE OPTIONS "PSECT_ATTR=$READONLY$,NOPIC,NOSHR,WRT"f6 * $ WRITE OPTIONS "PSECT_ATTR=$$$105_PROLOGUE,NOPIC"2 * $ WRITE OPTIONS "PSECT_ATTR=$$$110_DATA,NOPIC"3 * $ WRITE OPTIONS "PSECT_ATTR=$$$115_LINKAGE,WRT"i5 * $ WRITE OPTIONS "PSECT_ATTR=EXEC$INIT_CODE,NOSHR"o * $ WRITE OPTIONS "" * $ WRITE OPTIONS "" * $ CLOSE OPTIONSa * $ RETURN * */r */* Usage Instructions: * =================== *? * This driver was originally written as an IDE/ATA-only driver.? * and was tested with a $19 ISA IDE controller plugged into anc@ * ISA bus. Since that time, it was extensively modified to also? * operate ATAPI drives and to use the built-in IDE controllerso= * in several low-end systems. In either environment, someonepB * (VMS or the user) needs to make sure that everything is set up. *E * For the system supported directly by VMS, this is easy. We include @ * an appropriate paragraph in [SYSEXE]SYS$CONFIG.DAT. This willA * cause recognition of the PCI-to-ISA bridge chip and the driver 5 * will be automatically loaded and units configured.i *A * For other systems where we don't explicitly support the driver A * (such as the Digital AlphaStation 400 4/233), you must run the C * ENABLE-IDE progam (from the Freeware CD) to enable the IDE port. A * This port will use IRQ 14 and is on the ISA bus. You may need @ * to run the console's ISACFG utility to reserve IRQ 14 for the@ * device. For a separate ISA board, there is no need to do any * special hardware setup. * *) * A sample ISACFG line, might look like:* *R * >>>isacfg -mk -slot 3 -dev 0 -iobase 1f0 -irq0 14 -etyp 1 -enadev 1 -handle IDE * *1 * To load the driver, use the following command:  * * $ MCR SYSMANe! * SYSMAN> IO CONNECT DQA0: - ) * /DRIVER=SYS$DQDRIVER -  * /CSR=%X1F0 -  * /ADAPTER=x -r * /VECTOR=y - * /NODE=n *& * This command assumes (or requires): *A * CSR is %X1F0, this is the standard address for a primary IDE  *+ * VECTOR is usually 56 (IRQ=14, 14*4=56)  *; * ADAPTER is the TR Number of the bus adapter that hoststC * the controller, usually found using $ MCR SYSMAN IO SHOW BUSa" * or SDA> CLUE CONFIGURATION. *> * NODE is the slot number into which the board was plugged. * * */y  *"/* Supported and/or tested devices *< * So far, this driver is known to have been recently tested * with the following devices: * * Magnetic disk drives: *1 * Conner CP-3024 (Ancient 20MB drive) A * Conner CP-2084 (Old 85MB 2-1/2" drive, aka RED11-E) 5 * Fujitsu MPD3108AT (Very recent 10GB drive)*1 * Quantum ProDrive LPS52A (Ancient 52MB drive)lQ * Quantum LP240A (Old 245MB drive, aka RE24L-E, like<~ DQDRIVER.BCKѡ![FREEWAREV5.DQDRIVER]DQDRIVER.C;1rD}Q ProDrive LPS240)h4 * Quantum Fireball 1080A (Recent 1.090 GB drive)8 * Quantum Fireball SE8.4A (Very Recent 8.455GB drive)6 * Quantum Fireball SE6.4A (Very Recent 6.4GB drive)6 * Quantum Fireball SE4.3A (Very Recent 4.3GB drive)1 * WDC Caviar 2850 (Recent 833MB drive)n5 * WDC Caviar 31200 (Recent 1.xGB drive) (1) 1 * WDC Caviar 21600 (Recent 1.6GB drive) 1 * WDC Caviar 24300 (Recent 4.3GB drive)/1 * WDC Caviar 33200 (Recent 3.2GB drive)h: * R WDC Caviar 36400 (Very Recent 6.4GB drive) (2) * * * CD-ROM drives:s *D * Compaq CR-588 (32x CD-ROM OEM'ed from Panasonic/Matsushita)> * Compaq CRD-8322B (32x CD-ROM OEM'ed from Lucky/Goldstar)) * Hitachi CDR-8435 (32x CD-ROM) (3,4) # * Sony CDU711 (32x CD-ROM)i) * TEAC CD-532E (32x CD-ROM) (3,5)z# * Toshiba XM-5702B (12x CD-ROM)n* * Toshiba XM-6102B (12x-to-24x CD-ROM)# * Toshiba XM-6202B (32x CD-ROM)a' * Toshiba XM-6302B (32x CD-ROM) (6) D * Toshiba XM-6402B (32x CD-ROM rebranded as a "Compaq XM-6402B") * * * DVD-ROM drives: *O * Compaq SR-8583 (as a ?? CD-ROM drive, OEM'ed from Panasonic/Matsushita)/2 * Toshiba SD-M1102 (as a 24x CD-ROM drive) (7)- * Toshiba SD-M1212 (as a ?? CD-ROM drive)e * * * ATAPI Read/Write drives:i *0 * Iomega ZIP 100 100 MB diskette drive (8) * * * Notes:c *E * (1) This WDC Caviar drive eventually experienced data corruptionhC * on writes. The drive also failed PC-based diagnostics so IkB * assume a genuine hardware failure occurred; the drive wasC * returned to the vendor for exchange and a different, more- # * recent model was returned.n *C * (2) This WDC Caviar drive was observed to be slow to de-assertr? * DRQ after you've fed it the 0x200 bytes of write data.o= * Because of this, you can't use DRQ .AND. NOT_BUSY toa= * bypass the WFIKPTCH the way we do for CD-ROM drives.  *H * (3) The Hitachi and TEAC CD-ROM drives contain a green activity LED/ * rather than the more-usual orange LED.l *G * (4) The Hitachi CD-ROM drive doesn't contain any front-panel audiod7 * output (earphone) connector or volume control.p *B * (5) Due to electrical considerations on the IDE bus regardingA * pull-up and pull-down resistors, the TEAC CD532-E CD-ROMnA * drive causes problems in multi-drive configurations. ThelE * drive is believed to be unsupported by hardware engineering.g *B * (6) One sample (out of three) of this Toshiba XM-6302B CD-ROME * drive was getting random positioning errors and was returnedE * to Storage Engineering (along with the failing CD-ROM disc);F * this sample of this drive is no longer available for testing. *A * (7) My one sample of this Toshiba SD-M1102 DVD-ROM drive hasPE * since suffered a hardware failure and is no longer availableh * for testing.  *E * (8) The version of the Zip drive that was tested succesfully was J * labled as Iomega "P/N 270928-003" (beige bezel) or the apparentlyJ * electrically-similar "P/N 270928-703" (with a white bezel). These@ * were "Oprah 2" mechanisms and contained V12.A firmware. *F * Another version labled as Digital "P/N PCXRJ-AG, Rev 001" wasE * also tested but experienced *VERY OCCASIONAL* timeout errorssF * that were recovered on retries within the driver. This drive,C * on the other hand, appears about one third faster than the F * Iomega-labled drive when running the "ALIGN" buffer-alignmentM * stress test. This was an "Oprah" drive and contained V23.D firmware.m *I * "Bought-off-the-street" generic Iomega Zip drives were not tested*G * but we believe they track the revs of the Iomega-branded part.  * */  m /* Caveats:i *F * This driver was written from the X3T9.2 specs, which may or may notC * accurately reflect working drives. The driver has been tested oncC * a few different IDE drives, but the following are known items toa * watch out for:v *= * o The driver ran into a lot of weird problems supportingp; * DMA on the Cypress chips (documented in the revisionr: * history). For this reason, DMA is only supported on * the Acer chips. *< * o Driver has been used only with 16 bit data interfaces *. * I'm sure that there are others - good luck. * *G * One obvious place for improvement is the copying of data to and fromnE * the transfer buffer. It would be better to move the data directlytF * to/from the user buffer, but there were some reasons that I didn't.F * First, it's just plain a pain to get it right. You have to accountC * for the alignment issues; this is no good if you are constantly*C * taking alignment faults or if you are constantly loading/merging9C * data. Secondly, you need to always empty/fill the disk's bufferE * and that means handling non-sector-sized counts. Overall, not tooE * hard, but a pain. Finally, the SFF-8038$[i spec simply isn't capableG * of handling buffers that aren't at least word-aligned, so we'd often*% * end up copying data around anyway.o * * */, i./* Registers - Here's my register cheat sheet. *5 * +-----+-----+-----+-----++-----+-----+-----+-----+tL * | 7 | 6 | 5 | 4 || 3 | 2 | 1 | 0 | Pri ISA Adx SYMBOLJ * +-----+-----+-----+-----++-----+-----+-----+-----+ [CRAM idx] Name *P * +-----+-----+-----+-DSC/++-----+-----+-----+-----+ [00] Alternate StatusK * | BSY | DRDY| DWF | SVC || DRQ |CORR | IDX | ERR | 3F6 R REG_ALT_STS 5 * |-----+-----+-----+-----++-----+-----+-----+-----| K * | x | x | x | x || 1 | SRST| nIEN| 0 | 3F6 W REG_DEV_CTLyN * +-----+-----+-----+-----++-----+-----+-----+-----+ [01] Device control * *5 * +-----+-----+-----+-----++-----+-----+-----+-----+tL * | HiZ | nWTG| nHS3| nHS2|| nHS1| nHS0| nDS1| nDS0| 3F7 R REG_DRV_ADDRM * +-----+-----+-----+-----++-----+-----+-----+-----+ [02/03] Drive addressK * (Belongs to FDC!!!)  *5 * +-----+-----+-----+-----++-----+-----+-----+-----+ H * | | 1F0 RW REG_DATAN * +-----+-----+-----+-----++-----+-----+-----+-----+ [04/05] Data (16 bits) * *E * +-----+-----+-----+-----++-----+-----+-----+-----+ [06] ErrortI * | BBK | UNC | MC | IDNF|| MCR |ABRT |TK0NF|AMNF | 1F1 R REG_ERRORn5 * +-----+-----+-----+-----++-----+-----+-----+-----+mL * | | 1F1 W REG_FEATURESH * +-----+-----+-----+-----++-----+-----+-----+-----+ [07] Features * *5 * +-----+-----+-----+-----++-----+-----+-----+-----+lK * | | 1F2 RW REG_SEC_CNTbL * +-----+-----+-----+-----++-----+-----+-----+-----+ [08/09] Sector countF * | | RLS | I/O | CoD | Reason5 * +-----+-----+-----+-----++-----+-----+-----+-----+  *K * +-----+-----+-----+-----++-----+-----+-----+-----+ (REG_LBA_0) J * | | 1F3 RW REG_SECTORM * +-----+-----+-----+-----++-----+-----+-----+-----+ [0A/0B] Sector number K * (or LBA0-7)r *K * +-----+-----+-----+-----++-----+-----+-----+-----+ (REG_LBA_8)lJ * | | 1F4 RW REG_CYL_LOL * +-----+-----+-----+-----++-----+-----+-----+-----+ [0C/0D] Cylinder lowL * (or LBA8-15) *L * +-----+-----+-----+-----++-----+-----+-----+-----+ (REG_LBA_16)J * | | 1F5 RW REG_CYL_HIM * +-----+-----+-----+-----++-----+-----+-----+-----+ [0E/0F] Cylinder highiM * (or LBA16-23)c *L * +-----+-LBA-+-----+-----++-----+-----+-----+-----+ (REG_LBA_24)J * | 1 | MODE| 1 | DRV || HS3 | HS2 | HS1 | HS0 | 1F6 RW REG_DRV_HDJ * +-----+-----+-----+-----++-----+-----+-----+-----+ [10/11] Drive/headM * (or LBA24-27)c *F * +-----+-----+-----+-DSC/++-----+-----+-----+-----+ [12] StatusG * | BSY | DRDY| DWF | SVC || DRQ |CORR | IDX | ERR | 1F7 R REG_STSs5 * +-----+-----+-----+-----++-----+-----+-----+-----+ G * | | 1F7 W REG_CMD G * +-----+-----+-----+-----++-----+-----+-----+-----+ [13] Commandx * */2 /* DMA Registers *5 * +-----+-----+-----+-----++-----+-----+-----+-----+eL * | 7 | 6 | 5 | 4 || 3 | 2 | 1 | 0 | Pri ISA Adx SYMBOLJ * +-----+-----+-----+-----++-----+-----+-----+-----+ [CRAM idx] Name *5 * +-----+-----+-----+-----++-----+-----+-----+-----+ I * |XXXXX|XXXXX|XXXXX|XXXXX|| R/W |XXXXX|XXXXX| ACT | nnn0 R/W DMA_CMDSR * +-----+-----+-----+-----++-----+-----+-----+-----+ [14/15] DMA Cmd Register@ * 5 * +-----+-----+-----+-----++-----+-----+-----+-----+0I * | ? | ? | ? | ? || ? | ? | ? | ? | nnn1 R/W DMA_DS1*] * +-----+-----+-----+-----++-----+-----+-----+-----+ [16/17] Device-specific Register #1  *5 * +-----+-----+-----+-----++-----+-----+-----+-----+ I * |Smplx| DRV1| DRV0|XXXXX||XXXXX| Int | Err | BSY | nnn2 R/W DMA_STSfU * +-Only+-DMA-+-DMA-+-----++-----+-----+-----+-----+ [18/19] DMA Status Registera *5 * +-----+-----+-----+-----++-----+-----+-----+-----+hI * | ? | ? | ? | ? || ? | ? | ? | ? | nnn3 R/W DMA_DS2r] * +-----+-----+-----+-----++-----+-----+-----+-----+ [1A/1B] Device-specific Register #2  *G * +-----+-----+-----+-----++-----+-----+-----+-----+ (LSB)0I * | |XXXXX|XXXXX| nnn4 R/W DMA_AD0*U * +-----+-----+-----+-----++-----+-----+-----+-----+ [1C/1D] PRDT Address Byte 0  *5 * +-----+-----+-----+-----++-----+-----+-----+-----+ I * | | nnn5 R/W DMA_AD1U * +-----+-----+-----+-----++-----+-----+-----+-----+ [1E/1F] PRDT Address Byte 1 *5 * +-----+-----+-----+-----++-----+-----+-----+-----+NI * | | nnn6 R/W DMA_AD2PU * +-----+-----+-----+-----++-----+-----+-----+-----+ [20/21] PRDT Address Byte 2E *G * +-----+-----+-----+-----++-----+-----+-----+-----+ (MSB)LI * | | nnn7 R/W DMA_AD3+U * +-----+-----+-----+-----++-----+-----+-----+-----+ [22/23] PRDT Address Byte 3* * * *4 * +----------------------+--------------------+-+4 * | Base Address [31:01] |0|D * +---+------------------+--------------------+-+ A PRDT entry4 * |EOT| ---------------- | Byte Count [15:01] |0|4 * +---+------------------+--------------------+-+ * * */ /* IDE/ATA Basics  *J * IDE ("Integrated Device Electronics" or "Integrated Drive Electronics",G * depending on whom you ask) is a very inexpensive way to connect diskF * drives to computers. The drive bus is, essentially, an extension ofD * the ISA bus with more-limited functionality. This interface firstA * made its appearance on the IBM PC-AT computer, hence its otherL * name: ATA -- AT Attachment.  *> * Most modern IDE controller chips actually emit two separate@ * IDE busses known as the Primary and Secondary busses. On each< * bus, you can connect up to two drives which are knwon forA * historical reasons as the "Master" and "Slave" drives althoughTA * all modern drives are completely autonomous. This leads to theD! * following basic block diagram:  *B * +-------+ +-------+B * |Master | | Slave |B * | Drive | | Drive |B * | DQA0: | | DQA1: |B * +------------+ +-------+ +-------+? * | |______________________| |____________| | ? * -----| Dual |___Primary IDE Bus_____________________|  * PCI | IDE | > * bus | Controller |_______________________________________? * -----| |___Secondary IDE Bus__ ____________ |G? * | | | | | | B * +------------+ +-------+ +-------+B * |Master | | Slave |B * | Drive | | Drive |B * | DQB0: | | DQB1: |B * +-------+ +-------+ *C * From an "ISA" perspective, the two halves of the IDE controllersTF * usually are completely autonomous and use separate sets of "legacy" * addresses and IRQs: *; * | Primary | Secondary*A * ---------------------+-------------------+-------------------"? * IDE Address | 0x3F6 and 0x1F0 | 0x376 and 0x170N: * DMA Reg Addresses | 0x90A0 | 0x90A89 * IRQ | 14. | 15.T * * * NOTE: *= * Not all implementations "pin-out" the Secondary IDE bus." * */R I/* IDE/ATA Basics (Cont'd) *9 * From a PCI perspective, it's a little more confusing.*# * There are two basic approaches:I *% * Shared PCI Config Space registers$% * ---------------------------------a *H * Here, the two IDE ports share one set of PCI config space registers.G * The Intel SIO and Acer Labs chips exemplify this school of thought. *F * +-------+ +-------+F * +------------+ | Drive | | Drive |F * -----| ISA | |Master | | Slave |F * | --| Bridge | | DQA0: | | DQA1: |F * | | +------Dm------+ +-------+ +-------+C * | | | |______________________| |____________| | C * -- --| Dual |___Primary IDE Bus_____________________|- * PCI | IDE |iB * bus | Controller |_______________________________________C * -- --| |___Secondary IDE Bus__ ____________ |(C * | | | | | | | |AF * | | +------------+ +-------+ +-------+F * | --| USB | |Master | | Slave |F * -----| Bridge | | Drive | | Drive |F * +------------+ | DQB0: | | DQB1: |F * +-------+ +-------+ * *' * Separate PCI Config Space registers-' * -----------------------------------t *J * Here, the each IDE port has its own set of PCI config space registers.8 * The Cypress chip exemplifies this school of thought. *F * +-------+ +-------+F * +------------+ | Drive | | Drive |F * -----| ISA | |Master | | Slave |F * | --| Bridge | | DQA0: | | DQA1: |F * | | +------------+ +-------+ +-------+C * | --| Primary |______________________| |____________| |aC * | --| IDE |___Primary IDE Bus_____________________|r * -- | | Controller |g * PCI | +------------+ B * bus | | Secondary |_______________________________________C * -- --| IDE |___Secondary IDE Bus__ ____________ |:C * | --| Controller | | | | |iF * | | +------------+ +-------+ +-------+F * | --| USB | |Master | | Slave |F * -----| Bridge | | Drive | | Drive |F * +------------+ | DQB0: | | DQB1: | * */l (/* ATAPI Basicsr *@ * ATAPI (ATA Packet Interface) eveolved when it became apparent? * that the limited functionality of an ATA interface would notr= * be sufficient to handle devices that were more-complicated 2 * than ordinary fixed-media magnetic disk drives. *< * Essentially, ATAPI allows SCSI commands to be passed over? * the ATA bus and SCSI status to be returned over the ATA bus.e: * It's vastly simplified SCSI, though, with no concept of; * disconnect/reconnect, much-simplified phases, and so on.* * */O s/* DISK ADDRESSING *J * Disk addressing in the ATA world is, umm, "less than straight-forward". *4 * Firstly, there are two major modes of addressing: *. * o CHS -- "Cylinder, Head, and Sector" mode, * o LBA -- "Logical Block Addressing" mode* * o MSF -- "Minutes:Seconds:Frames" mode * *A * CHS mode is the legacy mode (from whence came the famous 528MB2B * IDE disk-size limit). In this mode, you select the disk address* * by loading values into three registers: *# * o The 16-bit Cylinder Register* * o The 8-bit Sector Register& * o The 4-bit Head (Track) Register *H * This sounds easy enough, but just to make your life more interesting,( * the numbering scheme is a bit screwy: *. * o The Cylinders are numbered from 0 to x.L * The identify_device command returns the number of cylinders (so x+1). *, * o The Sectors are numbered from 1 to y.H * The identify_device command returns the number of sectors (so y). ** * o The Heads are numbered from 0 to z.H * The identify_device command returns the number of heads (so z+1). * *I * And just FYI, early BIOS's were limited to 1024 cylinders [0000:03FF],wH * 63 sectors [00:3F], and 16 heads [00:0F]. This produces 1032192 total% * blocks or 528,482,304 total bytes.B * *C * LBA mode is newer and simple. The 28-bit Logical Block Number isKB * simply parsed up into an 8-bit low portion that is then stuffedA * into the Sector Register, a 16-bit middle portion that is thenh? * stuffed into the Cylinder Register, and a 4-bit high portionr> * that is stuffed into the Head Register. And all three parts * are zero-based. * *H * As more FYI, many recent PC BIOS are limited to a 24-bit disk addressE * consisting of 14 bits of cylinder, 4 bits of head, and six bits ofO6 * sector. This limits disks on these BIOS's to either *I * 16383 (cyls) * 16 (heads) * 63 (sectors) * 512 = 8,455 million bytese * * - or -) *N * 16383-1024 (cyls) * 16 (heads) * 63 (sectors) * 512 = 7,927 million bytes ** * This driver suffers from no such limit. * *E * Fiinally, all CDs (SCSI or ATA) can also be addressed in an audio- ? * relevant mode called "Minutes:Seconds:Frame" (MSF) mode. The* * resulting LBN is simply:h *9 * LBN = ( ( ( Min * 60 ) + Seconds ) * 75 ) + Frames  * */e e/* CD-DA BASICS  *> * CD's all use the same basic physical structure, a 2352-byteC * block ("big frame") of data. On the original CD-DA (Compact Disk F * Digital Audio) disks, this corresponds to 1/75 of a second of music9 * sampled at 44,100 Hz rate, 2 channels, 16-bits/sample.  *@ * So: 44,100 (samples/s) * 2 (chans/sample) * 2 (bytes/chan) => * 176,400 bytes/secA * And: 2352 (bytes/frame) * 75 (frames/s) = 176,400 bytes/secondr */ * This is the famous 1X CD data transfer rate.t * *K * Within this 2352-byte frame, there's actually a finer-grained structure.aH * The big frame actually consists of 98 "small frames" of 24 data bytes- * each. These small frames actually contain:  *F * +------+----------+-----------+----------+-----------+----------+F * | Sync | Sub-chnl | data | CRC | data | CRC |F * | 24+3 | 14+3 | 12*(14+3) | 4*(14+3) | 12*(14+3) | 4*(14+3) |F * +------+----------+-----------+----------+-----------+----------+ *+ * For a total of 588 bits per small frame.  *D * The sets of "14" bits come about through EFM -- Eight-to-FourteenG * Modulation. To mimimize dc and high frequency content and to controloH * the maximum and minimum pit lengths (the dual of the frequency-domainG * stuff), each byte is converted by a look-up table in the drive from/uG * to a unique run of 14 bits. The sets of "3" bits then come into playwC * in so-called "merging bits" that further minimize dc content ando8 * control minimum and maximum pit length between bytes. *@ * The CRC bits aren't exactly a CRC but I'll describe them more * fully in the next release.l *< * So 98 small frames contain 98 * (12*2) = 2352 data bytes. *? * The sub-channel byte is used (bitwise) to provide eight sub- 6 * channels of data called P, Q, R, S, T, U, V, and W: *? * o The "P" Channel provides "Pause" information, signallingn? * the time interval between tracks or times when the audioe6 * should be muted (such as computer data tracks). *C * o The "Q" Channel provides most of the interesting informationyB * such as the lead-in and lead-out indications, track number,= * minutes:seconds:frames absolute and relative time, they? * Universal Product Code (UPC), and International Standardr * Record Code (ISRC). * */+ -/* CD-ROM STANDARDS- *E * The great thing about standards is that there are so many of them!  *G * Built atop the basic CD-DA standard are a variety of CD data storage F * standards. Basically, they all devolve down to storing data in fiveF * types of records, all superimposed within the basic 2352-byte CD-DA * frame:  *M * CD-DA +-----------------------------------------------------+ M * "Red Book" | 2352 data bytes, no extra error-correction |M * Audio +-----------------------------------------------------+ *M * +------+--------+---------------+-----+-------+-------+SM * CD-ROM | sync | header | user data | EDC | blank | ECC |+M * "Yellow Book" | 12 b | 4 b | 2048 bytes | 4 b | 8 b | 276 b | M * Mode-1 Data +------+--------+---------------+-----+-------+-------+- *M * +------+--------+-------------------------------------+ M * CD-ROM | sync | header | user data, no extra error-corr | M * "Yellow Book" | 12 b | 4 b | 2336 bytes |-M * Mode-2 Data +------+--------+-------------------------------------+  *N * +------+--------+--------+---------------+-----+-------+N * CDROM-XA | sync | header | subhdr | user data | EDC | ECC |N * "Green Book" | 12 b | 4 b | 8 b | 2048 bytes | 4 b | 276 b |N * Mode-2, Form 1 +------+--------+--------+---------------+-----+-------+ *N * +------+--------+--------+-----------------------+-----+N * CDROM-XA | sync | header | subhdr | user data | EDC |N * "Green Book" | 12 b | 4 b | 8 b | 2324 bytes | 4 b |N * Mode-2, Form 2 +------+--------+--------+-----------------------+-----+ * * *G * The "header" field is the LBN, used for position-checking just as inR * magnetic disk drive headers.- *D * Green Book disks allow intermixing audio, video, and data sectorsH * within a track. The sub-header describes which type of data is stored * in each sector.  *0 * The EDC is the extended Error Detection Code. *( * The ECC is the Error Correction Code. * * * *NOTE*+ *I * *THIS DRIVER ONLY SUPPORTS YELLOW BOOK, MODE 1 (2048-byte) SECTORS!*  * */  /* CD TRACKS, TOCs, AND SESSIONS *@ * CDs are divided into "tracks, which are simply collections of> * contiguous blocks. A Table-of-Contents describes the tracks6 * within a disk (or session of a multi-session disk). *> * Within a track, the blocks must all be of the same "color"." * That is, they must be entirely: * * o Red Book CD-DA, or * o Yellow-Book CD-ROM, or? * o Green Book (which allows mixing Audio, Video, and Data)| * *F * So-called "Mixed Mode" disks may store one or more tracks of CD-ROMF * and one or more tracks of audio daa. The most common way to do thisD * is to record the CD-ROM data on Track 1 and the Audio data on all * subsequent tracks.- * *E * The Table-of-Contents (TOC) is stored as Q-subcode data within the| * Lead-in track of the CD.F * *@ * Conceptually, an entire CD is recorded in one "session", fromE * lead-in (and TOC) through the data tracks, through to the lead-outD * track. At that point, the session is "closed" and no further dataB * can be recorded on the CD-ROM (because it won't be described in * the already-recorded TOC). *@ * More-modern CD drives can read additional complete "sessions"D * (complete with their own TOCs) beyond the first session. PhotoCDs? * are probably the most common example of such a multi-session-A * CD(-ROM). I have never tested this driver with a multi-session0C * CD and have no idea if it works. It's quite unlikely: since each B * session is, conceptually, a complete CD, we would probably needC * to materialize additional devices to the VMS file system so that-, * each session could be separately mounted. * */  ?1/* Mapping 512-byte VMS blocks to 2K-byte sectors- *@ * Because CD-ROM drives read 2Kbyte sectors, it is necessary to@ * map VMS's 512-byte blocks onto this structure. The mapping is@ * straightforward, mapping 4 VMS LBNs into each CD-ROM LBN. The? * only interesting part is determining how many CD-ROM sectors9! * to read. The equation used is:- *I * # of CD_ROM sectors = ( Int( VMS_blocks + VMS_LBN%4 - 1 ) / 4 ) + 1  *A * The diagrams below show all the interesting cases of differing-F * VMS read lengths and VMS 512-byte block alignment within the 2Kbyte * CD-ROM sectors. * *W * __XX * Length: 0 |X * |X * +|--------+---------+---------+---------+ Length = 0 |X * ||0 1 2 3 | 4 5 6 7 | 8 9 A B | C D E F | Offset = _0_ |X * +|--------+---------+---------+---------+ 0 --> Would read 0 2K blocks |X * |X * +--|------+---------+---------+---------+ Length = 0 |X * | 0|1 2 3 | 4 5 6 7 | 8 9 A B | C D E F | Offset = _1_ |q * +--|------+---------+---------+---------+ 1 --> Would read 1 2K block |- Special case all these-X * |X * +----|----+---------+---------+---------+ Length = 0 |X * | 0 1|2 3 | 4 5 6 7 | 8 9 A B | C D E F | Offset = _2_ |X * +----|----+---------+---------+---------+ 2 --> Would read 1 2K block |X * |X * +------|--+---------+---------+---------+ Length = 0 |X * | 0 1 2|3 | 4 5 6 7 | 8 9 A B | C D E F | Offset = _3_ |X * +------|--+---------+---------+---------+ 3 --> Would read 1 2K block |X * __| * * Length: 1y * _O: * +|-|------+---------+---------+---------+ Length = 1; * ||0|1 2 3 | 4 5 6 7 | 8 9 A B | C D E F | Offset = _0_rN * +|_|------+---------+---------+---------+ 1 --> Read 1 2K block * _o: * +--|-|----+---------+---------+---------+ Length = 1; * | 0|1|2 3 | 4 5 6 7 | 8 9 A B | C D E F | Offset = _1_ N * +--|_|----+---------+---------+---------+ 2 --> Read 1 2K block * _v: * +----|-|--+---------+---------+---------+ Length = 1; * | 0 1|2|3 | 4 5 6 7 | 8 9 A B | C D E F | Offset = _2_ N * +----|_|--+---------+---------+---------+ 3 --> Read 1 2K block * _ : * +------|-|+---------+---------+---------+ Length = 1; * | 0 1 2|3|| 4 5 6 7 | 8 9 A B | C D E F | Offset = _3__N * +------|_|+---------+---------+---------+ 4 --> Read 1 2K block * * * Length: 2 * ___ : * +|---|----+---------+---------+---------+ Length = 2; * ||0 1|2 3 | 4 5 6 7 | 8 9 A B | C D E F | Offset = _0_N * +|___|----+---------+---------+---------+ 2 --> Read 1 2K block * ___ : * +--|---|--+---------+---------+---------+ Length = 2; * | 0|1 2|3 | 4 5 6 7 | 8 9 A B | C D E F | Offset = _1_ N * +--|___|--+---------+---------+---------+ 3 --> Read 1 2K block * ___w: * +----|---|+---------+---------+---------+ Length = 2; * | 0 1|2 3|| 4 5 6 7 | 8 9 A B | C D E F | Offset = _2_dN * +----|___|+---------+---------+---------+ 4 --> Read 1 2K block * _____+: * +------|--+--|------+---------+---------+ Length = 2; * | 0 1 2|3 | 4|5 6 7 | 8 9 A B | C D E F | Offset = _3_ O * +------|_____|------+---------+---------+ 5 --> Read 2 2K blocks4 * * * Length: 3 * _____T: * +|-----|--+---------+---------+---------+ Length = 3; * ||0 1 2|3 | 4 5 6 7 | 8 9 A B | C D E F | Offset = _0_N * +|_____|--+---------+---------+---------+ 3 --> Read 1 2K block * _____I: * +--|-----|+---------+---------+---------+ Length = 3; * | 0|1 2 3|| 4 5 6 7 | 8 9 A B | C D E F | Offset = _1_rN * +--|_____|+---------+---------+---------+ 4 --> Read 1 2K block * _______o: * +----|----+--|------+---------+---------+ Length = 3; * | 0 1|2 3 | 4|5 6 7 | 8 9 A B | C D E F | Offset = _2_ O * +----|_______|------+---------+---------+ 5 --> Read 2 2K blocks  * _______|: * +------|--+----|----+---------+---------+ Length = 3; * | 0 1 2|3 | 4 5|6 7 | 8 9 A B | C D E F | Offset = _3_ O * +------|_______|----+---------+---------+ 6 --> Read 2 2K blocks| * * * Length: 4 * _______u: * +|-------|+---------+---------+---------+ Length = 4; * ||0 1 2 3|| 4 5 6 7 | 8 9 A B | C D E F | Offset = _0__N * +|_______|+---------+---------+---------+ 4 --> Read 1 2K block * _________ : * +--|------+--|------+---------+---------+ Length = 4; * | 0|1 2 3 | 4|5 6 7 | 8 9 A B | C D E F | Offset = _1_ O * +--|_________|------+---------+---------+ 5 --> Read 2 2K blocksa * _________ : * +----|----+----|----+---------+---------+ Length = 4; * | 0 1|2 3 | 4 5|6 7 | 8 9 A B | C D E F | Offset = _2_BO * +----|_________|----+---------+---------+ 6 --> Read 2 2K blocks * _________n: * +------|--+------|--+---------+---------+ Length = 4; * | 0 1 2|3 | 4 5 6|7 | 8 9 A B | C D E F | Offset = _3_nO * +------|_________|--+---------+---------+ 7 --> Read 2 2K blocks*f~ DQDRIVER.BCKѡ![FREEWAREV5.DQDRIVER]DQDRIVER.C;1r * * * Length: 5  * ___________ : * +|--------+--|------+---------+---------+ Length = 5; * ||0 1 2 3 | 4|5 6 7 | 8 9 A B | C D E F | Offset = _0_AO * +|___________|------+---------+---------+ 5 --> Read 2 2K blocks  * ___________0: * +--|------+----|----+---------+---------+ Length = 5; * | 0|1 2 3 | 4 5|6 7 | 8 9 A B | C D E F | Offset = _1__O * +--|___________|----+---------+---------+ 6 --> Read 2 2K blocks  * ____________: * +----|----+------|--+---------+---------+ Length = 5; * | 0 1|2 3 | 4 5 6|7 | 8 9 A B | C D E F | Offset = _2__O * +----|___________|--+---------+---------+ 7 --> Read 2 2K blocks* * ___________ : * +------|--+--------|+---------+---------+ Length = 5; * | 0 1 2|3 | 4 5 6 7|| 8 9 A B | C D E F | Offset = _3_|O * +------|___________|+---------+---------+ 8 --> Read 2 2K blocksB * * * Length: 6  * _____________D: * +|--------+----|----+---------+---------+ x Length = 6; * ||0 1 2 3 | 4 5|6 7 | 8 9 A B | C D E F | Offset = _0_O * +|_____________|----+---------+---------+ 6 --> Read 2 2K blocksi * _____________ : * +--|------+------|--+---------+---------+ Length = 6; * | 0|1 2 3 | 4 5 6|7 | 8 9 A B | C D E F | Offset = _1_gO * +--|_____________|--+---------+---------+ 7 --> Read 2 2K blocksv * _____________S: * +----|----+--------|+---------+---------+ Length = 6; * | 0 1|2 3 | 4 5 6 7|| 8 9 A B | C D E F | Offset = _2_oO * +----|_____________|+---------+---------+ 8 --> Read 2 2K blocks * _______________ : * +------|--+---------+--|------+---------+ Length = 6; * | 0 1 2|3 | 4 5 6 7 | 8|9 A B | C D E F | Offset = _3_ O * +------|_______________|------+---------+ 9 --> Read 3 2K blockse * * * Length: 7: * _______________*: * +|--------+------|--+---------+---------+ Length = 7; * ||0 1 2 3 | 4 5 6|7 | 8 9 A B | C D E F | Offset = _0_cO * +|_______________|--+---------+---------+ 7 --> Read 2 2K blocks- * _______________*: * +--|------+--------|+---------+---------+ Length = 7; * | 0|1 2 3 | 4 5 6 7|| 8 9 A B | C D E F | Offset = _1_yO * +--|_______________|+---------+---------+ 8 --> Read 2 2K blocksT * _________________m: * +----|----+---------+--|------+---------+ Length = 7; * | 0 1|2 3 | 4 5 6 7 | 8|9 A B | C D E F | Offset = _2_eO * +----|_________________|------+---------+ 9 --> Read 3 2K blocks( * _________________e: * +------|--+---------+----|----+---------+ Length = 7; * | 0 1 2|3 | 4 5 6 7 | 8 9|A B | C D E F | Offset = _3_,O * +------|_________________|----+---------+ 10 --> Read 3 2K blocks, * * * Length: 8T * _________________l: * +|--------+--------|+---------+---------+ Length = 8; * ||0 1 2 3 | 4 5 6 7|| 8 9 A B | C D E F | Offset = _0_*O * +|_________________|+---------+---------+ 8 --> Read 2 2K blockst * ___________________p: * +--|------+---------+--|------+---------+ Length = 8; * | 0|1 2 3 | 4 5 6 7 | 8|9 A B | C D E F | Offset = _1_RO * +--|___________________|------+---------+ 9 --> Read 3 2K blocksc * ___________________-: * +----|----+---------+----|----+---------+ Length = 8; * | 0 1|2 3 | 4 5 6 7 | 8 9|A B | C D E F | Offset = _2_ O * +----|___________________|----+---------+ 10 --> Read 3 2K blocks  * ___________________-: * +------|--+---------+------|--+---------+ Length = 8; * | 0 1 2|3 | 4 5 6 7 | 8 9 A|B | C D E F | Offset = _3_oO * +------|___________________|--+---------+ 11 --> Read 3 2K blocksd * * */  l/* BLOCKS and SECTORSt *G * Most ATA devices employ 512-byte sectors the same as any traditionalLH * VMS or SCSI disk. Most magnetic read-write ATAPI devices (such as the2 * Iomega Zip drive) also employ 512-byte sectors. *C * Devices based on CD technology, however, employ CD-sized sectorsiH * (which contain at least 2352 raw bytes of data but, after subtractingL * extra error-handling codes, result in a net data transfer of 2048 bytes). *D * Generally speaking, within the driver, I've tried to refer to theG * on-disk, variable-sized things as "sectors" and to only use the term D * "blocks" to refer to the standard VMS-sized 512-byte data blocks. * */f /* REQUEST SEGMENTATION5 *L * A single I/O large I/O request may be segmented (fragmented) into severalK * smaller I/O requests at at least two different points in the life of thee * I/O request. In particular: *J * - [SYS]FSYSFASTIO or [SYS]SYSACPFDT will segment I/Os into transfersG * of no more than UCB$L_MAXBCNT (or 127 blocks/65024 bytes if that P * field is zero). I'm not sure why this is done, but it's handy for us. ... *B * - Our READ/WRITE/DATACHECK routines will also segment requestB * into transfers of varying sizes (depending on exactly whichA * segment-handler will then process the I/O onwards to whichhC * target device). This is required because the ATA, ATAPI, andnC * SFF-8038i (DMA) specs impose limits on the size of transfersr * or DMA buffers. * * In particular:  *@ * o a single ATA PIO command can't transfer more than oneF * sector (typically, 512 bytes) bytes per interrupt (althoughB * it can certainly transfer more in a single command that% * spans several interrupts).i *E * o a single ATAPI PIO command can't transfer more than 65,535tA * bytes per interrupt (although it can probably transferuC * more in a single command that spans several interrupts)., *E * o a single ATA or ATAPI DMA command can't transfer more thantD * 65,536 bytes using a single DMA window (although the PRDT? * mapping table can specify more than one DMA window.)s * *H * Devices that employ 2KB sectors make this all a bit more interesting.I * Although VMS's upper-level segmentation shields ATA and 512-byte ATAPItC * devices from a lot of these concerns, 2KByte ATAPI devices couldPC * have to transfer 33 sectors (33*2048=67584 bytes) to fulfill anySF * arbitrarily-aligned group of 127 512-byte blocks. VMS's upper-levelC * segmentation won't help here -- the driver must correctly handlehD * this case or set UCB$L_MAXBCNT to, say 124*512 so no transfer can4 * span more than 65536 bytes worth of on-disk data. *D * In principal, we could probably set UCB$L_MAXBCNT and rely on VMS# * to do all the segmentation, but:  *G * - Belt-and-suspenders (VMS doing it *AND* us doing it) is probably  * safer, andt *F * - Because our segmentation need pass through fewer levels, we canJ * do it faster. We may well someday want to set a large UCB$L_MAXBCNT4 * to disable some or all of VMS's segmentation. * */D /* IDE/ATAPI Bus Mastering DMA *A * This is a term used in the PC industry to describe the concept ? * that you or I would simply think of as "DMA". This describes-C * the capability of a controller to actually become the bus master A * (Be still my heart!) and thereby affect its own DMA transfers.> * This is also described as "First Party DMA", as compared toE * "Third Party DMA" where a dedicated DMA controller on the bus does6B * PIO to the device and then transfers the data into main memory. *? * For ATA and ATAPI devices, this capability is defined not by+C * the ATAPI spec but rather by a working group of the Small Factor 5 * Task force. The ruling spec is known as SFF-8038i.  * @ * Essentially, the spec defines two sets of registers (one eachA * for the Primary and Secondary IDE controllers) that each point*B * to tables (Called the PRDT or Physical Region Descriptor Table)@ * that then allow scatter-gather DMA through the PCI bus memoryE * window(s). In each table, the PRDT entries are stored sequentiallyb@ * in the table and the data transfer proceeds until all regions; * described by the table have been transfered or the drive * terminates the transfer.L *, * Each PRDT entry has the following format: *5 * +----------------------+--------------------+-+n5 * | Base Address [31:01] |0|r5 * +---+------------------+--------------------+-+r5 * |EDT| ---------------- | Byte Count [15:01] |0|e5 * +---+------------------+--------------------+-+  * * * NOTE WELL:* *A * - Regions *CAN NOT* exceed 64K bytes. Transfers (or at least2B * mapping windows) must be segmented to be smaller than that./ * A byte count of 0x0000 implies 64Kbytes.a *F * - Regions *CAN NOT SPAN* a 64K boundary in the PCI address space! *B * - Regions *MUST BE* word-aligned! We need to manually shuffle/ * bytes to the user buffer if they aren't.e *C * - The Descriptor Table itself *CAN NOT SPAN* a 64K boundary inC * the PCI address space!D *> * - The Descriptor Table itself *MUST BE* longword aligned! *I * - The EDT (End-Descriptor-Table) flag is set in the last PRDT entry.  *? * - The table must describe at least enough space to containo? * the entire transfer conducted by the drive. If the tableoA * describes more bytes than will be transfered by the drive,n@ * the driver must clear the "ACTIVE" bit (in DMA_CMD) after * the transfer is complete! * */n */* IDE/ATAPI Bus Mastering DMA (Continued) *F * We open two mapping windows per unit: One for the PRDT (controller-C * chip-based scatter/gather map table) and one for the actual datae * window. *B * Right now, our data window is pointed at our own buffer. In the@ * near future, it should be pointed to directly the user buffer= * any time the DMA is "simple". Simple DMAs are those where:i *9 * o The transfer length is less than or equal to 64KB. ? * This is a "gimme" given that VMS defaults to fragmenting ? * our requests to 127 or fewer blocks (63.5K) and we forceu8 * smaller fragmentation for 2K-byte sector devices. *; * o The transfer length is an integral number of blocks.c< * For non-integral write transfers, we probably need to@ * pad the last block. (Although the DVDRIVER has discoveredA * that this is automatic for floppy controllers, I won't yetc< * take that on faith for all possible IDE controllers.)= * For non-integral read transfers, we need to do further & * investigation at a future date. *= * o The transfer is word-aligned. This is required becaused- * there's no hardware to do the shuffle.  * * * Notes:S *A * Rather than open one window per unit, we could probably openlB * only one window per controller, but I'm not going to get into * that now. *? * For systems with less than 1GB of memory, we could use the A * direct-mapping window rather than the scatter/gather window.  * */  /* More PRDT Fun *> * Some day when we start doing direct I/O, the PRDT could, in? * principle, be used to automate some of work that needs to be  * done. For example:- *= * o We currently face the problem that for CD/DVD-ROM read B * transfers, we must transfer 2Kbyte sectors and then (often)? * extract only a portion of our buffer to transfer onwards-= * to the user. We could easily use the PRDT to cause the ; * transfer of the leading/trailing unwanted VMS blocks 8 * into a "junk" buffer while the desired blocks are. * transfered into the actual user buffer. *? * Similarly, for writes to 2KByte-sector devices, we could < * easily use the PRDT to do the "merge" between the old< * data blocks (previously read into our buffer) and the< * new data blocks (transferred from the user's buffer).? * This would make the required RMW operation much simpler.- * *# * We could get even fancier still:  *A * o Currently, we must do data extraction for all devices (not = * just 2Kbyte-sector devices) when the user asks for the-; * reading of less than a full sector. We could use theo< * PRDT to accomplish this for any transfer with an even? * byte-count, transferring the desired bytes to the user's-> * buffer while transferring the undesired bytes to a junk? * buffer. (We're still stuck doing it by hand for at least @ * the last sector/block of transfers with odd byte counts.) *9 * o Currently, we must do buffer-filling (with zeroes) 9 * extraction for all devices (not just 2Kbyte-sector : * devices) when the user asks for the writing of less> * than a full sector. We could use the PRDT to accomplish= * this for any even byte-count, transferring the desired-= * bytes from the user's buffer and transferring the resto: * of the bytes from a pre-zeroed buffer. (We're still: * stuck doing it by hand for the last sector/block of' * transfers with odd byte counts.)- *= * o We could also use the PRDT to facilitate transfers that|? * are less-than page-aligned (all the way down to transfers < * that are just word-aligned). (We're still stuck doing< * it by entirely by hand for transfers to odd addresses,9 * that is, transfers that are not even word-aligned.)- * */  *6/* Define system data structure types and constants */C#include ccbdef /* Channel Control Block */ C#include crbdef /* Controller Request Block */-D#include crabdef /* Counted Resource Allocation Block */D#include cramdef /* Controller Register Access Method */E#include crctxdef /* Counted Resource Context Block */-B#include dcdef /* Device Codes */C#include ddbdef /* Device Data Block */-C#include ddtdef /* Driver dispatch table */-C#include devdef /* Device characteristics */ C#include dptdef /* Driver Prologue Table */-C#include dtndef /* Define the DTNs */_C#include dyndef /* Dynamic type definitions */ E#include embdvdef /* Error log entry for devices */_C#include fdtdef /* Function Decision Table */C#include fkbdef /* ForK Block */-C#include idbdef /* Interrupt Data Block */ C#include iocdef /* IOC constants */ B#include iodef /* I/O function codes */C#include irpdef /* I/O Request Packet */ C#include orbdef /* Object Rights Block */-D#include pagedef /* Get page definitions and disk block size */C#include pcbdef /* Process Control Block */ C#include pcidef /* PCIbus definitions */-B#include ssdef /* System service status codes */C#include stddef /* Common definitions */ C#include stsdef /* STatuS value fields */_C#include ucbdef /* Unit Control Block */*C#include vadef /* Virtual Address definitions */-C#include prvdef /* Privilege bit definitions */  =#ifndef IOC$K_BYTE_LANED /* Defined in V7-1.2 onwards... */ ?#define IOC$K_BYTE_LANED 1 /* Define this for V71R as well */|#endif4/* Define function prototypes for system routines */;#include acp_routines /* ACP$ and ACP_STD$ routines */-;#include erl_routines /* erl$ and erl_std$ routines */ ;#include exe_routines /* exe$ and exe_std$ routines */-;#include ioc_routines /* ioc$ and ioc_std$ routines */ ;#include ldr_routines /* ldr$ and ldr_std$ routines */ >/* #include sch_routines /@ sch$ and sch_std$ routines @/ */9#include starlet /* Define SYS$SETPRV prototypes */ )/* Define various device driver macros */f<#include vms_drivers /* Device driver support macros */8#include vms_macros /* Define bug_check and such */4/* Define the DEC C functions used by this driver */B#include builtins /* C builtin functions -- unused, but... */9#include string /* String rtns from "kernel CRTL" */ /* Define some useful types */?typedef unsigned short int WORD; /* Define a WORD (16 bits) */ >typedef unsigned char BYTE; /* Define a BYTE (8 bits) */=typedef unsigned int UINT; /* Usigned int (32 bits) */  -./* Define constants specific to this driver */7 /* Miscellaneous controller-related constants: */*\#define NUMBER_OF_NON_DMA_CRAMS 10*2 /* Number of CRAMs needed to map the non-DMA CSRs*/Y#define NUMBER_OF_DMA_CRAMS 8*2 /* Number of CRAMs needed to map the DMA CSRs */-H#define NUMBER_OF_CRAMS NUMBER_OF_NON_DMA_CRAMS+NUMBER_OF_DMA_CRAMS( /* Total number of CRAMs needed */:#define MODEL_LENGTH 40 /* Model string length */Y#define ERR_BYTES (EMB$C_DV_LENGTH+12+5+8)/* Size of error log buffer (in bytes) */-P#define RDSTATS_LEN ((TIMEOUT_TIME + 9) * 4)/* Size of RDSTATS_LEN buffer */4#define DEVICE_IPL 21 /* IPL of device */ /* Timeout times */ L#define DRQ_TIME (1000 * 1000) /* DRQ wait time (i.e, 1 millisecond) */O#define RESET_TIME 4 /* Reset time (seconds) (Ensure two passes) */_M#define READY_TIME (100 * 1000) /* Ready time (i.e., 100 microseconds) */_A#define TIMEOUT_TIME 15 /* I/O Timeout time (seconds) */J /* Geometry and transfer constants */e#define MAX_RETRY 8 /* Maximum number of retries */|e#define MAX_SECTOR 63 /* Max sector allowed [1:n] */ e#define MAX_HEAD 15 /* Max head allowed [0:n] */ge#define MAX_CYLINDER 16383 /* Max cylinder allowed [0:n] */ J /* (That could be as big as 65535 */J /* but real drives seem to top out at 14 bits = 0x3FFF) */j#define MAX_BLOCKS_PER_CYLINDER 1008 /* 63 sectors per track * 16 heads */e#define MAX_ATA_XFER 127 /* Max ATA/IDE transfer size (blocks) */ J /* */e#define MAX_ATAPI_512_XFER 127 /* Max ATAPI transfer size (blocks) on a 512-byte-sector device */-J /* */J /* Both of these are limited to 127 blocks (63.5K) just in */J /* case there are drives out there that have trouble with */J /* a 64K transfer. The Zip *IS* an example of such a drive -- */J /* It reports a SCSI parity Error is you command it to do */J /* a read with the byte count register = 0x0000. */J /* */J /* 127 is also a good value because it matches the size */J /* to which VMS segments transfers by default if ucb$l_maxbcnt */J /* is left set to 0. */J /* */J /* */e#define MAX_ATAPI_2K_XFER 120 /* Max ATAPI transfer size (blocks) on a 2Kbyte-sector device */ J /* */J /* In much the same fashion as we limit ATA and ATAPI_512 reads */J /* to 63.5K, we've also limited ATAPI_2K reads to 62K (31 2K */J /* blocks) to avoid any problems with drives that might have */J /* trouble with 64K transfers. */J /* */J /* We then further limit this to 60K so that we can fulfill, */J /* from within this 62K buffer, any arbitrary set of 512-byte */J /* blocks. And keeping this "by 4" will keep an aligned */J /* transfer aligned through all segments. */^#define BLK_SIZE_CAPACITY 8 /* Size of returned capacity data block in bytes */^#define BLK_SIZE_SENSE 18 /* Size of returned sense data block in bytes */^#define BLK_SIZE_512 512 /* Size of a VMS (and typical IDE/ATA) disk block in bytes */^#define BLK_SIZE_2048 2048 /* Size of a typical CD-ROM data block in bytes */^#define BLK_SIZE_2352 2352 /* Size of a maximum CD-ROM data block in bytes */a#define BLK_SIZE_63_5K (127*512) /* Size of a 127 block buffer in bytes */*C /* (maximum practical ATA/ATAPI transfer) */ra#define BLK_SIZE_64K (128*512) /* Size of a 128 block buffer in bytes */fC /* (maximum ATA/ATAPI transfer) */lH#define BLK_MASK IOC$M_BLOCK_BYTEMASK /* "Byte within block" mask */G#define BLK_SHIFT 9 /* Shift factor for blocks to bytes */s]#define XFER_BUFFER_SIZE BLK_SIZE_63_5K /* Size of each transfer buffer */ : /* */: /* When the page size calculation is applied, */: /* this will round up to a 64K buffer. But */: /* we want the smaller size in the constant so */: /* we don't take 0x00010000 (64K) and end up */: /* passing 0x0000 to the ATA and ATAPI drives. */: /* At least some of them (like the Zip) take */: /* that as 0, not 64K. */F//#saythis "XFER_BUFFER_MAP_PAGES ought to be calculated, not forced!"V#define XFER_BUFFER_MAP_PAGES 8 /* Number of map pages to cover the xfer buffer */e#define SENSE_BUFFER_SIZE ( (BLK_SIZE_CAPACITY>BLK_SIZE_SENSE) ? BLK_SIZE_CAPACITY : BLK_SIZE_SENSE )*: /* Size of each sense buffer (Max of those two) */: /* */: /* When the page size calculation is applied, */: /* this will round up to at least an 8K buffer. */: /* */: /* Later, rather than waste the space, maybe */: /* re-use the end of this buffer for the */: /* for the DMA PRDT table. */N#define PRDT_ENTRIES 8 /* Number of PRDT (Scatter/Gather Table) entries */3#define PRDT_TABLE_SIZE (PRDT_ENTRIES*sizeof(PRDT))n: /* Total size of the PRDT table */_#define PRDT_ADX_MASK ~(PRDT_TABLE_SIZE-1) /* Mask to force natural alignment of the PRDT */  e/* External references */ 9extern int MMG$GL_PAGE_SIZE; /* Page size in bytes */f?extern int MMG$GL_VPN_TO_VA; /* Page to byte shift count */gEextern PTE * const mmg$gl_sptbase; /* Base of system page table */p.extern DDT driver$ddt; /* Prototype DDT */.extern DPT driver$dpt; /* Prototype DPT */.extern FDT driver$fdt; /* Prototype FDT */.extern UCB *sys$ar_bootucb; /* Boot UCB */3/* Shortcuts for some of the external references */ 5#define _ddt driver$ddt /* Abbreviation for DDT */t5#define _dpt driver$dpt /* Abbreviation for DPT */e5#define _fdt driver$fdt /* Abbreviation for FDT */F a(/* OWN values used for debugging only */#ifdef TRACE_COMMONIJint trc_dummy; /* An ASCII tag to help you find this */Hint *trc_buf; /* Pointer to the base of the common tracing buffer */Jint trc_index; /* Current index into the common tracing buffer */Oint trc_buf_alloc=0; /* Have we already allocated this? */ f//#saythis "Remove these fixup counters after the root cause of the UCB corruption problem is found */Gint fixup_dummy; /* An ASCII tag to help you find this */iFint fixup_bcnt; /* The number of times we've fixed UCB$L_BCNT */Fint fixup_boff; /* The number of times we've fixed UCB$L_BOFF */Hint fixup_svapte; /* The number of times we've fixed UCB$L_SVAPTE */#endif -'/* Define the IDE disk controller CSRs.  *? * Here are the customary values for PC AT compatible machines.-> * Note that the CSRs may be anywhere, and are defined here as= * offsets from a base. That base is the address of the DATA- * register.9 * Primary Secondary48 * Data/Control Ports 1F0-1F7h 170-177h8 * Control/Status Ports 3F7-3F6h 377-376h9 * DMA Registers 90A0-90A7h 90A8-90AFh  * */ )/* Offsets for control block registers */*< /* Actual Legacy Address */< /* Primary Secondary */< /* ----------------------- */V#define REG_ALT_STS 0x206 /* READ: Alternate status 0x3F6 0x376 */V#define REG_DEV_CTL 0x206 /* WRITE:Device control 0x3F6 0x376 */V#define REG_DRV_ADDR 0x207 /* READ: FDC Drive address 0x3F7 0x377 */)/* Offsets for command block registers */ R#define REG_DATA 0 /* R/W: Data 0x1F0 0x170 */R#define REG_ERROR 1 /* READ: Error 0x1F1 0x171 */R#define REG_FEATURES 1 /* WRITE:Features 0x1F1 0x171 */R#define REG_SEC_CNT 2 /* R/W: Sector count 0x1F2 0x172 */R#define REG_SECTOR 3 /* R/W: Sector number 0x1F3 0x173 */R#define REG_CYL_LO 4 /* R/W: Cylinder (low) 0x1F4 0x174 */R#define REG_CYL_HI 5 /* R/W: Cylinder (high) 0x1F5 0x175 */R#define REG_DRV_HD 6 /* R/W: Drive / Head 0x1F6 0x176 */R#define REG_STATUS 7 /* READ: Status 0x1F7 0x177 */R#define REG_CMD 7 /* WRITE:Command 0x1F7 0x178 */!/* LBA fields (read and write) */gR#define REG_LBA_0 3 /* LBA bits 0-7 0x1F3 0x173 */R#define REG_LBA_8 4 /* LBA bits 8-15 0x1F4 0x174 */R#define REG_LBA_16 5 /* LBA bits 16-23 0x1F5 0x175 */R#define REG_LBA_24 6 /* LBA bits 24-27 0x1F6 0x176 *//* Device Control Register */iD#define CTL_M_nIEN 0x01 /* Interrupt enable bit for the host */:#define CTL_M_SRST 0x02 /* Host software reset bit *//* Drive/Head Register */*8#define DRVHD_M_BASE 0xA0 /* Bits 7 and 5 must be 1 */4#define DRVHD_M_LBA 0x40 /* LBA addressing bit *//* ATAPI Features Register */o(#define FEAT_M_DMA 0x01 /* Use DMA */3#define FEAT_M_OVL 0x02 /* Overlap operations */d,/* Status (and Alternate Status) Register */%#define STS_M_ERR 0x01 /* Error */ *#define STS_M_IDX 0x02 /* Index mark */.#define STS_M_CORR 0x04 /* Corrected data */,#define STS_M_DRQ 0x08 /* Data request */@#define STS_M_DSC 0x10 /* Drive seek complete (in old spec)*/<#define STS_M_SVC 0x10 /* Service request (in new spec)*/1#define STS_M_DWF 0x20 /* Drive write fault */r+#define STS_M_DRDY 0x40 /* Drive ready */s$#define STS_M_BSY 0x80 /* Busy */##define STS_V_ERR 0 /* Error */g(#define STS_V_IDX 1 /* Index mark */,#define STS_V_CORR 2 /* Corrected data */*#define STS_V_DRQ 3 /* Data request */>#define STS_V_DSC 4 /* Drive seek complete (in old spec)*/:#define STS_V_SVC 5 /* Service request (in new spec)*//#define STS_V_DWF 6 /* Drive write fault */ )#define STS_V_DRDY 7 /* Drive ready */l"#define STS_V_BSY 8 /* Busy */ 1/* Offsets for DMA block (SFF-8038i) registers */u< /* Actual Legacy Address */< /* Primary Secondary */< /* ----------------------- */Q#define DMA_CMD 0 /* R/W: Command 0xnnnnn0 0xnnnnn8 */oQ#define DMA_DS1 1 /* R/W: Device-Specific 1 0xnnnnn1 0xnnnnn9 */ Q#define DMA_STS 2 /* R/W: Status 0xnnnnn2 0xnnnnnA */tQ#define DMA_DS2 3 /* R/W: Device-Specific 2 0xnnnnn3 0xnnnnnB */tQ#define DMA_AD0 4 /* R/W: PRD Table Address LSB 0xnnnnn4 0xnnnnnC */mQ#define DMA_AD1 5 /* R/W: : 0xnnnnn5 0xnnnnnD */KQ#define DMA_AD2 6 /* R/W: : 0xnnnnn6 0xnnnnnE */aQ#define DMA_AD3 7 /* R/W: PRD Table Address MSB 0xnnnnn7 0xnnnnnF */-&/* DMA (SFF-8038i) Command Register */M#define DMA_CMD_M_INBOUND 0x08 /* The DMA direction will be "INBOUND" */p1 /* (That is, a disk READ/memory WRITE!) */iM#define DMA_CMD_M_OUTBOUND 0x00 /* The DMA direction will be "OUTBOUND" */-1 /* (That is, a disk WRITE/memory READ!) */eM#define DMA_CMD_M_ACTIVE 0x01 /* Make the DMA controller active */rM#define DMA_CMD_M_INACTIVE 0x00 /* Make the DMA controller inactive */n%/* DMA (SFF-8038i) Status Register */eL#define DMA_STS_M_SIMPLEX 0x80 /* The DMA controller can only operate */1 /* one disk at a time (Master or Slave) */tL#define DMA_STS_M_DRV1 0x40 /* Drive 1 is DMA-capable */L#define DMA_STS_M_DRV0 0x20 /* Drive 0 is DMA-capable */L#define DMA_STS_M_RSV4 0x10 /* */L#define DMA_STS_M_RSV3 0x08 /* */L#define DMA_STS_M_INT 0x04 /* Interrupt */L#define DMA_STS_M_ERR 0x02 /* Error */L#define DMA_STS_M_ACTIVE 0x01 /* Active *//* DMA (SFF-8038i) PRDT */typedef struct { /* The PRDT structure */+ UINT phys_adx; /* Physical address */u3 WORD count; /* Byte count for this region *//M WORD flags; /* Flags: Only high bit is meaningful as EDT (end) flag */ } PRDT;sI#define DMA_PRDT_M_EDT 0x8000 /* The EDT (End-Descriptor-Table) flag */  $/* ATAPI magic "Signature" values */L#define ATAPI_SIG_STS 0x00 /* In the Status/Alternate Status Register */\#define ATAPI_SIG_STSE 0x01 /* In the Status/Alternate Status Register (Error bit set) */B#define ATAPI_SIG_CYL_HI 0xEB /* In the Cylinder "Hi" Register */B#define ATAPI_SIG_CYL_LO 0x14 /* In the Cylinder "Lo" Register */ c/* ATA Commands/ * * Notes:a *> * o All of these commands are issued to the command registerB * in the IDE/ATA "Task file" (the controller's register file). *@ * o CMD_ATA_ATAPI_SOFT_RESET belongs here because that command@ * is issued to the task file also, and not sent as a command * within an ATAPI packet.  *( * o This list is complete as of ATA-3. * */df#define CMD_ATA_NOP 0x00 /* NOP */f#define CMD_ATCʇ~ DQDRIVER.BCKѡ![FREEWAREV5.DQDRIVER]DQDRIVER.C;1rd|A_ATAPI_SOFT_RESET 0x08 /* Reset an ATAPI drive */f#define CMD_ATA_RECALIBRATE 0x10 /* Recalibrate */f#define CMD_ATA_READ_SECS 0x20 /* Read Sector(s) w/ retries */f#define CMD_ATA_READ_SECS_WO_RET 0x21 /* Read Sector(s) w/o retries */f#define CMD_ATA_READ_LONG 0x22 /* Read Long (i.e., including ECC bytes) w/ retries */f#define CMD_ATA_READ_LONG_WO_RET 0x23 /* Read Long (i.e., including ECC bytes) w/o retries */f#define CMD_ATA_WRITE_SECS 0x30 /* Write Sector(s) w/ retries */f#define CMD_ATA_WRITE_SECS_WO_RET 0x31 /* Write Sector(s) w/o retries */f#define CMD_ATA_WRITE_LONG 0x32 /* Write Long (i.e., including ECC bytes) w/ retries */f#define CMD_ATA_WRITE_LONG_WO_RET 0x33 /* Write Long (i.e., including ECC bytes) w/o retries */f#define CMD_ATA_WRITE_VFY 0x3C /* Write Verify */f#define CMD_ATA_READ_VFY_SECS 0x40 /* Read Verify Sector(s) w/ retries */f#define CMD_ATA_READ_VFY_SECS_WO_RET 0x41 /* Read Verify Sector(s) w/o retries */f#define CMD_ATA_FORMAT_TRACK 0x50 /* Format Track */f#define CMD_ATA_SEEK 0x70 /* Seek */f#define CMD_ATA_80 0x80 /* Unused group 0x8n */f#define CMD_ATA_EXEC_DEV_DIAGS 0x90 /* Execute Device Diagnostic */f#define CMD_ATA_INIT_DEV_PARAMS 0x91 /* Initialize Device Parameters */f#define CMD_ATA_DOWNLOAD_UCODE 0x92 /* Download Microcode */f#define CMD_ATA_STANDBY_IMMED_94 0x94 /* Standby Immediate 94 */f#define CMD_ATA_IDLE_IMMED_95 0x95 /* Idle Immediate 95 */f#define CMD_ATA_STANDBY_96 0x96 /* Standby 96 */f#define CMD_ATA_IDLE_97 0x97 /* Idle 97 */f#define CMD_ATA_CHK_PWR_MODE_98 0x98 /* Check Power Mode 98 */f#define CMD_ATA_SLEEP_99 0x99 /* Sleep 99 */f#define CMD_ATA_PACKET_CMD 0xA0 /* Send a command packet to ATAPI drive */f#define CMD_ATA_PACKET_IDENTIFY 0xA1 /* Get an ATAPI drive to identify itself */f#define CMD_ATA_SMART_DSBL_OPS 0xB0 /* SMART Disable Operations */f#define CMD_ATA_SMART_ENBLDSBL_ATTR_AUTO 0xB0 /* SMART Enable/Disable Attribute Autosave */f#define CMD_ATA_SMART_ENBL_OPERATIONS 0xB0 /* SMART Enable Operations */f#define CMD_ATA_SMART_READ_ATTR_THRESH 0xB0 /* SMART Read Attribute Thresholds */f#define CMD_ATA_SMART_RETURN_STATUS 0xB0 /* SMART Return Status */f#define CMD_ATA_SECUR_SET_PSWD_OBS 0xBA /* Security Set Password (Obsolete Version) */f#define CMD_ATA_SECUR_UNLOCK_OBS 0xBB /* Security Unlock (Obsolete Version) */f#define CMD_ATA_SECUR_ERASE_PREPARE_OBS 0xBC /* Security Erase Prepare (Obsolete Version) */f#define CMD_ATA_SECUR_ERASE_UNIT_OBS 0xBD /* Security Erase Unit (Obsolete Version) */f#define CMD_ATA_SECUR_FREEZE_LOCK_OBS 0xBE /* Security Freeze Lock (Obsolete Version) */f#define CMD_ATA_SECUR_DSBL_PSWD_OBS 0xBF /* Security Disable Password (Obsolete Version) */f#define CMD_ATA_READ_MULTIPLE 0xC4 /* Read Multiple */f#define CMD_ATA_WRITE_MULTI 0xC5 /* Write Multiple */f#define CMD_ATA_SET_MULTI_MODE 0xC6 /* Set Multiple Mode */f#define CMD_ATA_READ_DMA 0xC8 /* Read DMA w/ retries */f#define CMD_ATA_READ_DMA_WO_RET 0xC9 /* Read DMA w/o retries */f#define CMD_ATA_WRITE_DMA 0xCA /* Write DMA w/ retries */f#define CMD_ATA_WRITE_DMA_WO_RET 0xCB /* Write DMA w/o retries */f#define CMD_ATA_DOOR_LOCK 0xDE /* Door Lock */f#define CMD_ATA_DOOR_UNLOCK 0xDF /* Door Unlock */f#define CMD_ATA_STANDBY_IMMED_E0 0xE0 /* Standby Immediate E0 */f#define CMD_ATA_IDLE_IMMED_E1 0xE1 /* Idle Immediate E1 */f#define CMD_ATA_STANDBY_E2 0xE2 /* Standby E2 */f#define CMD_ATA_IDLE_E3 0xE3 /* Idle E3 */f#define CMD_ATA_READ_BUFFER 0xE4 /* Read Buffer */f#define CMD_ATA_CHK_PWR_MODE_E5 0xE5 /* Check Power Mode E5 */f#define CMD_ATA_SLEEP_E6 0xE6 /* Sleep E6 */f#define CMD_ATA_WRITE_BUFFER 0xE8 /* Write Buffer */f#define CMD_ATA_IDENTIFY_DEV 0xEC /* Identify Device */f#define CMD_ATA_MEDIA_EJECT 0xED /* Media Eject */f#define CMD_ATA_IDENTIFY_DEV_DMA 0xEE /* Identify Device DMA */f#define CMD_ATA_SET_FEATURES 0xEF /* Set Features */f#define CMD_ATA_SECUR_SET_PSWD 0xF1 /* Security Set Password */f#define CMD_ATA_SECUR_UNLOCK 0xF2 /* Security Unlock */f#define CMD_ATA_SECUR_ERASE_PREPARE 0xF3 /* Security Erase Prepare */f#define CMD_ATA_SECUR_ERASE_UNIT 0xF4 /* Security Erase Unit */f#define CMD_ATA_SECUR_FREEZE_LOCK 0xF5 /* Security Freeze Lock */f#define CMD_ATA_SECUR_DSBL_PSWD 0xF6 /* Security Disable Password */ /* ATAPI Commandse * * Notes:t *1 * o These commands are issued in ATAPI packets.  *> * o These commands are now defined by Annex B of the "SCSI-3< * Multi-Media Commands". Details of the commands can be- * found in several different SCSI specs:f *, * - SCSI-3 Primary Commands (SPC), * - SCSI-3 Block Commands (SBC), * - SCSI-3 Multimedia Commands (MMC) * *( * o This list is complete as of MMC-3. * */ f#define CMD_ATAPI_TEST_UNIT_READY 0x00 /* Test Unit Ready (SPC) */f#define CMD_ATAPI_REQUEST_SENSE 0x03 /* Request Sense (SPC) */f#define CMD_ATAPI_FORMAT_UNIT 0x04 /* Format Unit */f#define CMD_ATAPI_INQUIRY 0x12 /* Inquiry (SPC) */f#define CMD_ATAPI_START_STOP_UNIT 0x1B /* Start/Stop Unit (SBC) */f#define CMD_ATAPI_PREVENT_ALLOW 0x1E /* Prevent/Allow Medium Removal (SPC) */f#define CMD_ATAPI_READ_CAPACITY 0x25 /* Read Recorded Capacity */f#define CMD_ATAPI_READ_10 0x28 /* Read (10) (SBC) */f#define CMD_ATAPI_WRITE_10 0x2A /* Write (10) (SBC) */f#define CMD_ATAPI_SEEK 0x2B /* Seek (SBC) */f#define CMD_ATAPI_SYNCHRONIZE_CACHE 0x35 /* Synchronize Cache */f#define CMD_ATAPI_READ_SUBCHANNEL 0x42 /* Read Sub-channel */f#define CMD_ATAPI_READ_TOC_PMA_ATIP 0x43 /* Read TOC/PMA/ATIP */f#define CMD_ATAPI_READ_HEADER 0x44 /* Read Header */f#define CMD_ATAPI_PLAY_AUDIO_10 0x45 /* Play Audio (10) */f#define CMD_ATAPI_PLAY_AUDIO_MSF 0x47 /* Play Audio MSF */f#define CMD_ATAPI_PAUSE_RESUME 0x4B /* Pause/Resume */f#define CMD_ATAPI_STOP_PLAY_SCAN 0x4E /* Stop Play/Scan */f#define CMD_ATAPI_READ_DISK_INFORMATION 0x51 /* Read Disk Information */f#define CMD_ATAPI_READ_TRACK_INFORMATION 0x52 /* Read Track Information */f#define CMD_ATAPI_RESERVE_TRACK 0x53 /* Reserve Track */f#define CMD_ATAPI_SEND_OPC_INFORMATION 0x54 /* Send OPC Information */f#define CMD_ATAPI_MODE_SELECT_10 0x55 /* Mode Select (10) (SPC) */f#define CMD_ATAPI_REPAIR_TRACK 0x58 /* Repair Track */f#define CMD_ATAPI_READ_MASTER_CUE 0x59 /* Read Master Cue */f#define CMD_ATAPI_MODE_SENSE_10 0x5A /* Mode Sense (10) (SPC) */f#define CMD_ATAPI_CLOSE_TRACK_SESSION 0x5B /* Close Track/Session */f#define CMD_ATAPI_READ_BUFFER_CAPACITY 0x5C /* Read Buffer Capacity */f#define CMD_ATAPI_SEND_CUE_SHEET 0x5D /* Send Cue Sheet */f#define CMD_ATAPI_60 0x60 /* Unused group 0x6n */f#define CMD_ATAPI_70 0x70 /* Unused group 0x7n */f#define CMD_ATAPI_80 0x80 /* Unused group 0x8n */f#define CMD_ATAPI_90 0x90 /* Unused group 0x9n */f#define CMD_ATAPI_BLANK 0xA1 /* Blank */f#define CMD_ATAPI_PLAY_AUDIO_12 0xA5 /* Play Audio (12) */f#define CMD_ATAPI_LOAD_UNLOAD_CD 0xA6 /* Load/Unload CD */f#define CMD_ATAPI_READ_12 0xA8 /* Read (12) (SBC) */f#define CMD_ATAPI_WRITE_12 0xAA /* Write (12) (SBC) */f#define CMD_ATAPI_READ_CD_MSF 0xB9 /* Read CD MSF */f#define CMD_ATAPI_SCAN 0xBA /* Scan */f#define CMD_ATAPI_SET_CD_SPEED 0xBB /* Set CD Speed */f#define CMD_ATAPI_PLAY_CD 0xBC /* Play CD */f#define CMD_ATAPI_MECHANISM_STATUS 0xBD /* Mechanism Status */f#define CMD_ATAPI_READ_CD 0xBE /* Read CD */f#define CMD_ATAPI_C0 0xC0 /* Unused group 0xCn */f#define CMD_ATAPI_D0 0xD0 /* Unused group 0xDn */f#define CMD_ATAPI_E0 0xE0 /* Unused group 0xEn */f#define CMD_ATAPI_F0 0xF0 /* Unused group 0xFn */ tE/* Set up the table for CRAM initialization. This table contains the_D * CSR offset, the command used in this CRAM and the byte lane shift= * value. The byte lane shift value is computed at run time.  * */Dtypedef struct/ { /* The CRAM initialization structure */# int cmd; /* Command index */( int offset; /* Register offset */- int shift; /* Byte lane shift count */  } cram_item;4/* Define the indices in this (and the UCB) table */#define RD_ALT_STS 0*#define WT_DEV_CTL 1dC#define RD_DRV_ADDR 2 /* Belongs to the FDC -- Don't read! */dC#define WT_DRV_ADDR 3 /* Read-only register -- Don't write! */ #define RD_DATA 4 #define WT_DATA 5#define RD_ERROR 6o#define WT_FEATURES 7u#define RD_SEC_CNT 8 #define WT_SEC_CNT 9e#define RD_SECTOR 10 #define WT_SECTOR 11c#define RD_CYL_LO 12 #define WT_CYL_LO 13-#define RD_CYL_HI 14M#define WT_CYL_HI 15 #define RD_DRV_HD 16n#define WT_DRV_HD 17 #define RD_STS 18i#define WT_CMD 19n/* DMA (SFF-8038i) CRAMs */2#define RD_DMA_CMD 20 #define WT_DMA_CMD 21t#define RD_DMA_DS1 22*#define WT_DMA_DS1 23 #define RD_DMA_STS 24d4#define WT_DMA_STS 25 /* Probably Read-ONLY! */#define RD_DMA_DS2 26d#define WT_DMA_DS2 27R#define RD_DMA_AD0 28n#define WT_DMA_AD0 29f#define RD_DMA_AD1 30 #define WT_DMA_AD1 31n#define RD_DMA_AD2 32 #define WT_DMA_AD2 33R#define RD_DMA_AD3 34 #define WT_DMA_AD3 35A 0G#define cram_def(cmd,csr) CRAMCMD$K_##cmd##32, ##csr, ((##csr & 3) <<3)i&cram_item cram_init[NUMBER_OF_CRAMS] = {a! cram_def(RDBYTE,REG_ALT_STS),! cram_def(WTBYTE,REG_DEV_CTL), L cram_def(RDBYTE,REG_DRV_ADDR), /* Belongs to FDC ------ Don't read! */L cram_def(WTBYTE,REG_DRV_ADDR), /* Read-only register -- Don't write! */ cram_def(RDWORD,REG_DATA), cram_def(WTWORD,REG_DATA), cram_def(RDBYTE,REG_ERROR)," cram_def(WTBYTE,REG_FEATURES),! cram_def(RDBYTE,REG_SEC_CNT), ! cram_def(WTBYTE,REG_SEC_CNT),a cram_def(RDBYTE,REG_SECTOR), cram_def(WTBYTE,REG_SECTOR), cram_def(RDBYTE,REG_CYL_LO), cram_def(WTBYTE,REG_CYL_LO), cram_def(RDBYTE,REG_CYL_HI), cram_def(WTBYTE,REG_CYL_HI), cram_def(RDBYTE,REG_DRV_HD), cram_def(WTBYTE,REG_DRV_HD), cram_def(RDBYTE,REG_STATUS), cram_def(WTBYTE,REG_CMD), /* DMA (SFF-8038i) CRAMs */  cram_def(RDBYTE,DMA_CMD),u cram_def(WTBYTE,DMA_CMD),  cram_def(RDBYTE,DMA_DS1),2 cram_def(WTBYTE,DMA_DS1),  cram_def(RDBYTE,DMA_STS),_Q cram_def(WTBYTE,DMA_STS), /* Probably read-only register -- Don't write! */P cram_def(RDBYTE,DMA_DS2),  cram_def(WTBYTE,DMA_DS2),I cram_def(RDBYTE,DMA_AD0),s cram_def(WTBYTE,DMA_AD0),e cram_def(RDBYTE,DMA_AD1),O cram_def(WTBYTE,DMA_AD1),h cram_def(RDBYTE,DMA_AD2),l cram_def(WTBYTE,DMA_AD2),n cram_def(RDBYTE,DMA_AD3),E cram_def(WTBYTE,DMA_AD3) }; AK/* Define Device-Dependent Unit Control Block with extensions for DQ devicea * */n&#define MAX_DIAGNOSE_COMMAND_LENGTH 12+#define MAX_DIAGNOSE_DATA_SIZE BLK_SIZE_64Ks#define MIN(x,y) (xucb$r_dq_ucb_' d m//* Define the Identify Drive information buffern@ * Use the nomember_alignment to make sure that this structure * matches what the drive uses * */_#pragma member_alignment save +#pragma nomember_alignment typedef structD { /* Word(s): ATA-5 description */A /*--------------------------------------------------------*/ P WORD config; /* 0: Configuration information */O WORD cyls; /* 1: Number of cylinders */ O WORD rsvd2; /* 2: Reserved word */ O WORD heads; /* 3: Number of heads */BV WORD ubytes_track; /* 4: Unformatted bytes/track (retired) */V WORD ubytes_sector; /* 5: Unformatted bytes/sector (retired) */Q WORD sectors; /* 6: Number of sectors */fT WORD unique7[3]; /* 7-9: Vendor unique (retired) */Z char serial_number[20]; /* 10-19: ASCII serial number */U WORD buffer_type; /* 20: Buffer type (retired) */e[ WORD buffer_size_blocks; /* 21: Buffer size (in blocks) (retired) */SS WORD ecc_bytes; /* 22: Number of ECC bytes/sector (obsolete) */P] char firmware_revision[8]; /* 23-26: ASCII firmware revision */Rb char model_number[MODEL_LENGTH]; /* 27-46: ASCII drive model */U BYTE rw_multiple; /* 47: Max number of sectors/interrupt */fR BYTE unique47; /* 47.5: 0x80 */P WORD rsvd48; /* 48: Reserved */X WORD capabilities_49; /* 49: Capabilities */X WORD capabilities_50; /* 50: More Capabilities */S WORD pio_cycle; /* 51: PIO data transfer mode */ S WORD dma_cycle; /* 52: DMA I/O cycle times (retired) */)T WORD valid_bits; /* 53: Valid bits for several fields */S WORD curr_cyls; /* 54: Current logical cylinder count */fT WORD curr_heads; /* 55: Current logical head count */V WORD curr_sectors; /* 56: Current logical sector count */V int curr_capacity; /* 57-58: Current capacity in sectors */Y WORD multiple_sectors; /* 59: Current sectors/interrupt setting */1Y int lba_total_blocks; /* 60-61: Total number of user-adx'ible sectors */ X WORD single_word_dma; /* 62: Single word DMA info (retired) */W WORD multi_word_dma; /* 63: Multi word DMA info */d\ WORD pio_modes_supported; /* 64: Advanced PIO modes supported */[ WORD min_dma_cycle_time; /* 65: Min multiword DMA transfer cycle time */ [ WORD rec_dma_cycle_time; /* 66: Rec multiword DMA transfer cycle time */ [ WORD min_pio_cycle_time; /* 67: Min non-IORDY PIO transfer cycle time */c` WORD min_iordy_pio_cycle_time; /* 68: Min IORDY PIO transfer cycle time */P WORD rsvd69; /* 69: Reserved (for command queuing) */P WORD rsvd70; /* 70: Reserved (for command queuing) */W WORD atapi_pkt_time; /* 71: ns from PACKET cmd to bus release (ATAPI) *//W WORD atapi_svc_time; /* 72: ns from SERVICE to clearing BSY (ATAPI) */ P WORD rsvd73; /* 73: Reserved (ATAPI) */P WORD rsvd74; /* 74: Reserved (ATAPI) */U WORD queue_depth; /* 75: Maximum queue depth */_P WORD rsvd76; /* 76: Reserved */P WORD rsvd77; /* 77: Reserved */P WORD rsvd78; /* 78: Reserved */P WORD rsvd79; /* 79: Reserved */] WORD major_version_number; /* 80: Major version number (e.g, 4=ATA/ATAPI-4) */A] WORD minor_version_number; /* 81: Minor version number */L\ WORD cmd_set_supported_1; /* 82: Command set supported, word 1 */\ WORD cmd_set_supported_2; /* 83: Command set supported, word 2 */\ WORD cmd_set_supported_x; /* 84: Command set supported, extension */Z WORD cmd_set_enabled_1; /* 85: Command set enabled, word 1 */Z WORD cmd_set_enabled_2; /* 86: Command set enabled, word 2 */X WORD cmd_set_default; /* 87: Command set default */S WORD ultra_dma; /* 88: Ultra DMA control */5R WORD dse_time; /* 89: Data Security Erase time (~secs/2) */Z WORD enhanced_dse_time; /* 90: Enhanced Data Security Erase time (~secs/2) */V WORD cur_apm_value; /* 91: Current Advanced Power Management value */Z WORD master_passwd_rev; /* 92: Master Password Revision Code */T WORD rsvd94[33]; /* 94-126: Reserved */a WORD media_status_notification; /* 127: Removable Media Status Notification */ X WORD security_status; /* 128: Security Status */\ WORD vendor_specific[31]; /* 129-159: Vendor specific */U WORD rsvd160[96]; /* 160-255: Reserved */P } ID_PAGE; #pragma member_alignment restore/* Capabilities bits */C2#define CAP_M_LBA 0x200 /* Handles LBA mode */-#define CAP_M_DMA 0x100 /* Handles DMA */  /6#define IS_SET( reg, bits ) ( (reg & bits) == bits )6#define IS_CLEAR( reg, bits ) ( (reg & bits) == 0 )8#define $SUCCESS( code ) ( (code & STS$M_SUCCESS) == 1)8#define $FAIL( code ) ( (code & STS$M_SUCCESS) == 0)#define TRUE 1 #define FALSE 0e ;/* Prototypes for driver routines defined in this module */  iint atapi_packet_command( DQ_UCB *ucb, BYTE *buffer, int xfer_req, int *xfer_cnt, int dma_flag ); 8 /* xfer_req is implicit in the command packet */0int atapi_process_size( DQ_UCB *ucb );>int atapi_read_capacity( DQ_UCB *ucb, BYTE *buffer );>int atapi_request_sense( DQ_UCB *ucb, BYTE *buffer );/int diagnose( DQ_UCB *ucb);M0int atapi_xlate_error_to_vms( DQ_UCB *ucb );#ifdef BREAKPOINTS9void call_ini$brk( int code, int p1, int p2, int p3 ); #endifOvoid compute_address( DQ_UCB *ucb, int *sec, int *head, int *cyl );_8int ctrl_init( IDB *idb, DDB *ddb, CRB *crb );int driver$init_tables();0int datacheck( DQ_UCB *ucb );Aint dq_wfikpch( KPB *kpb, int orig_ipl, int erl_param );R0int drvclr( DQ_UCB *ucb );Tint fetch_drive_info( DQ_UCB *ucb, int atapi_flag, int init_time_flag );0int fill_packet_w_adx( DQ_UCB *ucb );9BYTE inp( DQ_UCB *ucb, int reg ); 9WORD inpw( DQ_UCB *ucb, int reg ); #void isr( IDB *idb); 0void load_prdt( DQ_UCB *ucb );0int locate_dma_regs( DQ_UCB *ucb );HBYTE *map_user_buffer( DQ_UCB *ucb, int offset, int length );Mvoid move_sec_from_drive( DQ_UCB *ucb, BYTE *buffer, int bytecount ); Mvoid move_sec_to_drive( DQ_UCB *ucb, BYTE *buffer, int bytecount );ADvoid out( DQ_UCB *ucb, int reg, BYTE data );Dvoid outw( DQ_UCB *ucb, int reg, WORD data );Dvoid outw_t( DQ_UCB *ucb, int reg, WORD data );Dint packack( DQ_UCB *ucb, int init_time_flag );0int process_drive_info( DQ_UCB *ucb );Eint rct_fdt( IRP *irp, PCB *pcb, DQ_UCB *ucb, CCB *ccb );fEint rdstats_fdt( IRP *irp, PCB *pcb, DQ_UCB *ucb, CCB *ccb );1Nint diagnose_fdt( IRP *irp, PCB *pcb, DQ_UCB *ucb, CCB *ccb );0int read( DQ_UCB *ucb );Mint read_dispatcher( DQ_UCB *ucb, int xfer_req, int *xfer_cnt );mint read_ata_seg_pio( DQ_UCB *ucb, int xfer_req, int *xfer_cnt ); /* Buffer adx comes from UCB */3mint read_ata_seg_dma( DQ_UCB *ucb, int xfer_req, int *xfer_cnt ); /* Buffer adx comes from UCB */d[int read_atapi_512_seg( DQ_UCB *ucb, int xfer_req, int *xfer_cnt, int dma_flag );R) /* Buffer adx comes from UCB */_[int read_atapi_2K_seg ( DQ_UCB *ucb, int xfer_req, int *xfer_cnt, int dma_flag );t) /* Buffer adx comes from UCB */W0int readrct( DQ_UCB *ucb );@void regdump( BYTE *buffer, int arg_2, DQ_UCB *ucb );0int reset_ctrl( DQ_UCB *ucb );0int seek( DQ_UCB *ucb );Hint set_features( DQ_UCB *ucb, int feature, int value );0int set_geom( DQ_UCB *ucb );=int sleep( DQ_UCB *ucb, int seconds );E$void startio( KPB *kpb );Ovoid struc_init( CRB *crb, DDB *ddb, IDB *idb, ORB *orb, DQ_UCB *ucb ); Ovoid struc_reinit( CRB *crb, DDB *ddb, IDB *idb, ORB *orb, DQ_UCB *ucb );W#ifdef TRACINGCvoid trace( DQ_UCB *ucb, int code, int bpt );#endif1int unit_init( IDB *idb, DQ_UCB *ucb );S<void unit_init_fork( void *fr3, IDB *idb, DQ_UCB *ucb );0int unload( DQ_UCB *ucb );0int wait_busy( DQ_UCB *ucb );0int wait_drq( DQ_UCB *ucb );0int wait_ready( DQ_UCB *ucb );0int write( DQ_UCB *ucb );Mint write_dispatcher(  /Ǚ~ DQDRIVER.BCKѡ![FREEWAREV5.DQDRIVER]DQDRIVER.C;1ru,  DQ_UCB *ucb, int xfer_req, int *xfer_cnt ); mint write_ata_seg_pio( DQ_UCB *ucb, int xfer_req, int *xfer_cnt ); /* Buffer adx comes from UCB */mint write_ata_seg_dma( DQ_UCB *ucb, int xfer_req, int *xfer_cnt ); /* Buffer adx comes from UCB */C[int write_atapi_512_seg( DQ_UCB *ucb, int xfer_req, int *xfer_cnt, int dma_flag );) /* Buffer adx comes from UCB */o[int write_atapi_2K_seg( DQ_UCB *ucb, int xfer_req, int *xfer_cnt, int dma_flag );d) /* Buffer adx comes from UCB */I l#ifdef TRACE_PER_DRIVEV/* TRACE - This routine is used to write a debugging entry in our tracing tumble-table * * Input:U" * ucb pointer to the UCB5 * data A longword to be written to the table Q * bpt A flag as to whether or not to do a breakpoint trap after tracingo * * Output: * none * * Side effect(s):8 * The tracing buffer and its index value are updated. * ini$brk may be invoked. * */ ,void trace( DQ_UCB *ucb, int code, int bpt ) { & ADP *adp; /* Address of ADP */9 int *ptr; /* Bind onto the ucb's pointer value *//4 int save_ipl; /* Place to save the old IPL */5 adp = baseucb.ucb$ps_adp; /* Get ADP address */ > device_lock( adp->adp$ps_spinlock, RAISE_IPL, &save_ipl );- /* Ensure exclusive access at IPL 31 */*> ptr = &ucb->ucb$l_trc_index; /* Fill our local pointer */j code = code | (ucb->ucb$l_trc_unit<<28); /* Shift the unit into the high nibble, .OR. into the code */C ucb->ucb$l_trc_buf[*ptr] = code; /* Save the new trace code */I( (*ptr)++; /* Bump the pointer */0 if (*ptr >= TRACING) /* Beyond the end? */6 *ptr = 0; /* If so, back to the beginning */K ucb->ucb$l_trc_buf[*ptr] = 0x0FEEEEEE; /* Mark the current end point */ A device_unlock( adp->adp$ps_spinlock, save_ipl, SMP_RESTORE );b#ifdef BREAKPOINTS5 /* Release exclusive access, back to old IPL */ : if (bpt) /* Does this caller want a breakpoint? */X call_ini$brk( code, (int) ucb, (int) ucb->ucb$l_trc_buf, ucb->ucb$l_trc_index ); /* If so, make it so */ #endif }o2#define TRACE( data ) trace( ucb, data, FALSE )2#define BPTRACE( data ) trace( ucb, data, TRUE )#endif u#ifdef TRACE_COMMONbV/* TRACE - This routine is used to write a debugging entry in our tracing tumble-table * * Input: " * ucb pointer to the UCB5 * data A longword to be written to the table Q * bpt A flag as to whether or not to do a breakpoint trap after tracingt * * Output: * none * * Side effect(s):8 * The tracing buffer and its index value are updated. * ini$brk may be invoked. * */k,void trace( DQ_UCB *ucb, int code, int bpt ) {a& ADP *adp; /* Address of ADP */4 int save_ipl; /* Place to save the old IPL */5 adp = baseucb.ucb$ps_adp; /* Get ADP address */c> device_lock( adp->adp$ps_spinlock, RAISE_IPL, &save_ipl );- /* Ensure exclusive access at IPL 31 */oj code = code | (ucb->ucb$l_trc_unit<<28); /* Shift the unit into the high nibble, .OR. into the code */> trc_buf[trc_index] = code; /* Save the new trace code */* trc_index++; /* Bump the pointer */5 if (trc_index >= TRACING) /* Beyond the end? */s: trc_index = 0; /* If so, back to the beginning */F trc_buf[trc_index] = 0x0FEEEEEE; /* Mark the current end point */A device_unlock( adp->adp$ps_spinlock, save_ipl, SMP_RESTORE ); #ifdef BREAKPOINTS5 /* Release exclusive access, back to old IPL */ : if (bpt) /* Does this caller want a breakpoint? */B call_ini$brk( code, (int) ucb, (int) trc_buf, trc_index ); /* If so, make it so */ #endif }l2#define TRACE( data ) trace( ucb, data, FALSE )2#define BPTRACE( data ) trace( ucb, data, TRUE )#endif gI/* One way or another, make sure we have TRACE and BPTRACE macros definedm *0 * If neither real tracing routine defined them, * then define them as nothing.o * */I #ifndef TRACEf#define TRACE( data ) #define BPTRACE( data )t#endif 2/* Define or null-out the debugging breakpoints */#ifdef BREAKPOINTS?/* CALL_INI$BRK - This routine is used to help debug the driver  * * Input: 8 * code A code to clue me in as to who called us7 * ucb The affected unit's ucb, also as a cluep * * Output:D * The side-effect of a breakpoint trap if XDELTA is installed.$ * Look in R16 to see the code.6 * Typically, look in R17 to see the ucb address.< * Typically, look in R18 to see the trace buffer base.= * Typically, look in R19 to see the trace buffer index.t * */ 5void call_ini$brk( int code, int p1, int p2, int p3 )d {t( ini$brk( ); /* And then break */ } C#define BREAK( code, p1, p2, p3 ) call_ini$brk( code, p1, p2, p3 )d#else !#define BREAK( code, p1, p2, p3 ) #endif 0/* DRIVER$INIT_TABLES - Initialize Driver Tables *F * This routine is used to initialize the driver tables. The DPT, DDT! * and FDT structures are set up.S * * Usage:n& * status = driver$init_tables(); * * Input:a * none * * Output: * none * * Return value:1 * SS$_NORMAL -- tables successfully set up * */dint driver$init_tables( void ) {h^/* BREAK( 0x00010000, 0, 0, 0 ); /@ BREAK: driver$init_tables called -- Can't TRACE yet */>/* Finish initialization of the Driver Prologue Table (DPT) */A ini_dpt_name( &_dpt, "DQDRIVER" ); /* Driver name */dA ini_dpt_adapt( &_dpt, AT$_ISA ); /* ISA bus device */rH ini_dpt_flags( &_dpt, DPT$M_SMPMOD|DPT$M_SVP);/* Set flags */9 ini_dpt_maxunits( &_dpt, 4 ); /* 4 units max */cA ini_dpt_ucbsize( &_dpt, sizeof(DQ_UCB) ); /* UCB size */*H ini_dpt_struc_init( &_dpt, struc_init ); /* Structure init rtn */K ini_dpt_struc_reinit( &_dpt, struc_reinit ); /* Structure reinit rtn */M ini_dpt_ucb_crams( &_dpt, NUMBER_OF_CRAMS ); /* Allocate some CRAMs */ ( ini_dpt_end( &_dpt ); >/* Finish initialization of the Driver Dispatch Table (DDT) */I ini_ddt_ctrlinit( &_ddt, ctrl_init ); /* Controller init rtn */ C ini_ddt_unitinit( &_ddt, unit_init ); /* Unit init rtn */yR ini_ddt_start( &_ddt, exe_std$kp_startio ); /* Exec's Start I/O rtn */F ini_ddt_kp_startio( &_ddt, startio ); /* KP's Start I/O rtn */K ini_ddt_kp_stack_size( &_ddt, KPB$K_MIN_IO_STACK ); /* KP stack size */O ini_ddt_kp_reg_mask( &_ddt, KPREG$K_HLL_REG_MASK );/* KP register mask */)F ini_ddt_cancel( &_ddt, ioc_std$cancelio ); /* Cancel rtn */I ini_ddt_regdmp( &_ddt, regdump ); /* Register dump routine */ H ini_ddt_erlgbf( &_ddt, ERR_BYTES ); /* Set error log size */" ini_ddt_end( &_ddt);@/* Finish initialization of the Function Decision Table (FDT) */K ini_fdt_act( &_fdt, IO$_READLBLK, acp_std$readblk, DIRECT_64 ); K ini_fdt_act( &_fdt, IO$_READPBLK, acp_std$readblk, DIRECT_64 ); K ini_fdt_act( &_fdt, IO$_READVBLK, acp_std$readblk, DIRECT_64 );/K ini_fdt_act( &_fdt, IO$_WRITECHECK, acp_std$readblk, DIRECT_64 );aK ini_fdt_act( &_fdt, IO$_WRITELBLK, acp_std$writeblk, DIRECT_64 );K ini_fdt_act( &_fdt, IO$_WRITEPBLK, acp_std$writeblk, DIRECT_64 ); K ini_fdt_act( &_fdt, IO$_WRITEVBLK, acp_std$writeblk, DIRECT_64 );tK ini_fdt_act( &_fdt, IO$_ACCESS, acp_std$access, BUFFERED ); K ini_fdt_act( &_fdt, IO$_CREATE, acp_std$access, BUFFERED ); K ini_fdt_act( &_fdt, IO$_DEACCESS, acp_std$deaccess, BUFFERED ); K ini_fdt_act( &_fdt, IO$_ACPCONTROL, acp_std$modify, BUFFERED );*K ini_fdt_act( &_fdt, IO$_DELETE, acp_std$modify, BUFFERED );lK ini_fdt_act( &_fdt, IO$_MODIFY, acp_std$modify, BUFFERED ); 6 K ini_fdt_act( &_fdt, IO$_MOUNT, acp_std$mount, BUFFERED );aK ini_fdt_act( &_fdt, IO$_READRCT, rct_fdt, DIRECT ); K ini_fdt_act( &_fdt, IO$_RDSTATS, rdstats_fdt, DIRECT );tK ini_fdt_act( &_fdt, IO$_UNLOAD, exe_std$lcldskvalid, BUFFERED_64 ); K ini_fdt_act( &_fdt, IO$_AVAILABLE, exe_std$lcldskvalid, BUFFERED_64 );*K ini_fdt_act( &_fdt, IO$_PACKACK, exe_std$lcldskvalid, BUFFERED_64 );eK ini_fdt_act( &_fdt, IO$_NOP, exe_std$zeroparm, BUFFERED_64 );nK ini_fdt_act( &_fdt, IO$_DRVCLR, exe_std$zeroparm, BUFFERED_64 ); K ini_fdt_act( &_fdt, IO$_RELEASE, exe_std$zeroparm, BUFFERED_64 ); K ini_fdt_act( &_fdt, IO$_SEEK, exe_std$oneparm, BUFFERED ); K ini_fdt_act( &_fdt, IO$_FORMAT, exe_std$oneparm, BUFFERED );vK ini_fdt_act( &_fdt, IO$_SETMODE, exe_std$setchar, BUFFERED_64 ); K ini_fdt_act( &_fdt, IO$_SETCHAR, exe_std$setchar, BUFFERED_64 );_K ini_fdt_act( &_fdt, IO$_SENSEMODE, exe_std$sensemode, BUFFERED_64 ); K ini_fdt_act( &_fdt, IO$_SENSECHAR, exe_std$sensemode, BUFFERED_64 ); F ini_fdt_act( &_fdt, IO$_DIAGNOSE, diagnose_fdt, DIRECT ); ini_fdt_end( &_fdt ); C/* If we got this far then everything worked, so return success. */ < return( SS$_NORMAL ); /* Return with success status */ }   k=/* STRUC_INIT - Device Data Structure Initialization Routine  *C * This routine is used to initialize the data structures at drivern * loading time. * * Usage: - * struc_init( crb, ddb, idb, orb, ucb )  * * Input:) * crb pointer to CRB * ddb pointer to DDB * idb pointer to IDB * orb pointer to ORB * ucb pointer to UCB * * Output: * none * * Return value: * none * */ Fvoid struc_init( CRB *crb, DDB *ddb, IDB *idb, ORB *orb, DQ_UCB *ucb ) { [/* BREAK( 0x00020000, (int) ucb, 0, 0 ); /@ BREAK: struc_init called -- Can't TRACE yet */ 4/* Initialize the fork lock and device IPL fields */E baseucb.ucb$b_flck = SPL$C_IOLOCK8; /* set up fork lock index */8; baseucb.ucb$b_dipl = DEVICE_IPL; /* and device IPL */ /* Initialize some UCB fields */M baseucb.ucb$l_devchar = ( DEV$M_DIR /* Device is directory-structured */eC + DEV$M_FOD /* File-oriented device */ J + DEV$M_AVL /* Device is available for use */O + DEV$M_ELG /* Device has error-Logging enabled */dS + DEV$M_IDV /* Device is capable of providing input */aT + DEV$M_ODV /* Device is capable of providing output */B + DEV$M_SHR /* Device is shareable */L + DEV$M_RND ); /* Device allows random-access */ baseucb.ucb$l_devchar2 =G ( DEV$M_NNM /* Use "node$P!" device names */ ` + DEV$M_NLT ); /* "No Last Track" bad block info on these devices */E baseucb.ucb$b_devclass = DC$_DISK; /* Device class is a disk */*G baseucb.ucb$b_devtype = DT$_GENERIC_DK; /* Device type for DDR */tF baseucb.ucb$l_devsts = UCB$M_NOCNVRT; /* Do NOT convert LBNs */M baseucb.ucb$w_devbufsiz = BLK_SIZE_512; /* Default to ATA-sized blocks */ V baseucb.ucb$l_media_id = 0x245242B2; /* Media ID of DQ|IDE50 in magic encoding */ return;  }  eA/* STRUC_REINIT - Device Data Structure Re-Initialization Routine  *E * This routine is used to reinitialize the data structures at driver  * reloading time. * * Usage:D- * struc_init( crb, ddb, idb, orb, ucb )  * * Input:  * crb pointer to CRB * ddb pointer to DDB * idb pointer to IDB * orb pointer to ORB * ucb pointer to UCB * * Output: * none * * Return value: * none * */ Ivoid struc_reinit ( CRB *crb, DDB *ddb, IDB *idb, ORB *orb, DQ_UCB *ucb )  {d]/* BREAK( 0x00030000, (int) ucb, 0, 0 ); /@ BREAK: struc_reinit called -- Can't TRACE yet */_9 ddb->ddb$ps_ddt = &_ddt; /* Point ddb to the ddt */A ddb->ddb$l_acpd = 'F11'; /* Fill-in the default ACP name */ 9 dpt_store_isr( crb, isr ); /* Set up ISR address */ & return; /* Return to caller */ }  '/* RCT_FDT - IO$_READRCT FDT Processingp *B * This routine is the FDT processing routine for the RCT functionG * code. The LBN and size are checked and, if ok, the buffer is locked_/ * down and the I/O handed off to be processed.  * * Input:  * irp pointer to IRP * pcb pointer to PCB * ucb pointer to UCB * ccb pointer to CCB * * Output: * * Return value:C * SS$_FDT_COMPL -- shows that the routine completed correctly  * */8int rct_fdt( IRP *irp, PCB *pcb, DQ_UCB *ucb, CCB *ccb ) {r1 int status; /* Returned routine status */ B irp->irp$l_bcnt = irp->irp$l_qio_p2; /* Copy the byte count */: irp->irp$l_media= irp->irp$l_qio_p3; /* and the LBN */Y if ( (irp->irp$l_bcnt <= BLK_SIZE_512) /* Byte count is less than or equal 512? */n5 && (irp->irp$l_media == 0) ) /* LBN = 0? */ { /* Met the tests */: status = exe_std$readlock( irp, /* Then do it! */' pcb, / (UCB *) ucb,Q' ccb, > (void *) irp->irp$l_qio_p1,3 irp->irp$l_bcnt,_' 0 );tE exe_std$qiodrvpkt( irp, (UCB *) ucb ); /* Queue the packet */f1 return( SS$_FDT_COMPL ); /* and exit */ }* else# { /* Failed the tests */ > irp->irp$l_iost1 = SS$_BADPARAM; /* Load error code */5 irp->irp$l_iost2 = 0; /* Clear high IOSB */E exe_std$finishio( irp, (UCB *) ucb ); /* Finish with error */n }_) return( SS$_FDT_COMPL ); /* exit */_  }* B+/* RDSTATS_FDT - IO$_RDSTATS FDT Processingt * d= * This routine is the FDT processing routine for the RDSTATSi@ * function code. If the EXTRA_STATS conditional is on, severalD * statistics are returned to the caller. Otherwise, the SS$_NODATA * error is returned.B * * Input:_ * irp pointer to IRP * pcb pointer to PCB * ucb pointer to UCB * ccb pointer to CCB * * Output: * * Return value:C * SS$_FDT_COMPL -- shows that the routine completed correctlyr * */d<int rdstats_fdt( IRP *irp, PCB *pcb, DQ_UCB *ucb, CCB *ccb ) {r/ int *bp; /* Longword buffer pointer */e" int i; /* Loop counter */8/* Check that LBN = 0 and byte count is large enough */#ifdef EXTRA_STATSM irp->irp$l_iost1 = SS$_BADPARAM; /* Assume an error - Load error code */c I if ( (irp->irp$l_qio_p2 >= RDSTATS_LEN) && (irp->irp$l_qio_p3 == 0) )l { B bp = (void *) irp->irp$l_qio_p1; /* Point to the buffer */G *bp = ucb->ucb$l_total_ints; /* Get count of all interrupts */ - bp++; /* Move to next longword */cO *bp = ucb->ucb$l_unsol_ints; /* Get count of unsolicited interrupts */r- bp++; /* Move to next longword */ D *bp = NUMBER_OF_CRAMS; /* Copy over the number of CRAMS */ bp++;CJ *bp = (int) ucb->ucb$ps_xfer_buffer; /* Transfer buffer address */ bp++; B *bp = (int) ucb->ucb$ps_s0_svapte; /* Base SPTE address */ bp++;IA *bp = (int) ucb->ucb$ps_s0_va; /* S0 VA (user buffer) */i bp++; A *bp = TIMEOUT_TIME+2; /* Save size of TIMEOUT vector */b- bp++; /* Move to next location */t,/* Copy over the timeout histogram vector */+ for (i=0; i<=(TIMEOUT_TIME+1); i++) { V *bp = ucb->ucb$l_int_hist[i]; /* Copy over the interrupt time histogram */* bp++; /* Advance pointer */ }*D *bp = ucb->ucb$l_int_tmo; /* Copy over the timeout count */' bp++; /* Advance pointer */c< irp->irp$l_iost1 = (RDSTATS_LEN << 16) + SS$_NORMAL; }r#elsep9 irp->irp$l_iost1 = SS$_NODATA; /* Load error code */n#endif1 irp->irp$l_iost2 = 0; /* Clear high IOSB */n> exe_std$finishio( irp, (UCB *) ucb ); /* Finish the I/O */. return( SS$_FDT_COMPL ); /* and exit */ }r *-/* DIAGNOSE_FDT - IO$_DIAGNOSE FDT Processing- *> * This routine is the FDT processing routine for the DIAGNOSE * function code.p * * Input:c * irp pointer to IRP * pcb pointer to PCB * ucb pointer to UCB) * ccb pointer to CCB p * * Output: * * Return value:C * SS$_FDT_COMPL -- shows that the routine completed correctly  * */ =int diagnose_fdt( IRP *irp, PCB *pcb, DQ_UCB *ucb, CCB *ccb )d{a# DIAGNOSE_PARAM *diagnose_param; int status;  int64 prvprv;o5 /* Check if process has the required privilege */r! sys$setprv(0, 0, 0, &prvprv);l ' if ((prvprv & PRV$M_DIAGNOSE) == 0)a= return(exe_std$abortio( irp, pcb, (UCB *) ucb, SS$_NOPRIV));l: diagnose_param = (DIAGNOSE_PARAM *) irp->irp$l_qio_p1;2 ucb->diagnose_opcode = diagnose_param->opcode;0 ucb->diagnose_flags = diagnose_param->flags;G ucb->diagnose_command_length = MIN(diagnose_param->command_length, eD MAX_DIAGNOSE_COMMAND_LENGTH);* if (ucb->diagnose_command_length > 0) > memcpy(ucb->diagnose_command, diagnose_param->command,- ucb->diagnose_command_length); 0A ucb->diagnose_data_length = MIN(diagnose_param->data_length, p< MAX_DIAGNOSE_DATA_SIZE); l& if (ucb->diagnose_data_length > 0)> memcpy(ucb->ucb$ps_xfer_buffer, diagnose_param->data, * ucb->diagnose_data_length);$ : ucb->diagnose_pad_length = diagnose_param->pad_length;@ ucb->diagnose_phase_timeout = diagnose_param->phase_timeout;J ucb->diagnose_disconnect_timeout = diagnose_param->disconnect_timeout;0 irp->irp$l_bcnt = ucb->diagnose_data_length; if (irp->irp$l_bcnt > 0) {: status = exe_std$readlock( irp, /* Then do it! */' pcb,n/ (UCB *) ucb,h' ccb,rA (void *) diagnose_param->data,T3 irp->irp$l_bcnt,*' 0 );o }eA exe_std$qiodrvpkt( irp, (UCB *) ucb ); /* Queue the packet */d. return( SS$_FDT_COMPL ); /* and exit */} / 0/* CTRL_INIT - Controller Initialization Routine * E * This routine is used to perform controller specific initializationEC * and is called by 1) system startup, 2) during driver loading and_$ * 3) during power failure recovery. * * Usage:, *, * status = ctrl_init ( idb, ddb, crb ) * * Input:t" * idb pointer to the idb" * ddb pointer to the ddb" * crb Pointer to the crb * * Output: * None.  * * Return value:8 * SS$_NORMAL -- unit was initialized successfully. * */h.int ctrl_init ( IDB *idb, DDB *ddb, CRB *crb ) {sS/* BREAK( 0x00040000, 0, 0, 0 ); /@ BREAK: ctrl_init called -- Can't TRACE yet */c0 return( SS$_NORMAL ); /* Return SUCCESS */ }w r*/* UNIT_INIT - Unit Initialization Routine *? * This routine is used to perform unit specific initializationC * and is called by 1) system startup, 2) during driver loading andL$ * 3) during power failure recovery. *F * This routine does very little work. Its primary job is to start upE * the fork process that will do the bulk of the unit initialization.i * * Usage:, *' * status = unit_init ( idb, ucb ) * * Input:f" * idb pointer to the IDB" * ucb pointer to the UCB * * Output: * None.  * * Return value:8 * SS$_NORMAL -- unit was initialized successfully. * */b'int unit_init ( IDB *idb, DQ_UCB *ucb )e {oa/* BREAK( 0x00050000, (int) ucb, (int) idb, 0 );/@ BREAK: unit_init called -- Can't TRACE yet */*= if (baseucb.ucb$v_power) /* Is this power recovery ? */i@ return( SS$_NORMAL ); /* Power recovery - just exit */G/* Set up and queue fork process to complete the unit initialization */ L baseucb.ucb$l_fpc = &unit_init_fork; /* Point to fork routine address */: exe_std$primitive_fork( 0, (int64) idb, (FKB *) ucb ); /* Start fork process */5 return( SS$_NORMAL ); /* Return with success */n }d p4/* UNIT_INIT_FORK - Unit Initialization Fork Routine *5 * This is the fork routine that does the bulk of theD * unit initialization work. * * Usage: *( * unit_init_fork ( fr3, idb, ucb ) * * Input: / * fr3 Fork routine parameter (unused)" * idb pointer to the IDB" * ucb pointer to the UCB * * Output: * None.* * * Return value: * none * * Note: *F * The default device name of "Generic IDE/ATAPI disk" should not beF * seen in normal operation. This will normally either be superceded? * by either a real device name (read from the device) or theo@ * "Nonexistent IDE/ATAPI disk" fake device name stored by the> * PACKACK/SENSECHAR when we fail to read a real name from a * non-existent device.S * */i7void unit_init_fork( void *fr3, IDB *idb, DQ_UCB *ucb )_ { B char model[DTN$K_NAMELEN_MAX+1] = "Generic IDE/ATAPI disk"; /* Default model name */U int mod_len = 22; /* Length of model string (*WITHOUT* trailing !) */ , DTN *dtn; /* Dummy DTN pointer */1 CRAM *cram_ptr; /* Pointer to a CRAM */n- CRCTX *ctx; /* Pointer to a CRCTX */F; int index; /* Index for walking the CRAM list */ ) ADP *adp; /* Address of ADP */n) CRB *crb; /* Address of CRB */ ) DDB *ddb; /* Address of DDB */;2 int status; /* Routine status values */: int page_cnt; /* Number of pages to allocate */5 int offset; /* PTE offset in page table */t/ int csr_base; /* Base CSR address */ < IDB *idb_ptr; /* CRAM IDB pointer value to use */4 uint64 q_nul = 0; /* A quadword of zeroes */F uint64 q_dma_csr_base; /* A quadword of dma register address */9 IRP *irp; /* Pointer to the IRP we'll build */oH int32 size; /* The size of several structures we'll allocate */= PTE *svapte; /* Pointer to PTE that maps our VA */Yg BREAK( 0x00060000, (int) ucb, (int) idb, 0 );/* BREAK: unit_init_fork called --- Can't TRACE yet */i5 adp = baseucb.ucb$ps_adp; /* Get ADP address */_4 crb = baseucb.ucb$l_crb; /* Get CRB address */4 ddb = baseucb.ucb$l_ddb; /* Get DDB address */C ucb->ucb$l_dummy_flgs = 'Flgs'; /* Put markers in the UCB */dC ucb->ucb$l_dummy_sens = 'Sens'; /* : */_C ucb->ucb$l_dummy_pakt = 'Pakt'; /* : */LC ucb->ucb$l_dummy_ints = 'Ints'; /* : */OC ucb->ucb$l_dummy_unso = 'Unso'; /* : */&C ucb->ucb$l_dummy_hist = 'Hist'; /* : */_C ucb->ucb$l_dummy_tmo = 'Tmo!'; /* : */C ucb->ucb$l_dummy_resets = 'Rst!'; /* : */ C ucb->ucb$l_dummy_trace = 'Trac'; /* : */ C ucb->ucb$l_dummy_end = 'End!'; /* : */oV ucb->ucb$l_unsolicited_int = 0; /* Forget any pending unsolicited interrupts */@ ucb->ucb$l_drive_lba_capable = 0; /* Clear all the flags */2 ucb->ucb$l_drive_dma_capable = 0; /* : */2 ucb->ucb$l_ctrl_id = 0; /* : */2 ucb->ucb$l_ctrl_dma_capable = 0; /* : */2 ucb->ucb$l_atapi_flag = 0; /* : */2 ucb->ucb$l_2K_flag = 0; /* : */C ucb->ucb$l_drv_head = DRVHD_M_BASE + (baseucb.ucb$w_unit << 4);d@ /* Set up drive/head unit bit for later use in commands */2 ucb->ucb$l_trc_buf = (void *) 0xDEADDEAD;% /* Indicate no tracing (yet) */c5 ucb->ucb$l_trc_index = 0x0000DEAD; /* : */_5 ucb->ucb$l_trc_unit = baseucb.ucb$w_unit + 1;$7 /* Set up part of our canonical unit number */_5 if ( ( (ddb->ddb$t_name_str[2] & 0x01 ) ==0 )n7 /* Check controller letter: */F7 /* Secondary controller (DQB, DQD, DQF, etc.)? */xY || (baseucb.ucb$w_unit>=2) ) /* DQA2:, DQA3:, DQC2:, DQC3, etc. ? */,T ucb->ucb$l_trc_unit += 2; /* If either, bump canonical unit by 2 */7 /* Now, 1->DQA0:, 2->DQA1:, 3->DQB0:, 4->DQB1: */x#ifdef TRACE_PER_DRIVET status = exe_std$alononpaged( TRACING*4, &size, (void **) &ucb->ucb$l_trc_buf );0 /* Allocate pool for our tracing buffer */: if ( $FAIL( status ) ) /* Check the return status */) return; /* Return if error */T ucb->ucb$l_trc_index = 0; /* Point the index to the beginning of the buffer */F TRACE( 0x0FFFFFFF ); /* Record a distinctive starting pattern */& TRACE( 0x0FF0F0F0 ); /* : */& TRACE( 0x0F0F0F0F ); /* : */& TRACE( 0x0FF0FFFF ); /* : */#endif#ifdef TRACE_COMMONic ucb->ucb$l_trc_buf = &trc_dummy; /* Provide a pointer in the UCB to the common trace buffer */bA if (trc_buf_alloc == 0) /* Only allocate the buffer once */* { > trc_buf_alloc++; /* Remember we've allocated this */9 trc_dummy = 'Trac'; /* Set the ASCII tags */t9 fixup_dummy = 'FxUp'; /* : */4 fixup_bcnt = 0; /* Zero some counters */4 fixup_boff = 0; /* : */4 fixup_svapte = 0; /* : */M status = exe_std$alononpaged( TRACING*4, &size, (void **) &trc_buf ); 0 /* Allocate pool for our tracing buffer */> if ( $FAIL( status ) ) /* Check the return status */- return; /* Return if error */dN trc_index = 0; /* Point the index to the beginning of the buffer */J TRACE( 0x0FFFFFFF ); /* Record a distinctive starting pattern */* TRACE( 0x0FF0F0F0 ); /* : */* TRACE( 0x0F0F0F0F ); /* : */* TRACE( 0x0FF0FFFF ); /* : */ }g#endifD/* Clear the histogram buffer counts. Clear each entry from 0 to */C/* TIMEOUT_TIME and the overflow count at the end of the vector. */ 5 for (index = 0 ; index < TIMEOUT_TIME+1; index++)  {_U ucb->ucb$l_int_hist[index] = 0; /* Clear the interrupt histogram counters */l }cF status = ioc$add_device_type( model, mod_len, (UCB *) ucb, &dtn );C /* Set up a default model name of "Generic IDE/ATAPI disk" */$ ;9/* Decide which PCI controller chip, if any, we're using. G * Then, by reading the BASE_ADDRESS_V register in the controller chip,aF * figure out where the Console has "located" the DMA registers today. * * Note: *< * These registers didn't exist back in ISA days, so there: * doesn't seem to be any "legacy" address like there is& * for the main blocks of registers. * */ e q_dma_csr_base = locate_dma_regs( ucb );/* Locate the DMA registers, if any, for this chip */e: /* This also sets the node_id and ctrl_dma_enable */ /* fields in the ucb. */ cB/* Ok, here's a hack. We're going to pick up the IDB$Q_CSR value.A * If it's <= 0x80000000, then it's treated as an offset from theo= * the base of ISA space. For example, 0x1F0. If not, we'llo; * assume it's the VA of the base of ISA space (as might be  * passed by Autoconfig).u *1 * Puting this another way, we are passed either:* *= * 1. An ISA offset. Clear the CRAM IDB pointer so thatb= * only the ADP$Q_CSR field is used. Use the IDB CSR,: * value as the offset to the register (csr_base). *B * 2. The VA of base of ISA space. We could use IOC$NODE_DATA? * to ask the PCI config space registers for the actual_> * ISA addresses (as shown below), but, in fact, we'll> * take the less machine-dependent legacy easy way out% * and just always configure:  *% * o DQA0 and DQA1 at 0x1F0h: * o DQB0 and DQB1 (or DQA2: and DQA3:) at 0x170 *! * Then, proceed as in 1.n * *P * int va[8]; /@ Storage for the array returned by ioc$node_data @/ *? * idb_ptr= NULL; /@ Use pointer to IDB in CRAM @/ T * ioc$node_data( crb, /@ Get ISA address of one of the two IDE ports @/W * IOC$K_EISA_IO_PORT,/@ "EISA"? Oh well, go with the flow @/,& * &va[0] ); *d * if (this_is_a_Cypress_CY82C693) /@ Cypress chip? We need this non-existent test! @/` * va[0] = va[0] & 0xFFFFFFF8; /@ If so, mask off the Cypress's "block size" bits @/> * /@ @/> * /@ Note: Bits <31:16> in that register are only R/W @/> * /@ if bit <4> in PCI Config Space Register 4D @/> * /@ ("Stand-Alone Control) is set to 1. This @/> * /@ Resgister and bit may only exist in the @/> * /@ CY82C693U (USB version), not the vanilla @/> * /@ version. @/ *I * csr_base = va[0]; /@ Use that returned CSR as the base @/$ * */(6 idb_ptr= NULL; /* Use no IDB pointer in CRAM */F if (idb->idb$q_csr < 0x80000000) /* Check if it's in ISA space */ {lW csr_base = idb->idb$q_csr; /* Apparently, so use the passed CSR as the base */ V q_dma_csr_base = 0; /* What to do about the DMA registers in this case??? */^ ucb->ucb$l_ctrl_dma_capable = 0; /* For the moment, disable DMA for this controller */ }G else /* No, big VA so Autoconfig passing base adx of ISA bus */na if (ucb->ucb$l_trc_unit <= 2) /* DQA0: (=1) or DQA1: (=2) ? */ J { /* If either, then... */^ csr_base = 0x1F0; /* Use legacy primary addresses */ }K else /* Else DQB0:/DQA2: (=3) or DQB1:/DQA3: (=4) */: {r^ csr_base = 0x170; /* Use legacy secondary addresses */^ q_dma_csr_base += 0x8; /* Use secondary group of DMA CSRs */ }* /* !!!???  * * Note: *@ * In the paragraph below, several of the values are hard-wired.? * Realistically, they should vary depending on the type of buss< * that we're mapping. This is just to get me off the round. u~ DQDRIVER.BCKѡ![FREEWAREV5.DQDRIVER]DQDRIVER.C;1r|I * */q^//#saythis "Consider creating just one pair of mappings and sharing them among our four units"F status = ioc$map_io( adp, /* Map the main CSRs into our space */M crb->crb$l_node, /* Node number of the bus to map */7 &q_nul, /* physical_offset */oH 0x1000, /* Bytes to map including *ALL OF*: */; /* - Primary ISA main regs at 0x01F0 and 0x3F6 */M; /* - Secondary ISA main regs at 0x0170 and 0x376 */f@ IOC$K_BUS_IO_BYTE_GRAN,/* attributes */2 &ucb->ucb$q_iohandle_1 );: if ( $FAIL( status ) ) /* Check the return status */) return; /* Return if error */ E status = ioc$map_io( adp, /* Map the DMA CSRs into our space */pM crb->crb$l_node, /* Node number of the bus to map */o? &q_dma_csr_base, /* physical_offset */ F 0x8, /* Bytes to map including *EITHER*: */2 /* - Primary ISA DMA regs at 0xnnnnn0 */2 /* - Secondary ISA DMA regs at 0xnnnnn8 */@ IOC$K_BUS_IO_BYTE_GRAN,/* attributes */2 &ucb->ucb$q_iohandle_2 );: if ( $FAIL( status ) ) /* Check the return status */) return; /* Return if error */r/*; * Now, load the CRAMs that we'll use for register accessesB * */ O cram_ptr = baseucb.ucb$ps_cram; /* Point to the first CRAM in our chain */P; for ( index=0; indexcram$l_idb = idb_ptr; /* Set IDB pointer in the CRAM */C ucb->ucb$ps_crams[index] = cram_ptr; /* Set up UCB table */o4 status = ioc$cram_cmd( cram_init[index].cmd,@ csr_base+cram_init[index].offset,# adp,h( cram_ptr,B (uint64*) &ucb->ucb$q_iohandle_1 );H cram_ptr->cram$l_idb = idb; /* Set the IDB pointer correctly */> if ( $FAIL( status ) ) /* Check the return status */, return; /* Return if error */@ cram_ptr->cram$v_der = 1; /* Disable error reporting */l cram_ptr = cram_ptr->cram$l_flink; /* On to next CRAM pointer, preparing for a possible next pass */ }dk for ( ; indexcram$l_idb = idb_ptr; /* Set IDB pointer in the CRAM */C ucb->ucb$ps_crams[index] = cram_ptr; /* Set up UCB table */y4 status = ioc$cram_cmd( cram_init[index].cmd,7 cram_init[index].offset, # adp,_( cram_ptr,B (uint64*) &ucb->ucb$q_iohandle_2 );H cram_ptr->cram$l_idb = idb; /* Set the IDB pointer correctly */> if ( $FAIL( status ) ) /* Check the return status */, return; /* Return if error */@ cram_ptr->cram$v_der = 1; /* Disable error reporting */l cram_ptr = cram_ptr->cram$l_flink; /* On to next CRAM pointer, preparing for a possible next pass */ }=/*: * Touch some device registers, just to prove we can do it? * (In other words, if we can't, crash here-and-now, not later)  * * Note: *: * Even if this fails, we may not actually crash because: * ISA-space registers just return 0xFF for non-existent * registers.  * */ A inp( ucb, RD_ALT_STS ); /* Get alternate status register */o6 inp( ucb, RD_DMA_STS ); /* Get a DMA register *//* * Allocate transfer buffer  * */ O page_cnt = ( XFER_BUFFER_SIZE + MMG$GL_PAGE_SIZE - 1 ) >> MMG$GL_VPN_TO_VA;t3 /* Compute the size of the buffer in pages */O status = exe_std$alophycntg( page_cnt, (void *) &ucb->ucb$ps_xfer_buffer );/*5 * Allocate a buffer to hold last ATAPI request-sensez * */*` status = exe_std$alophycntg( (SENSE_BUFFER_SIZE + MMG$GL_PAGE_SIZE - 1) >> MMG$GL_VPN_TO_VA,E (void *) &ucb->ucb$ps_sense_buffer );^ if ( $FAIL( status) ) /* Allocate the sense buffer (usually, just one page -- plenty) */. return; /* Just exit on failure */O/* Allocate SPTEs for double mapping the user buffer (plus guard + spillage) */uM status = ldr_std$alloc_pt( page_cnt+3, (void *) &ucb->ucb$ps_s0_svapte );R if ( $FAIL( status ) ). return; /* Just exit on failure */K/* Compute S0 address of the double map buffer. Note that "offset" will */IK/* be the number of PTEs, not the offset from SPTBASE. So, the shift is */uK/* page number to VA, not PTE offset to VA. A small factor of PTE size. */g4 offset = ucb->ucb$ps_s0_svapte - mmg$gl_sptbase;P ucb->ucb$ps_s0_va = (BYTE *) ( (offset << MMG$GL_VPN_TO_VA) | VA$M_SYSTEM ); r/*; * Allocate and initialize the data buffer CRCTX structure.9 * Then load the map registers that cover our data buffer* * */tB status = ioc$alloc_crctx( adp->adp$l_crab, /* CRAB address */^ &ucb->ucb$ps_xfer_crctx, /* Address to save the CRCTX address */E SPL$C_IOLOCK8); /* Lock information */ 5 if ( $FAIL( status ) ) /* Did that go okay? */d1 return; /* Just return on failure */tN ctx = ucb->ucb$ps_xfer_crctx; /* Point to the context we just created */X ctx->crctx$l_item_cnt = XFER_BUFFER_MAP_PAGES + 2; /* Including 2 for guard pages */C status = ioc$alloc_cnt_res( adp->adp$l_crab, /* CRAB address */ W ucb->ucb$ps_xfer_crctx, /* xfer buffer CRCTX address */61 0, /* Unused */ 1 0, /* : */e3 0 ); /* : */I5 if ( $FAIL( status ) ) /* Did that go okay? */t1 return; /* Just return on failure */i@ mmg_std$svaptechk( ucb->ucb$ps_xfer_buffer, 0, 0, &svapte );0 /* Get SVAPTE for the data buffer's VA */4 status = ioc$load_map( adp, /* ADP address */R ucb->ucb$ps_xfer_crctx, /* xfer buffer CRCTX address */1 svapte, /* SVAPTE */uK (int) ucb->ucb$ps_xfer_buffer & mmg$gl_bwp_mask, & /* Byte offset into the page */4 &(ucb->ucb$l_xfer_phy) );6 /* Address to save the resulting DMA address */5 if ( $FAIL( status ) ) /* Did that go okay? */R1 return; /* Just return on failure */e /*: * Allocate and align a small space to hold our PRDT table4 * Allocate and initialize the PRDT CRCTX structure.2 * Then load the map registers that cover the PRDT * */Y status = exe_std$alononpaged( PRDT_TABLE_SIZE*2, &size, (void **) &ucb->ucb$l_prdt ); @ if ( $FAIL( status) ) /* Allocate the PRDT table space *// return; /* Just exit on failure */oh ucb->ucb$l_prdt = (int *) ( ( ( (int) ucb->ucb$l_prdt ) + PRDT_TABLE_SIZE - 1 ) & PRDT_ADX_MASK );3 /* Now, force the pointer into alignment. */ 3 /* This also ensures that it doesn't */;3 /* cross any page boundaries */B status = ioc$alloc_crctx( adp->adp$l_crab, /* CRAB address */^ &ucb->ucb$ps_prdt_crctx, /* Address to save the CRCTX address */E SPL$C_IOLOCK8); /* Lock information */45 if ( $FAIL( status ) ) /* Did that go okay? */ 1 return; /* Just return on failure */rN ctx = ucb->ucb$ps_prdt_crctx; /* Point to the context we just created */[ ctx->crctx$l_item_cnt = 3; /* Including 1 page for spillover and 1 page for guard */rC status = ioc$alloc_cnt_res( adp->adp$l_crab, /* CRAB address */iP ucb->ucb$ps_prdt_crctx, /* PRDT CRCTX address */1 0, /* Unused */r1 0, /* : */ 3 0 ); /* : */ 5 if ( $FAIL( status ) ) /* Did that go okay? */t1 return; /* Just return on failure *//Z mmg_std$svaptechk( ucb->ucb$l_prdt, 0, 0, &svapte );/* Get SVAPTE for the PRDT's VA */4 status = ioc$load_map( adp, /* ADP address */K ucb->ucb$ps_prdt_crctx, /* PRDT CRCTX address */ 1 svapte, /* SVAPTE *//C (int) ucb->ucb$l_prdt & mmg$gl_bwp_mask,/& /* Byte offset into the page */4 &(ucb->ucb$l_prdt_phy) );6 /* Address to save the resulting DMA address */5 if ( $FAIL( status ) ) /* Did that go okay? */'1 return; /* Just return on failure */u t,/* Do any controller-specific initialization * */u switch (ucb->ucb$l_ctrl_id)b {/. case 0x522910B9: /* The Acer chip */ { j status = ioc$write_pci_config( adp, /* Write the CDRC -- CD-ROM (ATAPI?) Control Register */: crb->crb$l_node,j 0x53, /* Register at offset 0x53 in config space */; IOC$K_BYTE_LANED,eB /* For V71R's benefit, avoid EV6 IOC$K_BYTE feature */o 0x01<<24 ); /* Enabling CD-ROM DMA, shifted into the MS byte lane */-^ if ( $FAIL( status ) ) /* Check the return status */U return; /* Return if error */lP break; /* Done with Acer-specific stuff */ } 1 case 0xC6931080: /* The Cypress chip */:& { /* : */, break; /* (Nothing to do) */* } /* : */G default: /* Anything else (hopefully ISA comes here too!) */t& { /* : */, break; /* (Nothing to do) */* } /* : */ }t )/* * Enable interrupts * */ G status = ioc$node_function( baseucb.ucb$l_crb, IOC$K_ENABLE_INTR );*3 if ( $FAIL( status ) ) /* Check status and */ / return; /* simply exit if error */s/*' * Size the disk (for non-system disks) 3 * or size and pack-ack the disk (for system disks)  * */0G status = exe_std$alononpaged( sizeof(IRP), &size, (void **) &irp );b/ if ( $FAIL( status ) ) /* Check status */ . return; /* If it failed, return */L memset( irp, 0x0, size ); /* Clear all the memory we just allocated */^ irp->irp$w_size = size; /* And make it all into an IO$_PACKACK or IO$_SENSECHAR IRP *// irp->irp$b_type = DYN$C_IRP; /* : */p. irp->irp$l_ucb = &baseucb; /* : */0 if (&baseucb == sys$ar_bootucb) /* : */4 irp->irp$l_func = IO$_PACKACK; /* : */ else /* : */6 irp->irp$l_func = IO$_SENSECHAR; /* : */( irp->irp$v_physio = 1; /* : */; irp->irp$l_pid = (unsigned int) exe_std$deanonpaged;(= baseucb.ucb$l_qlen++; /* Bump up our IO queue length */ L//#saythis "Don't forget to knock it offline upon a possible failure later!"H baseucb.ucb$v_online = 1; /* Mark the purported disk as on-line */1 ucb->ucb$r_dq_dt.ucb$l_maxblock = 0x7FFFFFFF;04 /* Give it a temporary (but valid) capacity */8 baseucb.ucb$v_bsy = 1; /* Mark the unit as busy */M ioc_std$initiate( irp, &baseucb ); /* Initiate processing of that IRP */ return; /* And return */ }r uH/* LOCATE_DMA_REGS -- Locate the base address for the DMA register block *9 * Decide which PCI controller chip, if any, we're using.=G * Then, by reading the BASE_ADDRESS_V register in the controller chip, F * figure out where the Console has "located" the DMA registers today. * * * Input:l" * ucb pointer to the UCB * * Output: * None.c * * Return value:6 * ISA address of DMA registers, if found, else 0 * * Side effects:) * ucb$l_ctrl_id is updated ) * ucb$l_ctrl_dma_capable is updateda * * * Notes:* *> * o These registers didn't exist back in ISA days, so there< * doesn't seem to be any "legacy" address like there is( * for the main blocks of registers. *B * o The Acer I/O chip has one set of PCI Config Space registersC * so just one BA_V register for both the Primary and SecondaryiE * IDE controllers. This makes it easy to find its DMA registers.O *D * o The Cypress has two blocks of PCI Config Space registers, butG * only the set for the Primary IDE controller has a BA_V register.IC * This makes it tough for the us to find the DMA registers for F * the Secondary IDE controller, so we wander around in PCI Config$ * Space looking for the answer. * */r"int locate_dma_regs( DQ_UCB *ucb ) {C) ADP *adp; /* Address of ADP */ ) CRB *crb; /* Address of CRB */6 int dma_csr_base; /* Base DMA CSR address */2 int status; /* Routine status values */5 adp = baseucb.ucb$ps_adp; /* Get ADP address */a4 crb = baseucb.ucb$l_crb; /* Get CRB address */[ ucb->ucb$l_ctrl_dma_capable = 0; /* For the moment, disable DMA for this controller */wk status = ioc$read_pci_config( adp, /* Read the vendor ID and device ID fields of the IDE controller */ 2 crb->crb$l_node,I PCI$K_VENDOR_ID, /* *AND* DEVICE_ID! */o$ 4,: &(ucb->ucb$l_ctrl_id) );+ if ( $FAIL( status ) ) /* Failure? */bE return( 0x0 ); /* If so, then indicate no DMA registers */ B if (ucb->ucb$l_ctrl_id == 0x522910B9) /* Acer Aladdin chip? */! { /* If so, then... */ G status = ioc$read_pci_config( adp, /* Read the BA_V register */ 6 crb->crb$l_node,+ 0x20,C( 4,6 &dma_csr_base );> if ( $FAIL( status ) ) /* Check the return status */2 return( 0x0 ); /* Return if error */S ucb->ucb$l_ctrl_dma_capable = 1; /* Else, enable DMA for this controller */ 1 dma_csr_base = dma_csr_base & 0xFFFFFFF0;& /* Mask off the low four bits */> return( dma_csr_base ); /* Return the found value */ } #ifdef CYPRESS_DMA? if ( ucb->ucb$l_ctrl_id == 0xC6931080 ) /* Cypress chip? */* {N int base_node; /* Base node to look for the Cypress registers */K int i; /* A counter to look through the Cypress registers */iE int temp; /* Temporary storage for a returned value */ S base_node = crb->crb$l_node & 0xFFF0; /* Find the base node for this bus */ ? for (i=0;i<16;i++) /* For each node on this bus... */ {lK status = ioc$read_pci_config( adp, /* Read the BA_V register */d6 base_node+i,Q PCI$K_VENDOR_ID, /* *AND* DEVICE_ID! */ , 4,2 &temp );A if ( $FAIL( status ) ) /* Check the return status */2 continue; /* If error, next i */L if (temp != 0xC6931080) /* Is this a Cypress register block? *// continue; /* If not, next i */ K status = ioc$read_pci_config( adp, /* Read the BA_V register */s6 base_node+i,X PCI$K_REVISION_ID, /* *AND* PROGRAMMING_IF! */, 4,2 &temp );A if ( $FAIL( status ) ) /* Check the return status */t6 return( 0x0 ); /* Return if error */G temp = temp & 0xFFFFFF00; /* Mask off the revision byte */ X if (temp != 0x01018000) /* Is this a Cypress primary IDE register block? *// continue; /* If not, next i */_K status = ioc$read_pci_config( adp, /* Read the BA_V register */ 6 base_node+i,/ 0x20, , 4,: &dma_csr_base );A if ( $FAIL( status ) ) /* Check the return status */h6 return( 0x0 ); /* Return if error */V ucb->ucb$l_ctrl_dma_capable = 1; /* Else enable DMA for this controller */5 dma_csr_base = dma_csr_base & 0xFFFFFFF0;b& /* Mask off the low four bits */A return( dma_csr_base ); /* Return the found value */ } /* Next i */Q return( 0x0 ); /* Can't find the regs; indicate no DMA registers */S4 /* And leave ucb$l_ctrl_dma_capable cleared */- /* (We probably should bugcheck here *// } #endifK return( 0x0 ); /* Nothing we recognize; indicate no DMA registers */ 4 /* And leave ucb$l_ctrl_dma_capable cleared */ }w t"/* REGDUMP - Register Dump Routine *G * This is the register dump routine. It is used to dump the registersf8 * at the time of an error. It is called at device IPL. * * Input:C4 * buffer address of buffer to store registers4 * arg_2 additional argument passed by caller * ucb pointer to UCB * * Output: * none * * * Note: *< * For some reason, the error packet isn't displaying well.= * So, hack to. Fudge the pointer based on empirical results]4 * and add "ssss" and "eeee" to bracket the packet. * */ 4void regdump( BYTE *buffer, int arg_2, DQ_UCB *ucb ) {49 TRACE( 0x03500000 + arg_2 ); /* REGDUMP beginning */i) buffer += 5; /* Advance pointer */ 0 *buffer++ = 's'; /* Bracket the buffer */# *buffer++ = 's'; /* : */u# *buffer++ = 's'; /* : */m# *buffer++ = 's'; /* : */n5 /* Put all of the registers into the buffer. */ 5 /* Pad to an even longword *//I *buffer++ = arg_2; /* Copy over the marker */uX *buffer++ = inp( ucb, RD_DMA_CMD ); /* Get the DMA command register */X *buffer++ = inp( ucb, RD_DMA_DS1 ); /* Get the DMA device-specific register 1 */X *buffer++ = inp( ucb, RD_DMA_STS ); /* Get the DMA status register */X *buffer++ = inp( ucb, RD_DMA_DS2 ); /* Get the DMA device-specific register 2 */X *buffer++ = inp( ucb, RD_DMA_AD0 ); /* Get the DMA PRDT Address Register 0 */X *buffer++ = inp( ucb, RD_DMA_AD1 ); /* Get the DMA PRDT Address Register 1 */X *buffer++ = inp( ucb, RD_DMA_AD2 ); /* Get the DMA PRDT Address Register 2 */X *buffer++ = inp( ucb, RD_DMA_AD3 ); /* Get the DMA PRDT Address Register 3 */X *buffer++ = inp( ucb, RD_ERROR ); /* Get error */X *buffer++ = inp( ucb, RD_SEC_CNT ); /* Get sector count */X *buffer++ = inp( ucb, RD_SECTOR ); /* Get sector number */X *buffer++ = inp( ucb, RD_CYL_LO ); /* Get cylinder number (low) */X *buffer++ = inp( ucb, RD_CYL_HI ); /* Get cylinder number (high) */X *buffer++ = inp( ucb, RD_DRV_HD ); /* Get drive/head information */b *buffer++ = inp( ucb, RD_STS ); /* Get status, quashing any pending interrupts as well */E *buffer++ = 0; /* Round up to an even */uE *buffer++ = 0; /* number of longwords */(8 *buffer++ = 'e'; /* Add tail of buffer bracket */# *buffer++ = 'e'; /* : */ # *buffer++ = 'e'; /* : */s# *buffer++ = 'e'; /* : */S6 TRACE( 0x03510000 + arg_2 ); /* REGDUMP ending */ }  /* STARTIO - Start I/O Routine *E * This is the driver start I/O routine. This routine processes eachl * of the I/O requests.y * * Input:e- * irp Pointer to I/O request packetl- * ucb Pointer to unit control block( * * Output: * none * * * Note: *< * IO$_SENSECHAR is never queued to us by VMS. Instead, we= * queued this IO function code to ourselves as part of ourl: * startup; we do this to size non-system disks. (System * disks get IO$_PACKACK.) * r */Tvoid startio( KPB *kpb ) {o- int iost1, iost2; /* IOSB fields */ * int temp; /* Temporary value */) DQ_UCB *ucb; /* Pointer to UCB */m) IRP *irp; /* Pointer to IRP */T int status;$/* Set up necessary pointers */< ucb = (DQ_UCB *) kpb->kpb$ps_ucb; /* Get UCB pointer */ p///#saythis "Temporary test for the V_BSY bit.."Q if (baseucb.ucb$v_bsy == 0) /* Is this an expected interrupt? */ Q BPTRACE( 0x010E0000 ); /* STARTIO starting *WITHOUT* V_BSY! */ 2 irp = kpb->kpb$ps_irp; /* Get IRP pointer */7 ucb->ucb$ps_kpb = kpb; /* Save the KPB address */ H ucb->ucb$l_media.lbn = irp->irp$l_media; /* Copy the disk address */F//#saythis "Temporary new copies to hack around VBNMAPFAIL crashes..."` if (baseucb.ucb$l_bcnt != irp->irp$l_bcnt) /* Is bcnt correct? */> { /* If not, then... */H#ifdef TRACE_COMMON /* */J fixup_bcnt++; /* Bump the event counter */=#endif /* */ R TRACE( 0x01200000 ); /* UCB$L_BCNT corruption (by IRP over-copy!) */R// BPTRACE( 0x01200000 ); /* Blammo! */a baseucb.ucb$l_bcnt = irp->irp$l_bcnt; /* Copy the bcnt from the IRP */i }a` if (baseucb.ucb$l_boff != irp->irp$l_boff) /* Is boff correct? */> { /* If not, then... */H#ifdef TRACE_COMMON /* */J fixup_boff++; /* Bump the event counter */=#endif /* */ R TRACE( 0x01210000 ); /* UCB$L_BOFF corruption (by IRP over-copy!) */R// BPTRACE( 0x01210000 ); /* Blammo! */a baseucb.ucb$l_boff = irp->irp$l_boff; /* Copy the boff from the IRP */d }s\ if (baseucb.ucb$l_svapte != irp->irp$l_svapte) /* Is bcnt correct? */7 { /* If not, then... */sH#ifdef TRACE_COMMON /* */L fixup_svapte++; /* Bump the event counter */=#endif /* */fR TRACE( 0x01220000 ); /* UCB$L_SVAPTE corruption (by IRP over-copy!) */R// BPTRACE( 0x01220000 ); /* Blammo! */\ baseucb.ucb$l_svapte = irp->irp$l_svapte; /* Copy the bcnt from the IRP */ }dM//#saythis "...End of Temporary new copies to hack around VBNMAPFAIL crashes" H ucb->ucb$l_bcr = baseucb.ucb$l_bcnt; /* Copy remaining byte count */_ TRACE( 0x01000000 + (irp->irp$v_fcode ) ); /* STARTIO starting... */i_ TRACE( 0x01010000 + ( ( (int) irp>>16 ) & 0xFFFF) ); /* : Log starting IRP_hi */p_ TRACE( 0x01020000 + ( ( (int) irp ) & 0xFFFF) ); /* : Log starting IRP_lo */s_ TRACE( 0x01030000 + ( (irp->irp$l_media>>16) & 0xFFFF) ); /* : Log starting LBA_hi */ _ TRACE( 0x01040000 + ( (irp->irp$l_media ) & 0xFFFF) ); /* : Log starting LBA_lo */ _ TRACE( 0x01050000 + ( (baseucb.ucb$l_bcnt ) & 0xFFFF) ); /* : Log starting bytecount */aA/* Check that either volume is valid or this is a physical I/O */t4 if ( !irp->irp$v_physio && !baseucb.ucb$v_valid) {h5 ioc_std$reqcom( SS$_VOLINV, 0, (UCB *) ucb );  /* Finish I/O */T BPTRACE( 0x01100000 ); /* BREAK: STARTIO punting on volume not valid... */$ return; /* And return */ } //* Interpret the LBN according to PHYSIO bit *//@ if (irp->irp$v_physio) /* Convert from physical format? */ {cJ switch (irp->irp$v_fcode) /* Does this command use an address? */ { T case IO$_READLBLK: /* These shouldn't occur with v_phys set, right? */. case IO$_WRITELBLK: /* : */b BPTRACE( 0x01110000 ); /* BREAK: IO$_READLBLK or IO$_WRITELBLOCK with V_PHYS set */" /* Fall through anyway... */K case IO$_SEEK: /* These can be physical and use an address */P. case IO$_WRITECHECK: /* : */- case IO$_READPBLK: /* : */ . case IO$_WRITEPBLK: /* : */4 { /* So range-check the address */G if ( (ucb->ucb$l_media.pa.sec == 0) /* [1:n] */ Y || (ucb->ucb$l_media.pa.sec > baseucb.ucb$b_sectors ) /* : */ Y || (ucb->ucb$l_media.pa.trk >= baseucb.ucb$b_tracks ) /* [0:n-1] */$] || (ucb->ucb$l_media.pa.cyl >= baseucb.ucb$w_cylinders ) ) /* [0:n-1] */e {fO BPTRACE( 0x0112000 ); /* BREAK: CHS address out of range */tC ioc_std$reqcom( SS$_BADPARAM, 0, (UCB *) ucb );$ /* Complete the I/O failing */. return; /* And return */ break; }  } G default: /* No address used -- no need to range-check */  break; } \ ucb->ucb$l_media.lbn = ( ( ( (ucb->ucb$l_media.pa.cyl * baseucb.ucb$b_tracks )Y + ucb->ucb$l_media.pa.trk) * baseucb.ucb$b_sectors )iA + ucb->ucb$l_media.pa.sec - 1 );(2 /* Convert the physical address to an LBN */ } &/* Remember the transfer parameters */9 ucb->ucb$l_org_media = ucb->ucb$l_media.lbn;/* LBN */hH ucb->ucb$l_org_svapte= baseucb.ucb$l_svapte;/* Page table address */? ucb->ucb$l_org_bcnt = baseucb.ucb$l_bcnt; /* Byte count */)@ ucb->ucb$l_org_boff = baseucb.ucb$l_boff; /* Byte offset */#/* Handle based on function code */ ; TRACE( 0x01060000 ); /* Log our calling reqchan... */zH iost1 = ioc$kp_reqchan( kpb, KPB$K_LOW ); /* Get the data channel */B if ( $FAIL( iost1 ) ) /* Check for failure to get channel */ { @ ioc_std$reqcom( iost1, 0, (UCB *) ucb );/* Finish I/O */i BPTRACE( 0x01130000 + (iost1 &0xFFFF) );/* BREAK: STARTIO punting on failure to get channel... */f" return; /* And exit */ }v> iost1 = SS$_ILLIOFUNC; /* Assume illegal I/O function */3 iost2 = 0; /* Assume no data transferred */* switch (irp->irp$v_fcode) {y case IO$_NOP:c8 BPTRACE( 0x01070000 ); /* BREAK: IO$_NOP */: iost1 = SS$_NORMAL; /* Status is "normal" */1 break; /* and complete the I/O */0 case IO$_UNLOAD:; BPTRACE( 0x01070001 ); /* BREAK: IO$_UNLOAD */aB iost1 = unload( ucb ); /* Call the unload function */1 break; /* and complete the I/O */  case IO$_SEEK:9 BPTRACE( 0x01070002 ); /* BREAK: IO$_SEEK */s> iost1 = seek( ucb ); /* Call the SEEK function */1 break; /* and complete the I/O */h case IO$_DRVCLR:; BPTRACE( 0x01070004 ); /* BREAK: IO$_DRVCLR */lG iost1 = drvclr( ucb ); /* Call the DRIVE CLEAR function */n1 break; /* and complete the I/O */ case IO$_PACKACK: [ iost1 = packack( ucb, 0 ); /* Call PACKACK w/o asserting the init_time_flag */ 1 break; /* and complete the I/O */p case IO$_READRCT:*< BPTRACE( 0x01070009 ); /* BREAK: IO$_READRCT */B iost1 = readrct( ucb ); /* Get back the drive data */B iost1 = (iost1 & 0xFFFF) + (baseucb.ucb$l_bcnt << 16);1 break; /* and complete the I/O */s case IO$_AVAILABLE:t> BPTRACE( 0x01070011 ); /* BREAK: IO$_AVAILABLE */B iost1 = unload( ucb ); /* Call the unload function */1 break; /* and complete the I/O */o case IO$_DIAGNOSE:% ucb->ucb$l_sense_key = 0; K status = diagnose( ucb ); /* Call the audio function */S8 temp = baseucb.ucb$l_bcnt - ucb->ucb$l_bcr;9 iost1 = SS$_NORMAL | ((temp & 0xFFFF) << 16);aN iost2 = ((ucb->ucb$l_sense_key & 0xFF) << 24); 1 break; /* and complete the I/O */n case IO$_FORMAT:; BPTRACE( 0x0107001E ); /* BREAK: IO$_FORMAT */sL iost1 = SS$_UNSUPPORTED; /* Return UNSUPPORTED error for now */1 break; /* and complete the I/O */  case IO$_SENSECHAR: W iost1 = packack( ucb, 1 ); /* Call PACKACK asserting the init_time_flag */o1 break; /* and complete the I/O */l> case IO$_WRITECHECK: ? BPTRACE( 0x0107000A ); /* BREAK: IO$_WRITECHECK */, case IO$_READLBLK: case IO$_READPBLK:@ iost1 = read( ucb ); /* Read the required blocks */> if ( $FAIL( iost1 ) ) /* Did the read go akay? */1 break; /* If not, bug out now */*< if ( IS_SET( irp->irp$l_func, IO$M_DATACHECK ) ) /* Datacheck requested? */F iost1 = datacheck( ucb ); /* Yes, do the datacheck */8 temp = baseucb.ucb$l_bcnt - ucb->ucb$l_bcr;4 iost1 = (iost1 & 0xFFFF) + (temp << 16);1 break; /* and complete the I/O */  case IO$_WRITELBLK:  case IO$_WRITEPBLK: B iost1 = write( ucb ); /* Write the required blocks */> if ( $FAIL( iost1 ) ) /* Did the read go akay? */1 break; /* If not, bug out now */o< if ( IS_SET( irp->irp$l_func, IO$M_DATACHECK ) ) /* Datacheck requested? */F iost1 = datacheck( ucb ); /* Yes, do the datacheck */8 temp = baseucb.ucb$l_bcnt - ucb->ucb$l_bcr;4 iost1 = (iost1 & 0xFFFF) + (temp << 16);1 break; /* and complete the I/O */o case IO$_AUDIO: K BPTRACE( 0x01070037 ); /* BREAK: IO$_AUDIO (IO$_READPROMPT) */*L iost1 = SS$_UNSUPPORTED; /* Return UNSUPPORTED error for now */F/* iost1 = audio_audio( ucb ); /@ Call the audio \ݲѡ&[FREEWAREV5.DQDRIVER]DECW$CDPLAYER.S*8Z#[$ho! '# o1QzZ!Jj"sSKbEGkG)a`d{933|CDXeV;"n j Pf"dVE3akC6Aq}Foj9?=(9A{MMH\m/BB0N$mzc P8 ">^LQSh n+wsr'Ue#.A@6(u| &I(c\[s.DR+?DO[J=Qq^K2!pIdz%raR^x>.c fxnyV> =uK%f=r?k%t0C1WA9>B5 c@nViHn5#n3 u xlKCKlEV8;zwo:p*_0S^ )v"so6 _j #4Gu\^e<_;9xZm.*k_j#^!^$>+hG\"N_Vw*P>=~3`!$rnSB L /H5g[C K JoV?ekD4C=5IKb+w V;_5ACFkY?'(!EEzC:nk,v9A)TZLrq/&3(5U~D jX<{c~01+w\GN#_iT97R v`dI$:Z`AMy?RR0-/@e[Q70;~=kxZ#H#_ ?^P (\r\~5$U=]?3F@4n%uY4$ca62gd({V9Oc~.pM+aN,n <;qih c֒4{eB Cg,HIt[w~Kdv*)YMXwA~hW3/UzU8am7G$/1Pv7`xff dR\HE#78YYAT<] ap,>: LP[e[\!g7s%o>E"EKO_FFNLDE urk)8qq7|{m o$xq 8Z7u,b}]^B>'Zbc;(g/v)%;+6;@U##`z:eC\zpLo/>B`Xj>lMWxwV`sd9q7(Lq8|7 70_B`* 4cyo>}zOa_hW{0 merMc>Ual2eN^cPe@l.I#;bL .,bټ#UmPTd*.#8Qfib4H$*Ѳq12(ua c5f1$:BBJDpSW f]Vs7'itwNf,-\^>{xw*Qxr =`~x?#)W5md5!T4*D)sg9A`w.`-yV[={T^`/? VRN% NJrq4p RDqpkHw!D_A r"+F1%najDMfM U}ia`z9> {W=LbL#!,(cl4|(c|>7va` 9j?{(J>x<U&&-hx]\siNep0r6sPACmd%*T6v#sxZnxoDP9I_Gn#hT^ QQAyj_?T_I"VUz/5Y7+}9B5<4{,o^4-WJ~Tn!qJ? ZgN$$4 '}.9"h lmS/3[RL("!B\Z"n+:J d;^ "=Gx4?U7t)}wGC[ (E+c (V!A5}Z%~ZBFLUaR(xN1< JNc?ozD-\@\ ^Dx9JJ*2#g"[75l+:xG NEX>&M#0>8lkE0:@lwzJ:r@g}} _ #tn BuVehn^%(|0!/2>0aZs M g&Ch"[S]KkL6eIo&kk )KoAkd&Y|X@}%>rmlooA@M({n*MZK=0zrZ$^, 911ty3%E)m.T Z />jYr@Uf hrj&>Z9b V(N&i k=RF%;]{VU*EC<;:j!8|kcg;%=]5s&K3:\f/q?sO|3s|vj43M ~8&)5qZKk@a#0ehdW::XO_] ) A(R7BHK2 &Kj|3GZvE 6\`<6I18a )}P/1I$7} Oqg1~I]]N6h(0 j(Pgi,Kw%BJxpw {/3.]z>11\ 8FJ+j4;!p]bABkOIQK. JZuU7G3_gzG@d9* %$:\a=tW"RnEWs4h(p_OL?QyF5+D?gBb;;MNXJ:;A]&Czli%u  _0zdtOH2)h! ]M=2rOB`mTh29p4-j-" *1Wu`zs8Tm/:]TgL(]v% O> 2u@[+NoZ'hR= .c,Xd@~IY\bQmJ1[d=.\'-@cIW'HCFx~ 7R!w..3gY`82|o[yAE/)n:'ZbL/)(Um}qk7gK6UO5Weg+ >b~ $^,wR 5Gh5^ t._&|ZPRp#L dbC=Q:m6Q3,}+HTd][1NQ^j{P2-3+Q#cmM "}b %'AhgFN=<_aPSo1Y5*0qd4iS]:Mfo jnwaK$lq]XxJ0.d0,lwBEjn5IqGU,r;CeaO-k<uT`=5~j9})kC9e_}\ReDud'`ky3;=W :KK_$ca a!1V#^Fd/׸ aY+"F}'V(4nk_owo/uk1^$l$Z "~ Sr#-_ 8#ϧ!hpm'!ps @B_1RZe1@b)7ZW _:(_ >n"㫼Z\,|iz*,S.x l7*9Or ])"U.{r-F RJ\0$Gi"d\,vbWm\Or^!+hw"MvD5-!Fmo7f)8 FlQT']~ C_[$>AXe{i$1b,7 'u\h M@MIEM>[^)5D.D=IWVe m2 R\f `d.E^;~N%D'vo0PUd _q@ .PVd" H#s*BCoWr>CSM7@$=\MQz?k W7 (fUc*`G*QpL{`zcl.7vt8tZ{):D3Wu?u.\Z#grDme JdMA),bUqZVyY{_]o-(zDLc.7F-$9mvERtxR7+$hX=Iur L LLv}=%9g%&jT^+v鞫W)Bz@?$M_V PRI1RWI:N< mϡ`Zy >v6wU9j'ahK4Co/@0kR86|#CcV,YD^H-@9oo,"ҟ?Pi,@Dm 5UNTmYHEK&vH_*X xI|:X"-kX$.0~ n*wF.\kZrh:gSCÁL4:I=>`1!3\/S}4n }`0"Qn7{AY-Qta;GadkVL_Pa&{N'X$d`hUG9tTzi2;j"m@D638ZLS._ 2AKh";= d Y}O.+90" fI!}^1lds 0 doii0`Y -XoOQ,&|pzo\/uD<}IT'XhKJ3>I!Ph k)*o[I'jk:~OKx W#HR`cP.=DyZ|gp( i).2,,0(Y7OV9usyS{Siuz2~0MhWh,P(XNj))uKn5,[]j'9|^͠r=gG1tUݔFb1 wGNUad="SZ/%4r u<4\\+uQ> @Vk1KPjJU5d4"Lj)G'n ? oIy=n%Ou2'9E`=Q~D J~Kdc  uP(z~g+1O0?~M];cb 'MS-` {Ab+]Z / @9-0=fn !Fazos!ig[tHQs& UZ%Y3i?F@jI$! dXZH^D qml<n_7]7"Q h| B$<3U 0JpadP=T~G5Y9j!y^w~?J& eMxN>aWbwGr $6@Bl>;CL r\?iov/SmuG1|.Px>9A\LT Nd]A9AdYtjR? ]tMN&G8O{ lGc2cUAo-0i67d}1kY;5ku-qb<#0aH9UTV4)yV iB J sq5S~_h4 . y !u, T_gn\Qwb13?Ww&g/l].4,2ot^:#cr((Tq:F ^T[1l$b4%^GtCCW(bv?Owt3=3J{xgU6lX}`@b7zvot_)bHS_|WRj7wgGl1E08S}\5A,M[ ;+|{FKg&?HmusMQ0m%'JOgO;R t6bXxHpK]=wv5c(x4"(HKfdxRM9:4DV#ufu-BT>]u7hOU)RihpXpfMQ7$h&)X0%B5#(eqJ:\PpE]t%Y6?zoRA ELQeW {KIS+&akGex*)p ArUonSm,WrR|)O F  IzefuNa{rRyy1d:k{ijm9em:*BEq BE" ?~koq7r]%u{ dYgr|-Hnx{`EZ4w 1qVsDdV=PYc-TOD3j=H{%)0[5 JNk0G4r&\kJ\e1/OjJS$51.R-N)MP ~\<}3*Xgc/rOkknN3?"FXC1 8+n"w!Zq4L!"cUK9uHuW.T'w5&R?0qj2:Q1d|m}GycB:E̘O#]A ̀Wv}$C7%Loq_{4yjIbMTn2RN4RE6=ɻ3/Ow{F\9U*e]03.y-jh$O"~v$K?}a?Fsr)PniV+G mJ\ 8N_3GF]u< bhnC+bUvfIs5VnP]`;HfN@XG+Wn!"*cv$?) {luRA*Y>?)I<+M0Q6PmpI%nY^pN&*9mo:&v7\yXv2*12KGi4uf KpUt35wSt$ʟa &3`J%4zP->+a;P%h {{xRZO T [:;:0509dg7*gcf]Cxz\I K"=vpkR>A\ۜDfuC &up0W 4kA^9Fnmaaa]V,Z+oV$n G=As((up3vGm<aRl\!C,f\TQXBUFE}B2V 6."^Hq:=r^Wk; Mppmvyk'Vy,PYDY4ik;-VLTMK875I+OC*9,N ,)]1W*|uzi(}##UlvZ48y+*`g| Zn2E6tG<)hK (1!C>/A:'y6!^H1'|B8G1UU{uV"(|mN?9@Fsf+R8[w8X2!4NB9yo4m-'pIF/,}'pU@i Z\v}; Fkj{`[Q5Jr`Q~;CV0k)o{? >Tzei`HDm~v0Jo c1b`,* OR/%CncHTK,PQTCX;YhgH x~(`Er.Z1C '#tr<X0xFE^?J=aI x~ R9fS7F~/%&}|n1?gBQsd ! {MWmq|DSIB"@?-[X+J2qF-~cvIZn8 q>97>i~Kfa06:]u'w1na8O*l+VdgkSi5#4ka/U$SZ*9HPah8d9PlO"tppOi1Gq.Ll{7#TAG֧닀ʢ{w+vNsVGMCIfhS)1,ɭ8v~>d=5}-/iy`%yfHv+.UWJOzZ)ku_cK*mWpL_v@(5wJ0&9|h%=9kL:=i$ACmgpK!'[-],{N=_`^` jO&:0kBEl>Al_+1wmH`imB_prX:|:F1^VD[(/S< APY- uf(o@F"cK jr!U=+JsP5p'"pX_ft21s|6hRK uc$˰؎œצݤƩ1h[Ռ?#iF-59!;.?f?ixD` R>$gU,\3)5$ 5$ " f;mz<=$^>_#yEh_< F#?46**"+8<0XOe[HuwCv'"F@$#9CN;RXTG;K[O1)C!6?!.\`D 0Tc;,e(|?n~rQh.SsX:HLCW(s'w4'er i1H2@%tWYl2 41d(k}d-a*(.^FnsDWaaFu[B=H|4rUHs/dqAOVED!uJ2W^hUXU`9& xjY/x';UR~_i${cXUM@9RW6;#6A3h wl%5r >1`N$Gf y,s-3P aE"fUu"~h2|uh>h>b>u2E`M L-DJ\|a fo^ G0t,.E;HA %]9YS:qJ2"&mv/V 4{K3wrr&5:6Ax8! ~ aCZL)&RbUYR`97&f.L0rn0W~g:O7{_1k9"IVN"^(>i&V%MkXW>LAk$gwG7J0t@}}Jck;z-@|>4;F(j,<9Tn7 5(9^ntn,b~.2X'/I^cT.WHvfj':jAA=I1ASu/?ܴDSnv+ax>U0f{7Su`5? h%uys!`p8T Le-NBziao4+2j7JHr Mvfoe*oDJU~!u6L_)n.5AQJBM<>X/&/dN?fk[5/+d7 XdQG8 Ti\*+!qf}Nt42Wswa!&e?ret5` rcKWxWme-x}gb5# !49e\NQ[ )Rkpc@ib{Q9R+1#T6$(rZ'I@MBb(*n;:Kjy\` lds+(rOp@4N<_Ckwa-W-a{1Is^sOyP}K_a+uaAIOO0,\5sb;tpp @ i1WG hu[/e#H i{"" lqsW6LwgS~+Y7nKf9Y7J( jvy3_5#ZF>,EB\_Q .vd +)GSt(&3;uI/pj}6Rpn<)I+`u3tOE.;>hsjGv C~w`Dr5\zXY'u-A#2 )UsA;|y<"2W& * >>'wx9N^o: p mq/:gLjhX3 1yin=AqI~6GYQi9PwR*_ca24BWO:EJ~k+Zy`c4@ Q7yd.Lq6)+H{q<:Whn<)q|gYi\BLULqJ/f1k.DrOYq؜hC,}4Y8+$ Cn*o&8s<,j38vZu 6E/5Vw 5i |5'd[6_;`:hI&e7G(?1:j0FKXs"MT .fjӌ8Bi.^%SZ| +89nT !Yplux.o#L'7PQ'F[2K5y K#2ZAX82&9Y,s5zR2{o$6 `fa1iv&Lfc$j{h4<D<3I8)olu|h I8(T1xq;A g:-6>+NC oT-dnzb:}8Qlmbo<=WXOqJvRGd*Vu !O#_w|;Re*cnD{Q7ONFg6Xb_ J`#o(,D^_}E8: 7u9Ev$:x_k(\L/-/|Q[9|(QZf\d 7%`A&, ='/mJeS>;Q 7(H+qXCDy~"3 :'9oH +wv=9'd?v'{;w[e7FEpWn'xlx-Q7Kfd]t} B^3ac4zlAE&rc%]SwfNoS c?MM"**]L2_%GKI<Dr<R c"|bg v ?"Wj@* Uj 5am\ SK)IIdzBBx7!bm v*7H :INkQ"#p2m .'xUyX[g_&+C#|,i\MpAy+/N Fke~Rr/pLZi }cDfBZgL<&d>MPv%O*f}k<,dKE 7Y\9FV"VG@vDk|3kVSSiOoiR)SO Wl-:NcSe#u?rKcZf!Hsp m5)\\ i^R.g>F+)FY+8 NIr|G`H"vl%\OWV@* @>*V:%@WX7| ck4<4b|G~?VM [>O2'Lq,kBzy&FHC1hZxb.VY;N 6P~)hBuJ7m;Ggeg4Tc?r RK R1j(9'N4'N(s#ZWAmd*,G$6Xngu;OV nv3EiqnDKb3?OA#X>/r`jnU_k5#beC Y#~n<7(1D4*N2 "@Kt"N{eiw@I~Q(Rf ;uDCT8kUle6,%Hz1",;chszfO"""'qi/*D"Lee4wj[O|X Ruqf4vYJ_ k & #IDzc*o=Pe:H6&WxatyTg>W:3Y5*,{kB(nr9pbpSN#>G eQ y/ |d3/V:xz7[2{4 \}k0u[( tUh!|{->~~$I-y()B~Z8i3F:G 3gA D }8< nisL(w3'>Q,ID$4JM`_W^G"D#*C.=d 7_fC_!oi1F,?yJb?pOHt_<>^ P|/iu|Ff<7(UspUn"=V Ej+"x{7OFVCN'A8T6{EYWy+#?r}uAbsR+'dT8 'o.9B)[le2 9+V+ZD7\i@6j/%aF-[P:zb^ rgl05&o\-Hm=wOO 1 F"\du/>*%R;1c /VRMhc8 4iL:(+YYO.k:5.z=dx"v13 XsB)~P5MinIO\G+AZ"T-bW;eJ_3\XNF Y1bJ})sXM~{F8$!5UNq"y(u@)uo%2 {{THIO"NH{! C{l"-(: Oh; 30lO9_ODSODH3/w`$Od8wl\XO; 'GZ}<'K{1(J-rYjig==0R~2N8D2 "own${qON;_WnQjtPHqy7$F= /jWb] @bX/, '$1 UfLc.{qEL0+[8~0!e\%'g p~I2s^8-m]/I$6 N@ aQOR<,+9{w^3" "mN+G[M^qf JmO`hv HFNXs#*F@!]bRkZ_#!O(%MTQ4mrL$<^` 0Nsl>^q3:9i/sNJ-sv3LK8 ix') ~k]SyXKf`/2~HJXW&Q]91:6Rnh_{'F`UbQ QuF+[z7IbQ> TVG;.GM'c?rhd;x;fd5m !65+~ X4tG[7fr;`|x7;xc25Fmwdpd `P 8d w6,$g'ga,uvFF*77%{`$YdGv4}NUeHa+Fgs7U)Rgk<i\n,/tBlLU? .C@i,n5|5MXY6SZ MBv J@cWZ/QGi rgmtjOC|~W@#fBv' ^Ud8YnlcO/3Q@KywK'UT ( lyqd6Si=p 2F3 iKB.|netH?z 3424Bn&7W4',IY|SBP,=fg7XqBQ}44F~VZ3tz 9Y|.wb5#@".y]b}H9tfSMFb'jyZ( ^u(`4}MKUT fsgRneJeZBp2By5T=B^u+ `4AsPqvdo2vδ~ =L2NE sXMA F#`] MFe O^<+bsWAU\KXpyy  Ap<,A`C ~RscVPZtA%et xr~_U[ lD_>$`3x0;sI/}3'_$Z <  9K*3,?$) wV΀j ;bƭ)f\:&dL`Xqk`pVvH7of;H~Kg[Q"D ^~ F)G]E/`'ez k GzTe cPf1" g18{Jy gJ*S1 f9m=qkkMi1)r* F[)k7x  o 歾䈀<.v>Es< !{_]*3+[Wp7c:KR 2*WLrRY&~ߖUSu):,:OuB![: VGQp{bE,VI+ ,H GF0Mr1,?Po U2vpT^guWs-,*",:.tk}&=iq%mP/a^azW_OgD TUMza,t_z D%CV|n+iN+1 u>{?V#?o{YHc{{bQ!jtpr~D<7 P^Xc,_T$|zd,+`BS4n%Hj6 %h9@2t&CA9]uR :z (cwHMVg;!9 F]Djc0*@umGH3V~f 5:^GFA*j{9al#8.(FO/ Mhⴼۻ䃪鷖ȅ͛Ϻܠln p~2a_35KBgNpvHsO%nw|j,.'%b7"=.NR 6;4"Q|3Q~(AMBzJc\x,1CD[6O]N~}hsQ-g{{B*kqZbqKJZ @^Xwzu 9R7T`~6d~v1 UH8;{~d( UGbik}=~(NoXDp5+qmj$qI|KwDSG~ IK&esJ!wXbfFw)@*Ex[> lag >f\%Vg.?,}s+=hVO{u\"x=SI*q/);3a35vCI&5]X?x!SZU`lb_>keO3icU4_uv*]x'e3>" Z@%p[ăMrqj hny_qO#%QYBn i xy/kp\QTA}4QJPv. 0-[-&q]}/H6(Zt.e6sAmot"Pwn:7P(y< M?0MNv0 B5hKOHff4 G[Dr{/1p?Otv"i-V<8 i*j3=%' j5{~=v*%\[LH!mql1"@PPOFX}tUP-aA Jl?R jbsAR^UYZqT"$[ O<.VjGG4Glg\^b-Zix2#2 0Nc,H FI:.T^f)O i8 PbZ W `5|jh23rDydz'lR@Wrx4DC/Co|79 46~&> Z$|$:AV}p"_7 > Oufgvzrs/vxZqV=hV$t>| Uw/`OONUne{ xf8!HD(p5^L|4Ll KRUBd1@P}:?GS{Ow^HxS}+ .V .Bvat֬K# [;\P[EI^BT+*`q5[ N59lL od[Z :x 1W=1us {?V/,O+5 P2s*M\%oz.rq] 6; B](8NvPlTU]WS${u{tjX.7RP0:BI!N,akf& /Bwl,<< a7{I.d~>N#jzxF&W18^I~mj$:Ab .:9HBk{rir27- "kD7< D9k_on& hTHOD4[6gyoy` U7SP#xK` mM@Is%z.OPc6%qQLT@X(8:'(>Z?p-2-*^xV/PTr0M5*vH( +?SVY8S;2f\k u+MI&va r=GZ-!G2LU`9 uEA-|(g EgN]\Vga4U$v{1 0q%P^TzR !4f P}8$jkS-=iU)aD% (P7.dIkxXR`n9YQ CjhZ1^$'N(6o1F>dg%Yw^{ #QF}5KAJ$b/UR7Knu-ir(lJ8&9.G_{="F_Zn~|P\ca;+AL|<3bTqbKibD6h;82s m5]`IHIZ jL tuAyIp>F11 ?Q\DS.yk+wE53LWy}16C1{j~T c>JR/RQN$h$SFK,vuIOx>]%:@htMNG5EQ e9*'v'@:*xIB,@m d/rG<%l)0B`LMd';S[6{_C&o`m.=,8 K3JL%hW$?'7 BsJG\T^6{ 8 & [K7-ZeAIcMc+!w\o&#aHNYx$qw{:#(ANHS 2 iv{_V)3 wy?.>U^WHzszY;ն7t=g(Xc]X_a*Wyuq5zz9=00YG*J\!|oO2j55B#%O\kzM MJy0^%tOD[O}P  {/kB.f6 v$%c^7)b^";gGA"xָg@^sSQDY!".(S_/<G5FiCwHD:#~-n&CcT^a ]`S$m{6G IdDAX )#7lNLZY]rVhv"HA=oQ$Qx o)@(;/W1gs +30=u2](*%e<%H (CS{,to\KNwDAXdh%__n/>.rVmQn<~qI#++D84$!ap&GqYy~Ҳ +^d_{F9,a== <5%wiKJ 4w<-j-*w1 V҈A`9H~QU Y8P[t|BAmWm+ׯT_KVSqpD_{sOGGSZK,%q)GjHB_LIjvC1?|7"SiQH7! 8e0ٻFu wuKM$cjC ?[TES]$zl"$ntwJTXq:?/?Q%,X=ZN3]2@y{3W H!][d/dB \~ c^y X[I2$z3(VYf:N7e^#ejw$"BBUBf~R\)+j-'q MI-+U4j.PE .BHOuURJk32; [HLQT KO7q`0J!!moX,Eo\BNGBMMJLHZD>](2 *go7K2%(6E.;csFcy8 \uaK"p'J'D dP(DO L3^EWTAXAEHbj4g&=)G'](-lup'wUf9MK AU^ M  ;NO7e:_;rHw,=VA__Z k27Ul.{ FID `j 84_- # e pp"1&v..@Ta>~q%UTIX"8KeVYLH 1x\(`19H#BXRL(^ MqPziSk_mdWiY }>;{Ԏ:ۂbXio(;+QL# uW>Gl7BWxZEr|TXTP ]>l Ѵ_wD7^/!o<{FZtp=m A7+x=R+ݍ&-why Y:l.%-|[,*1Jy[_/g7RS V !lzbcɇo?Tzjg=PB 42@k'H VFGw0~N`AZF,xW(1`o?PMmVOkf@w d\qgI^4 R= e! vw& unnxVLc%1TB #&,0 /I_\[onLc~B[ 5KC1JizyVBSwnACqe;jc/'<F^xS~=uWeZ;[)'WVMl#Q}ҶMus[U~u+;>&B\;I\=')B4RO,,, qHYYEhɽ"`^vGEo1z/|LR^D)h;=pAodcyxS; c3EcWt g5.)\n$G*HA! mg^]I:y =0y0 tU&N1& %nE w~p4'[pdJ%B&aU{ҕ*Lf )+wl``#A/R= I?g|D^Iu(T0,ce=(vO&%(jw%={|,O9YJ1*GX[5x8rZVIR!wV7$>5V`4D@oCsgb4D]\Yw`Ft(z%i@eWcA^w,JRp= e #2CO 1 2Hژ!<l p_vhQLg7 0J<[ c .s\Y : +3WºAl?/ko\1qFr{mt^Dn)X&wLZ}bC bl dwekίn i=y5y+tN_ l&";y!<!W saMKs 9h1w1>a$ݘEݒyk*Ʃ:pmZ ڎ>og""Z$J0Y+,vA2LWC S4G#9ug4g[rI>jqĘ>_9/C 5t/ٛa} :kQDXj`hT d 3M]b52r9=9'*wZp'^a :m, {>b9qdmtd4w2t?zLGWEu.VAh|-p6Vs識d\:=au25k.A' J! wfD1w} .wxY<:(JwUuS!Z @])8iG}GP X 7gU3 rld4[^@Ley[dk5_Bwwb|#MJnyuD3kI)f;EV%"Q[^3ve|Rn1u,Vc%z?05TS&mf%GD4i4.܌/e1khA,G9;- FXrI26fd NT~LyA7Z?;i ]2[GK e2H֣ (}.is }1*bIAZg%=1b"X)<%_$ +wCt.`UXS1G7&3 ,+a6BÈa(=JG~iZTJCdWLc"|V9!]"9@3-.5%fw.>{R=^*Z =uB'D+no7^IAlV'oi^[EԄ곹R G;H` p(Ukd'@c1 w'jCH];F05iP7U#HwY1I땰~*};U||{yAkEU=G=,{s]"&w]Pi/XbRo+v~J0HژSd/qHuT{6B˙a>`Tbe,ujr}xj `$} .5_%&%\O#8wobY[ ,dT_Yu= c)z?1B/  JO|+> nF(P²5;jaY+lLPI',Ch>|7>~c8K\`=IC%_4CoAgMsO_&YYBK#o/SnG YU *p:sJK7_TjPxAH*[%_bX@zD2DP/=:>)y[zZQ:- b "tEgOG< $8iBO94ZmQgC{4$VNe4>K2"fq78+=k0o6iwNXgHd$|7vF A.2Ab0?1ǸCtwM5Yne+e1c"Y@ռFH]bboR3,.Q(f4v$ D-Bl3AdO˿ÌBu@D\O;s_z b^W( .r9O3' uhLvٚ_I'$P0(yS) yhb}1&W v$?(rs8~I?#gNwep"P 7B j{VvgqH |5ES"ORZpҽO؃jYz*h5LGRJYw!>mxUl?94>pj8ob._' $Ruut[)|R]8 QDzH@3E>m+l~nH",(iC, SG]pa4-c@!|GX$LHXeDCh03 G3umi5$R| hEvS<1!sW8,$Q.Hy:UfG*.=YB>":%OC&(;>e=CMC^As@aRa`l8|Xu* <-cap%}wX_D4FE3szϵJ[JXFXT K+Uq&m@}NRTq\JgI8y@v BيQzgGH Ά3]7cR 8>X?dۡvUVH\g+|7JR R;\%nFn{]7V$IhCnc h$7O/o+cp3Jabax$5M)J -E)!xE}0rtXEOhb 89z4+NΝ*rC @r'mHA]B ԏ*ۢy'QR f梶Cd?;wle%ɺՎ<'NhPK*b~7Wu ʲvEg#T0 i?n#UK?)]xn ur,m&u?|zN0?h>dTҕjBuE0LHE=!,nNKX"qW??_!8G ,|lS&BR'DŏKyr- .9o*i5!`mS3uϟf$nѓN*w|I&|)$ |9g0u1?L0 ) lC+8۶<ʗ'Ak]ic)uBО/cb,  w? B_vG_caڒUN!bP'F/&`dKEBz:㝧^Dֱ'+9 pDh+Jٻ"̼߫0mGq>P&3R]ò-djNk78'P+%66QJ Kp%>(\IKVz2f9V=TK @ifwEcD-X;w%7hlX6TFv T>ob[~ G*jO-ghoA/pKN&g5QH}K =3ϒ-wؽӣ!80s6Ljr)'B2KiH"s}G+-p\F!YJt{@fpga;>RShZFMI1AzF1} n ~`/ M'0#3vO`1} ~?b#m-"qrCǷ /{bTkOqYr'\W'XN0z48{ޔ\f@d&d<pdΚDl@_@7?C:nyI"w&˃rA"0n ] -RK`585=o F^zk8 09:wJBr@c& U@ʇb'wea<zAe&un }j;vC3Qh$Us <_,{@{n`F rT6zZU Y']fA̩j a-x\]=Mn=wDu_A?j9."])U(3F +{*O^4/5*^'8.u2Zhs9(p H?[jRI&i>@=^a?_N 3U3}uG%,.咎lĀ:MjK\x *WSCPGO7b/FL;)14aD=.?l# u@:>H U?f/# a0j f)o[ԚMNwo@@fQ]`:/`<1Q1'Zl6ufiz G] 0r@O4s)Zs0]Ms)/ V5iS"iF1rUytDkkzt9t4E$kMn.tnO}Ȃ~ ,189Un\-]\2 *@XmZ +Rf|q6J%>a"oKd6^[9l.][>lC\Nd `$IaL{# x/DWkn^W*hh%`XGF 6+լeHeub.\yMKE&a2nANBP@FDV{|yZE0 GpZ^_CkvZ%i+(&tv0?i>NBnecZN@.7O1;u'i}B\(BK6dV7-k! {TUnBN$!1N&li42jwIo%-3D X _k|=*v?BmVƽbdRvp6jB:Dmj3eWJ_!m IJ9d53InfC;Q+8'VV+R%P. (D@]_T}/ Wy7:6O 3DWtZN EaaXu#.YR@$cG~{;zCV> 8RS)nC!6S*pYM90=:t Q%X~k:mt!2.Eu?FBrlM z3BD#,ppxLnG5?_yC?[ yVyI#mbSOKW + /HAF^sp6NPoLXs[W(4*Kw26cVfc9ejSo,0$]izij@ TksM?Ljse5I!M{qVs~_K4 )|5<2IU5[F0ZRhaMWnaD(P &c/bUMX? ;'cfkJ5_i$>!8qdJ=T1+~6F[`^| d 'm0v3sL6t8=LPfwU'nR2O`x/0 FyprOVES$W)C(~ =p2N'1~+H#VntT!-8<#1ERd@E;l%PsXO|SrGfJ܀YMp<%_SW1 WF((Kh C8;a-?i{gAR_B">X7n m~OC+pj 3I`?Tr6GKtu-K|aɉ8Վ7|:=;8RH4Km%[(NTV*@G{HcfS;sL`ImwE47Xd;"(sFD`n4z/ A=PҐ.6bv'] @.6,2%]Hhl w-Cx&?-L.4EA2~BSl{e+?7wH&2m:Uep| \DڡAX~BQ k`SB_F) O~{{,e's;8q}soT]&#8h()9EoO616|5 (l;{Y&iZ__&|M$#n;&U;TWn`]Rg0WxgoVH=f% >qK5Z ´F~KM͋J#4-Z n DjA_Ic8grY B?&?si?`]JV(gdjk8YWqVR!^/0 )'s6zkJzK2 r4Me+ ~/z5tˠbaG)Wxjn0٧X2\v2FME3Xn<:.}3!V^|@bg&pPV! &=16OnAdkYQ*dL $)a!iO*$is2zb__-5;-1CKv?P\cA^BK3;`1&.0 C&R;v' /٘4 Q}M5@ZM"N8*r(BEN#Ӝ[;6'UrkDvEY.| !.uyؓSG,e=MJ;toe|T/; sS)8tL2!xMTI_tJ2h"`}!XaXZ7[)@_CF;Hj,d,Y4D$p4Em! yb-dN~!&4eJL@${qc&iZ k(Eҕ KC?|*+u2n1\%V2{l*@=pN> Yl qERai+$~ZY^f7^PB3qqifrA[Wl *eN˅TMFop/Ғ+KrhU"#jV<hX?7kGU~ Yy>;v.3HGb<)C{^+cDRUQ oE`jIiagCI>R)AyL!*TvSf/e^׍5m,ޅ(HoSWhVTFpf(q>1wZL GTaq gjj1  T1q4_ Sf5mgJ*VgUjd%pEW6; á0b82N0vI)`Ptb5G-^ )fvPlq5}jTn'7mb%c,]W8G!{X|I_V6_?/8xaPJOA\`^wR3_n<'$O7H X/)Wf]GVQ01QbJMvK]g9'kCS0Baa5#7Im18'_^q*\=Fi[u{DTdcGRim>y>}?lf}9{Qi;b=>%A8sUcC w|.,sXC޷pmit%IHuF1MUO{KN׆-pu'$M@ sTmiBGdg9U bTd{/zzxٰu*k*J88JKhqp5:n#CkF -x%62 xYc#<b,8#frzHRG]@F,(?:S%Hx\w-affJpw?)x k,w1iN6|4\AT $JmP\omD OhF+!@FLe$?HUB = ;fL i~Jp/Pof,h~E+WY^z,l>lX8v'ỵ;(}Z!jZ0QqWy=MCI6?$`Yo5/0i+0":;lr-|I ['Bs?G;Ur^`:gR"\" eB `T+P{kJ_;ifh}v77 a&+]d6 c `,!,+b2:t%va@XHaoX%/\TJmz^&7ca#<$x(= 3,f:s=^Tw1K0.G#uITRA"Bg^RA.0lM%Me&vC?h Y19\v8e;:K_oPg%o:aHAze"kE|@uO-_~e\OL!=b̶re¡~rA]~^)i\ peiB)=KS8np lQ{w-m\'dy{g>Mp#xaf`HKe?( RWpg<3Њ ~~Gq37Yf́P8)a+'V ({\Qi)oUW:@4gEm!zR܅lR&}Jxs',Iޝ0\87ʚ,#nz};T[B)[x:;hL}MiN',na+%,V6Mk2;;yRE?3>7z`flS'9-'=ҵ` #Uo{ }3A Bb*z'PzX!CӛTR7i#['\xF;tg ooo<,: % I OYnVZ|+(A?U]XOgOe 30%4jܑKAjVCk8Z;@|(~c0 *YȏK|?^3Ms8/6)5"uWx]Dd_ S GeS >N=tk?7Pi0/-U<^F"]?@D_ L]qJu-d&IixFPnc^_!1#B kPsCyi~]TVo}t/_Gglo6\ 2jW\Dq:_=@tz|YO9r {hbH$aJX>DwOn|BD3#2RWkHMTR*I\Rzb!eGMq 0PDH G B\4a:6 b/>DMLB,77qTed #O(c1! eUX!^XZWs:9`|O>16) & 0xFFFF));/* STARTIO finishing... (IOSB_1 high word) */t] TRACE( 0x01FE0000 + ( iost2 & 0xFFFF));/* STARTIO finishing... (IOSB_2 low word) */ ] TRACE( 0x01FF0000 + ((iost2>>16) & 0xFFFF));/* STARTIO finishing... (IOSB_2 high word) */ C ioc_std$relchan( (UCB *) ucb ); /* Release the data channel */r0//#saythis "Temporary test for the V_BSY bit..."Q if (baseucb.ucb$v_bsy == 0) /* Is this an expected interrupt? */GP BPTRACE( 0x010F0000 ); /* STARTIO ending *WITHOUT* V_BSY! */1//#saythis "Temporary test for UCB corruption..." ` if (baseucb.ucb$l_bcnt != irp->irp$l_bcnt) /* Is bcnt correct? */> { /* If not, then... */R// TRACE( 0x01300000 ); /* UCB$L_BCNT corruption (by IRP over-copy!) */R BPTRACE( 0x01300000 ); /* Blammo! */ }a` if (baseucb.ucb$l_boff != irp->irp$l_boff) /* Is boff correct? */> { /* If not, then... */R// TRACE( 0x01310000 ); /* UCB$L_BOFF corruption (by IRP over-copy!) */R BPTRACE( 0x01310000 ); /* Blammo! */ } \ if (baseucb.ucb$l_svapte != irp->irp$l_svapte) /* Is bcnt correct? */7 { /* If not, then... */=R// TRACE( 0x01320000 ); /* UCB$L_SVAPTE corruption (by IRP over-copy!) */R BPTRACE( 0x01320000 ); /* Blammo! */ } M//#saythis "...End of temporary new copies to hack around VBNMAPFAIL crashes" Z ioc_std$reqcom( iost1, iost2, (UCB *) ucb );/* Finish I/O, providing status in IOSB */ return; /* And return */ }  &/* PACKACK - Perform PACKACK operation *C * This routine is used to determine information about the drive so) * that is can be mounted and put to use.u * * Input:i& * ucb pointer to UCBA * init_time_flag Set to indicate we're being called duringeC * initialization time: modifies processing.o * * Output: * none * * Return value: * status value:  * SS$_NORMAL - success*0 * SS$_NODATA - failed to get drive info9 * SS$_other -- specific error from a lower level* * * Notes:F *B * This is also the function called to process the IO$_SENSECHARC * that we queue to ourselves during initialization of non-systemnA * disks. Processing is essentially the same, but we stop short ? * of getting the capacity (for ATAPI drives) and setting ther" * ucb$v_valid volume valid bit. *A * If the ATA fetch_drive_info() fails, we could, conceptually,+? * look for the magic ATAPI signature in the STS, CYL_LO, and< * CYL_HI registers (STS=0x00: neither 'Ready' nor 'Busy',@ * CYL_HI=0xEB, CYL_LO=0x14). But instead, we just barge ahead@ * and do an ATAPI PACKET_IDENTIFY command and see if we get a * response. *> * Reportedly, certain TEAC CD-ROM drives refuse to speak toC * us until we read the "signature". (This signature is presentedi> * by ATAPI devices after a reset or refused command such as8 * ATA_GET_INFO.) We read the signature, just in case. *A * If an ATAPI drive has just powered up or the medium has just ? * been changed, there are certain special conditions we wantT< * to handle. If we were a more-sophisticated driver, we'd? * probably have a state machine handle all this but for now,f@ * we'll just do some empirically-determined retry code. TheseA * errors can also "stack up": If the drive has just powered-up C * and you have just inserted media into it, you can get SENSE=6,R? * ASC=0x29 ("Power on, reset, or bus reset occurred") on onef@ * retry and immediately get SENSE=6, ASC=0x28 ("``Not ready''; * to ``ready'' change , medium may have changed") on thee * next retry. * */  .int packack( DQ_UCB *ucb, int init_time_flag ) {wD char model[DTN$K_NAMELEN_MAX+1] = "Nonexistent IDE/ATAPI disk";# /* Model name upon failure *//T int mod_len = 26; /* Length of model string (*WITHOUT* trailing !) */+ DTN *dtn; /* Dummy DTN pointer */;> int status; /* Return status from various routines */' int retry; /* Retry counter */ 0 int cyl_lo; /* Drive CYL_LO register */0 int cyl_hi; /* Drive CYL_HI register */0 int drvsts; /* Drive status register */M ucb->ucb$l_sense_key = 0xDEADDEAD; /* Forget any remembered sense key */Y ucb->ucb$l_asc = 0xDEADDEAD; /* Forget any remembered additional sense code */ c ucb->ucb$l_ascq = 0xDEADDEAD; /* Forget any remembered additional sense code qualifier */ 8 status = fetch_drive_info( ucb, 0, init_time_flag ); /* Try an ATA get_info */e/ if ( $FAIL( status ) ) /* Did it fail? */o= { /* If so, then read "signature", just in case */Nf drvsts = inp( ucb, RD_STS ); /* Read device status, quashing any pending interupts as well */L cyl_hi = inp( ucb, RD_CYL_HI ); /* Read high order cylinder bits */K cyl_lo = inp( ucb, RD_CYL_LO ); /* Read low order cylinder bits */t< status = fetch_drive_info( ucb, 1, init_time_flag );- /* And try an ATAPI get_info instead */a }Q; if ( $FAIL( status ) ) /* Is status still failing? */o! { /* If so, then... */sJ status = ioc$add_device_type( model, mod_len, (UCB *) ucb, &dtn );B /* Change the device name to "Nonexistent IDE/ATAPI disk" */H return( SS$_NOSUCHDEV ); /* And exit with appropriate status */ }e/ /* Either ATA or ATAPI get_info worked */0N status = process_drive_info( ucb ); /* Collect the returned drive info */+ if ( $FAIL( status ) ) /* Success? */-> return( status ); /* If not, then exit with error */= if (init_time_flag) /* Doing this during init_time? *//F return( SS$_NORMAL ); /* If so, all done -- go no further */2 if (ucb->ucb$l_atapi_flag != 0) /* ATAPI ? */ {cD for (retry=0;retry<8;retry++) /* Try this eight times... */= { /* Drives take ~10 seconds to become ready */ S status = atapi_read_capacity( ucb, (BYTE *) ucb->ucb$ps_sense_buffer ); # /* Read the drive capacity */*5 if ( $SUCCESS( status ) ) /* Success? */ C break; /* If so, then break out of retry loop */ E BPTRACE( 0x04200000 ); /* BREAK: Error during packack */ S status = atapi_request_sense( ucb, (BYTE *) ucb->ucb$ps_sense_buffer );h6 /* Read the sense data to see what went wrong */ S BPTRACE( 0x04210000 ); /* BREAK: After request_sense during packack */ 2 if ( $FAIL( status ) ) /* Success? */E return( status ); /* If not, then exit with error */ _ if ( (ucb->ucb$l_asc==0x04) /* "Logical unit is in process of becoming ready" */ 9 && (ucb->ucb$l_ascq==0x01) ) /* : */(( { /* If so, then... */> sleep( ucb, 2 ); /* Hang out for 2 seconds */= continue; /* And commence the next retry */. }rF if (ucb->ucb$l_asc==0x28) /* "Medium may have changed" */? continue; /* If so, commence the next retry */ L if (ucb->ucb$l_asc==0x29) /* Various "Reset occurred" errors */? continue; /* If so, commence the next retry */ I if (ucb->ucb$l_asc==0x30) /* Incompatible medium in drive */ H return( SS$_MEDOFL ); /* Not much point in re-trying */? if (ucb->ucb$l_asc==0x3A) /* No medium in drive */ H return( SS$_MEDOFL ); /* Not much point in re-trying */Z if ( (ucb->ucb$l_asc==0x00) /* The drive doesn't think an error occurred */9 && (ucb->ucb$l_ascq==0x00) ) /* : */s {rc BPTRACE( 0x04220000 ); /* BREAK: Drive denies any error occurred during packack */o@ continue; /* Commence the next retry anyway */ } # /* Any other sense keys... */S BPTRACE( 0x04230000 ); /* BREAK: Unhandled sense key during packack */PM return( SS$_DRVERR ); /* And default to a nice, safe disaster */:3 /* "SYSTEM-W-DRVERR, fatal driver error" */ $ } /* Next retry */Q status = atapi_process_size( ucb ); /* Collect the returned drive info */0/ if ( $FAIL( status ) ) /* Success? */ B return( status ); /* If not, then exit with error */ }c= baseucb.ucb$v_valid = 1; /* Set the Volume VALID bit */h? return( SS$_NORMAL ); /* Return to caller with success */i }h </* FETCH_DRIVE_INFO - This routine is used to read a drive's, * drive information page * * Input:O* * ucb pointer to the UCB4 * atapi_flag 0 -- to do an ATA drive_info6 * 1 -- to do an ATAPI drive_infoA * init_time_flag Set to indicate we're being called during C * initialization time: modifies processing./ * * Output: * none * * Status: * SS$_NORMAL --- success- * SS$_NODATA --- error reading the pageW% * SS$_TIMEOUT -- device timeout  * */0Gint fetch_drive_info( DQ_UCB *ucb, int atapi_flag, int init_time_flag ) {*/ int status; /* Routine return status */E( int orig_ipl; /* Original IPL */. int drverr; /* Drive error register *// int drvsts; /* Drive status register */ =//#saythis "Hacking around the funny buddy-init problem here" G /* (This test is bypassed for ATAPI devices during init time!) */>9 if ( (init_time_flag == 0) /* After init time? */ I || (atapi_flag == 0) ) /* Or trying an ATA (IDE) device? */% { /* If either, then... */iE status = wait_ready( ucb ); /* Wait for drive to be ready */ C if ( $FAIL( status ) ) /* Check the status for failure */]7 return( status ); /* Return with error */0 }*/*) * Take out the device lock and raise IPL  * Write the registers% * Then issue the appropriate command  * */t< device_lock( baseucb.ucb$l_dlck, RAISE_IPL, &orig_ipl );M out( ucb, WT_DRV_HD, ucb->ucb$l_drv_head ); /* Select drive and head 0 */ #ifdef NEVERE inp( ucb, RD_ALT_STS ); /* Read all the registers except STS */ B inp( ucb, RD_DATA ); /* : */C inp( ucb, RD_ERROR ); /* : */ E inp( ucb, RD_SEC_CNT ); /* : */eD inp( ucb, RD_SECTOR ); /* : */D inp( ucb, RD_CYL_LO ); /* : */D inp( ucb, RD_CYL_HI ); /* : */D inp( ucb, RD_DRV_HD ); /* : */#endif: if (atapi_flag) /* Expecting ATA or ATAPI drive? */3 out( ucb, WT_CMD, CMD_ATA_PACKET_IDENTIFY);l! /* Expecting ATAPI drive */  elseI out( ucb, WT_CMD, CMD_ATA_IDENTIFY_DEV);/* Expecting ATA drive *//8 status = dq_wfikpch( ucb->ucb$ps_kpb, orig_ipl, 1 );" /* Wait for the interrupt */> if ( $FAIL( status ) ) /* Any error (timeout, etc.) ? */; return( status ); /* If so, return with status */U? drvsts = inp( ucb, RD_ALT_STS ); /* Get the status byte */&: if ( IS_SET( drvsts, STS_M_ERR ) ) /* Any errors? */! { /* If so, then... */d@ drverr = inp( ucb, RD_ERROR ); /* Get the error byte */2 return( SS$_NODATA ); /* Return error */ }s /* Else success, so... */pD ucb->ucb$l_atapi_flag = atapi_flag; /* Remember ATA or ATAPI */0 return( SS$_NORMAL ); /* Return success */ }O D/* PROCESS_DRIVE_INFO - This routine is used to read and process theI * information returned by the drive in the ID page.B * * Input:D" * ucb pointer to the UCB * * Output: * none * * Status: * SS$_NORMAL -- successt * * * Note: *< * ATAPI CD-ROM drives don't return any information in the> * sectors, heads, and cylinders field of the block returned> * returned by identify_drive. So for now, we dummy this up.? * atapi_process_size() will fill in the actual values later.  * */ %int process_drive_info( DQ_UCB *ucb )r {;? char model[DTN$K_NAMELEN_MAX+1]; /* ASCIZ model name */n4 int mod_len; /* Length of model string */4 ID_PAGE *id_ptr; /* Pointer to the ID page */, DTN *dtn; /* Dummy DTN pointer */4 int status; /* Returned routine status */% int i; /* String index */CC/* Read the data from the sector buffer into the transfer buffer */uO move_sec_from_drive( ucb, (BYTE *) ucb->ucb$ps_xfer_buffer, BLK_SIZE_512 );c0 /* Get the returned data from the drive */1 id_ptr = (ID_PAGE *) ucb->ucb$ps_xfer_buffer;E% /* Now cast it as an ID_PAGE */ /*, * Do some sanity checks for magnetic drives *( * Else force some data for ATAPI drives * */r9 if (ucb->ucb$l_atapi_flag == 0) /* ATA or ATAPI ? */N { /* ATA */P if ( (id_ptr->cyls > MAX_CYLINDER) /* Check for too many cylinders */O || (id_ptr->heads > MAX_HEAD+1) /* or too many heads */ Q || (id_ptr->sectors > MAX_SECTOR) /* or too many sectors */lG || (id_ptr->cyls == 0) /* or too few cylinders */ H || (id_ptr->heads == 0) /* or too few heads */K || (id_ptr->sectors == 0) ) /* or too few sectors */;2 { /* Any of those are bad, so... */_ BPTRACE( 0x04300000 ); /* BREAK: Sanity checks failed during PROCESS_DRIVE_INFO */k= return( SS$_IVADDR ); /* Sanity failed - exit */o }a1 /* Copy over the geometry information... */IF baseucb.ucb$w_cylinders= id_ptr->cyls; /* Set the cylinders */@ baseucb.ucb$b_tracks = id_ptr->heads; /* and tracks */B baseucb.ucb$b_sectors = id_ptr->sectors;/* and sectors */O ucb->ucb$r_dq_dt.ucb$l_maxblock = /* Now set maxblock based on those */d8 baseucb.ucb$b_sectors /* : */7 * baseucb.ucb$b_tracks /* : */T; * baseucb.ucb$w_cylinders; /* : */ o if ( (id_ptr->cyls == MAX_CYLINDER) /* Check for 0x3FFF cylinders */ n && (id_ptr->heads == MAX_HEAD+1) /* and 16 heads */p && (id_ptr->sectors == MAX_SECTOR) /* and 63 sectors */p && (id_ptr->lba_total_blocks != 0) )/* and non-zero total blocks in the new word */Q { /* If all four, it's a good bet that it's a new, *BIG* drive */cF /* so modify our capacity information... */ int lbas;J lbas = id_ptr->lba_total_blocks; /* Set the local variable */4 ucb->ucb$r_dq_dt.ucb$l_maxblock = lbas;F /* Set maxblock directly, based on info from newer drives */H baseucb.ucb$w_cylinders = (lbas / MAX_BLOCKS_PER_CYLINDER )N + ( (lbas%MAX_BLOCKS_PER_CYLINDER) != 0);F /* Set the cylinders based on maxblock */F /* Add 1 cylinder if our calculation had any remainder */F /* This will get us to 33 GBs, then ucb$w_cylinders overflows */ }> set_geom( ucb ); /* Set the geometry in the drive */ } else { /* ATAPI */G baseucb.ucb$w_cylinders= MAX_CYLINDER+1;/* Set the cylinders */*= baseucb.ucb$b_tracks = MAX_HEAD+1; /* and tracks */f> baseucb.ucb$b_sectors = MAX_SECTOR; /* and sectors */P ucb->ucb$r_dq_dt.ucb$l_maxblock = /* and now maxblock, based on those */: baseucb.ucb$w_cylinders /* : */7 * baseucb.ucb$b_tracks /* : */ 9 * baseucb.ucb$b_sectors; /* : */e }B/*' * Set flags based on capabilities flagI * */ E ucb->ucb$l_drive_lba_capable = 0; /* Assume no LBA capability */T7 if ( IS_SET( id_ptr->capabilities_49, CAP_M_LBA ) )s /* If LBA capable */@ ucb->ucb$l_drive_lba_capable = 1; /* Set the LBA flag */E ucb->ucb$l_drive_dma_capable = 0; /* Assume no DMA capability */h7 if ( IS_SET( id_ptr->capabilities_49, CAP_M_DMA ) )00 /* If the drive is DMA capable, then... */C ucb->ucb$l_drive_dma_capable = ucb->ucb$l_ctrl_dma_capable; G /* Set the drive's DMA flag based on the controller's DMA flag */ H// if (ucb->ucb$l_drive_dma_capable) /* Is the drive DMA capable? */S// set_features( ucb, 0x03, 0x20 ); /* If so, then set multiword-DMA mode 0 */;V//// set_features( ucb, 0x03, 0x21 ); /* If so, then set multiword-DMA mode 1 */)// else /* Else if not, then... */ P// set_features( ucb, 0x03, 0x08 ); /* Set PIO mode 0 with flow-control *//* * Add the device type name  *3 * Ok, this is brain damaged, but we have to do it. 9 * Each of the bytes in the ASCII string is byte swapped.  * So, swap them back  * */C/ mod_len =(MODEL_LENGTH>DTN$K_NAMELEN_MAX) ? N DTN$K_NAMELEN_MAX : MODEL_LENGTH; /* Set the length of string */A for (i=0; i < (mod_len>>1)<<1; i += 2) /* For each word... */s {*L model[i] = id_ptr->model_number[i+1]; /* Copy the swapped bytes */9 model[i+1] = id_ptr->model_number[i]; /* : */e }*D if ( (mod_len & 1) == 1) /* Get the odd last byte if needed */9 model[mod_len-1] = id_ptr->model_number[mod_len]; N model[mod_len] = '\0'; /* Make the string ASCIZ so strlen can size it *//*B * Now, working backwards along the string, remove trailing spaces * */s for (i=1; i < mod_len; i++)o {*? if (model[mod_len - i] != ' ') /* Is this a space ? */u2 break; /* Non-space - leave loop */C model[mod_len - i] = '\0'; /* Terminate string at space */ }*O mod_len = strlen( model ); /* Get the new length (as ASCII, not ASCIZ) */q/*C * Now, add the device type and name for Dynamic Device Recognitiong * */tF status = ioc$add_device_type( model, mod_len, (UCB *) ucb, &dtn );: return( SS$_NORMAL ); /* Return success to caller */ }* hI/* ATAPI_PROCESS_SIZE - This routine is used to process the READ_CAPACITY @ * information returned by the ATAPI drive. * * Input:n" * ucb pointer to the UCB * * Output: * none * * Status: * SS$_NORMAL ---- SuccessP? * SS$_IVBUFLEN -- Not a 512, 2048, or 2352 byte blocksize* *& * * Notes:a *: * 1. Both values in the buffer are in big-endian formatD * 2. The value in the buffer is the *MAXIMUM* LBN, i.e., blocks-1I * 3. The value in the buffer may be expressed in 2KB (not 512B) blocks  * * */e%int atapi_process_size( DQ_UCB *ucb )s { 6 BYTE *sense_ptr; /* Pointer to the ID page */9 int blocks; /* Number of blocks on the volume*/ = int blocksize; /* Bytes per block for the volume */r2 sense_ptr = (BYTE *) ucb->ucb$ps_sense_buffer;3 /* Bind onto returned data as a byte array */ F blocks = ( sense_ptr[0]<<24 /* Re-order the maxblock value */0 | sense_ptr[1]<<16 /* : *// | sense_ptr[2]<<8 /* : */r. | sense_ptr[3] ) /* : */: + 1; /* Account for blocks vs. max LBN */ *? blocksize = sense_ptr[4]<<24 /* And the blocksize value */ - | sense_ptr[5]<<16 /* : */$- | sense_ptr[6]<<8 /* : */;+ | sense_ptr[7]; /* : */nL if ( (blocksize!=BLK_SIZE_512) /* Do we recognize this blocksize? */L && (blocksize!=BLK_SIZE_2048) /* SCSI-3 spec'd CD-ROM? blocksize*/M && (blocksize!=BLK_SIZE_2352) ) /* ATAPI tested CD-ROM blocksize? */nG return( SS$_IVBUFLEN ); /* If not, "Invalid buffer length" */ ; ucb->ucb$l_2K_flag = 0; /* Clear the 2K block flag */e= if (blocksize>=BLK_SIZE_2048) /* CD-ROM-sized blocks? */ ! { /* If so, then... */u= ucb->ucb$l_2K_flag = 1; /* Set the 2K block flag */aL ucb->ucb$r_dq_dt.ucb$l_maxbcnt = (MAX_ATAPI_2K_XFER * BLK_SIZE_512);9 /* And set the appropriate maximum transfer size */tD blocks = (blocks<<2); /* And account for 4-to-1 packing */ }f. /* Copy over the geometry information */@ ucb->ucb$r_dq_dt.ucb$l_maxblock = blocks; /* Set maxblock */< baseucb.ucb$b_sectors = 8; /* Dummy-up the sectors */; baseucb.ucb$b_tracks = 4; /* Dummy-up the tracks */,K baseucb.ucb$w_cylinders = (blocks>>5) /* Compute the cylinders (/32) */,X + ( (blocks&0x1F) != 0 ); /* Did our division have a remainder? */' /* If so, add another cylinder */aH /* This will get us to 1.05 GBs, then ucb$w_cylinders overflows */3 return( SS$_NORMAL ); /* Return succeeding */, }s L/* SET_GEOM - this routine is used to set the current geometry in the drive. * * Input:i" * ucb pointer to the UCB * * Output: * none * * Status:/ * SS$_NORMAL ---- success, geometry is ok- * SS$_BADPARAM -- geometry is incorrect0 * */tint set_geom( DQ_UCB *ucb )  {l) int sector; /* Sector number */A7 int drv_head; /* Drive drive/head register */)) int cyl; /* Cylinder number */ 9 int status; /* Status returned from routines *//* int orig_ipl; /* Original IPL */1 int drvsts; /* Drive status register */a0 int drverr; /* Drive error register */'/* Attempt to read the maximum block */sG sector = baseucb.ucb$b_sectors; /* Use highest sector number */a< drv_head = ucb->ucb$l_drv_head+(baseucb.ucb$b_tracks-1);$ /* Use highest head number */N cyl = baseucb.ucb$w_cylinders - 1; /* Use highest cylinder number *//*) * Take out the device lock and raise IPLh * Write the registers * Then issue the commandC * */0< device_lock( baseucb.ucb$l_dlck, RAISE_IPL, &orig_ipl );< out( ucb, WT_SEC_CNT, sector); /* Set sectors/track */> out( ucb, WT_DRV_HD, drv_head); /* Set heads/cylinder */H out( ucb, WT_CMD, CMD_ATA_INIT_DEV_PARAMS); /* Issue the command */T status = dq_wfikpch( ucb->ucb$ps_kpb, orig_ipl, 2 );/* Wait for the interrupt */? if ( $FAIL( status ) ) /* Any error (timeout, etc.) ? */ < return( status ); /* If so, return with status */@ drvsts = inp( ucb, RD_ALT_STS ); /* Get the status byte */: if ( IS_SET( drvsts, STS_M_ERR ) ) /* Any errors? */" { /* If so, then... */A drverr = inp( ucb, RD_ERROR ); /* Get the error byte */eN BPTRACE( 0x04400000 ); /* BREAK: Drive error during set_geometry */; return( SS$_IVADDR ); /* and return an error */u },= if ( IS_CLEAR( drvsts, STS_M_DRDY ) ) /* If not READY */x {aR BPTRACE( 0x04410000 ); /* BREAK: Drive not ready during set_geometry */? return( SS$_DRVERR ); /* return with DRIVE ERROR */p } 6 return( SS$_NORMAL ); /* Return with success */ }t tP/* SET_FEATURES - This routine is used to set the current features in the drive. * * Input:u" * ucb pointer to the UCB * * Output: * none * * Status:/ * SS$_NORMAL ---- success, geometry is okr- * SS$_BADPARAM -- geometry is incorrect_ * */07int set_features( DQ_UCB *ucb, int feature, int value )( {01 int drvsts; /* Drive status register */i0 int drverr; /* Drive error register */* int orig_ipl; /* Original IPL */9 int status; /* Status returned from routines */ < device_lock( baseucb.ucb$l_dlck, RAISE_IPL, &orig_ipl );E wait_ready( ucb ); /* Make sure unit is selected and ready */sS out( ucb, WT_FEATURES, feature ); /* Select the specific feature to be set */ = out( ucb, WT_SEC_CNT, value ); /* Set specific value */(< /* (A value is only meaningful for feature 0x03) */F out( ucb, WT_CMD, CMD_ATA_SET_FEATURES ); /* Issue the command */T status = dq_wfikpch( ucb->ucb$ps_kpb, orig_ipl, 3 );/* Wait for the interrupt */? if ( $FAIL( status ) ) /* Any error (timeout, etc.) ? */ < return( status ); /* If so, return with status */@ drvsts = inp( ucb, RD_ALT_STS ); /* Get the status byte */: if ( IS_SET( drvsts, STS_M_ERR ) ) /* Any errors? */" { /* If so, then... */A drverr = inp( ucb, RD_ERROR ); /* Get the error byte */ N BPTRACE( 0x04080300 ); /* BREAK: Drive error during set_features */; return( SS$_IVADDR ); /* and return an error */t } = if ( IS_CLEAR( drvsts, STS_M_DRDY ) ) /* If not READY */  {MR BPTRACE( 0x04080301 ); /* BREAK: Drive not ready during set_features */? return( SS$_DRVERR ); /* return with DRIVE ERROR */, }a6 return( SS$_NORMAL ); /* Return with success */ }t t /* SEEK - Perform Seek operation *A * IDE drives will return immediately upon issuing the first seek B * command. Subsequent commands will actually wait until the seekD * is completed. This routine issues only one seek command and thenC * completes the I/O. Any subsequent command will be stalled untilf * the seek is completed.m * * Input:  * ucb pointer to UCB * * Output: * status value+ * SS$_NORMAL -- seek complete  * */wint seek( DQ_UCB *ucb )f {e* int status; /* Status of calls */) int orig_ipl; /* Original IPL */ ( int cyl; /* Cylinder number */0 int drvsts; /* Drive status register *// int drverr; /* Drive error register */sB status = wait_ready( ucb ); /* Wait for drive to be ready */? if ( $FAIL( status ) ) /* Check the status for failure */V3 return( status ); /* Return with error */0/* Set up seek parameters */? cyl = ucb->ucb$l_media.lbn; /* Get the cylinder number */n/*) * Take out the device lock and raise IPL  * Write the registers * Then issue the command  * */ < device_lock( baseucb.ucb$l_dlck, RAISE_IPL, &orig_ipl );J out( ucb, WT_DRV_HD, ucb->ucb$l_drv_head); /* Select drive and head */= out( ucb, WT_SECTOR, 1); /* Put in the sector number */ > out( ucb, WT_CYL_LO, cyl); /* Low order cylinder bits */A out( ucb, WT_CYL_HI, cyl>>8); /* High order cylinder bits */ L out( ucb, WT_CMD, CMD_ATA_SEEK); /* Attempt to seek to the sector */8 status = dq_wfikpch( ucb->ucb$ps_kpb, orig_ipl, 4 );" /* Wait for the interrupt */> if ( $FAIL( status ) ) /* Any error (timeout, etc.) ? */; return( status ); /* If so, return with status */_? drvsts = inp( ucb, RD_ALT_STS ); /* Get the status byte */e9 if ( IS_SET( drvsts, STS_M_ERR ) ) /* Any errors? */t! { /* If so, then... */s@ drverr = inp( ucb, RD_ERROR ); /* Get the error byte */E BPTRACE( 0x04100000 ); /* BREAK: Drive error during seek */MD return( SS$_DRVERR ); /* Return with DRIVE ERROR status */ }i5 return( SS$_NORMAL ); /* Return with success */e }O )/* DRVCLR - Perform Drive Clear operation* * * Input:s * ucb pointer to UCB * * Output: * status value * */int drvclr( DQ_UCB *ucb )* {  return( SS$_NORMAL );  }S I&/* READRCT - Perform READRCT operation *3 * This routine returns the drive information page.b * * Input:t * ucb pointer to UCB * * Output: * none * * Return value: * status value% * SS$_NORMAL -- success*= * SS$_NODATA -- failed to get drive informationn * */ int readrct( DQ_UCB *ucb ) {n0 int status; /* Routine return status */) int orig_ipl; /* Original IPL */.0 int drvsts; /* Drive status register *// int drverr; /* Drive error register */n6 void *temp; /* Dummy for IOC$MOVTOUSER call */< status = wait_ready( ucb ); /* Wait for drive ready */2 if ( $FAIL( status ) ) /* Check for error */> return( status ); /* and return if there is one */< device_lock( baseucb.ucb$l_dlck, RAISE_IPL, &orig_ipl );M out( ucb, WT_DRV_HD, ucb->ucb$l_drv_head ); /* Select drive and head 0 */bF out( ucb, WT_CMD, CMD_ATA_IDENTIFY_DEV ); /* Ask for drive info */8 status = dq_wfikpch( ucb->ucb$ps_kpb, orig_ipl, 5 );" /* Wait for the interrupt */> if ( $FAIL( status ) ) /* Any error (timeout, etc.) ? */; return( status ); /* If so, return with status */s? drvsts = inp( ucb, RD_ALT_STS ); /* Get the status byte */8 if ( IS_SET( drvsts, STS_M_ERR) ) /* Any errors? */ { /* If so, then.. */@ drverr = inp( ucb, RD_ERROR ); /* Get the error byte */5 return( SS$_NODATA ); /* Exit with error */M } O move_sec_from_drive( ucb, (BYTE *) ucb->ucb$ps_xfer_buffer, BLK_SIZE_512 ); 0 /* Get the returned data from our drive */Y ioc_std$movtouser( ucb->ucb$ps_xfer_buffer, baseucb.ucb$l_bcnt, (UCB *) ucb, &temp );)% /* Move the data to the user */ 5 return( SS$_NORMAL ); /* Return with success */c }   (/* DIAGNOSE - Perform DIAGNOSE operation *H * This routine implements pass-through of user formatted ATAPI commandsH * directly to the device, usually for audio function of CD-ROM players. * * Input:  * ucb pointer to UCB * * Output: * none * * Return value: * status value% * SS$_NORMAL -- success = * SS$_NODATA -- failed to get drive information  * */.int diagnose(DQ_UCB *ucb ){/; BYTE *packet; /* The packet bytes within the UCB */*L int *packetl; /* The packet (as longwords) within the UCB */1 int status; /* Routine return status */bO int xfer_cnt; /* Count of blocks actually transferred (dummy here) */*: BYTE *buffe p~ DQDRIVER.BCKѡ![FREEWAREV5.DQDRIVER]DQDRIVER.C;1rzr; /* Pointer to our transfer buffer */9 BYTE *user_va; /* Returned user buffer address */  void *svaptr;i^ packetl = (int *) ucb->ucb$b_packet; /* Bind onto packet in the UCB as a longword array */- packetl[0] = 0; /* Clear the packet */ " packetl[1] = 0; /* : */" packetl[2] = 0; /* : */Z packet = (BYTE *) ucb->ucb$b_packet; /* Bind onto packet in the UCB as a byte array */H memcpy(packet, ucb->diagnose_command, ucb->diagnose_command_length);R buffer = (BYTE *) ucb->ucb$ps_xfer_buffer; /* Initialize our buffer pointer */V status = atapi_packet_command(ucb, buffer, baseucb.ucb$l_bcnt, &xfer_cnt, FALSE ); if (status == SS$_NORMAL) { % if (baseucb.ucb$l_bcnt > 0) {aF ioc_std$movtouser(buffer, xfer_cnt, (UCB *) ucb, &svaptr); }  }s3 ucb->ucb$l_bcr = baseucb.ucb$l_bcnt - xfer_cnt;) return(status); }   //* READ - Performs IO$_READxBLK driver function  *E * This routine issues READ commands to the drive. This routine willAF * break up the request into segments of not more than 127 sectors per * command. 3 * , * Input: r * ucb pointer to UCB * * Output: * status value * */iint read( DQ_UCB *ucb )  { < int offset; /* Offset within a possible 2K block *// int xfer_size; /* Size (in sectors) */ O int xfer_cnt; /* Count of 512-byte blocks read in the latest segment*/e, int blks_xfrd; /* For-loop index */1 int byte_cnt; /* Number of bytes read */l: BYTE *buffer; /* Pointer to our transfer buffer */8 int xfer_req; /* Number of sectors requested */0 int status; /* Routine return status *// int retry_cnt; /* Error retry count */3 int buf_ofs; /* Offset into user buffer */*9 BYTE *user_va; /* Returned user buffer address */B. TRACE( 0x07000000 ); /* READ starting */P status = wait_ready( ucb ); /* Wait for drive to be ready for a command */9 if ( $FAIL( status ) ) /* Check status for error */d3 return( status ); /* Return with error */ / /* Compute number of blocks and set up */0E xfer_size = (baseucb.ucb$l_bcnt + BLK_SIZE_512 - 1) >> BLK_SHIFT;d; if (xfer_size == 0) /* Was there any work to do ? */t> return( SS$_NORMAL ); /* Exit with success if not */ uR buffer = (BYTE *) ucb->ucb$ps_xfer_buffer; /* Initialize our buffer pointer */8 retry_cnt = 0; /* Initialize the retry counter */I for (blks_xfrd = 0; blks_xfrd < xfer_size;) /* For each segment... */A {/W xfer_req = xfer_size - blks_xfrd; /* Compute 512-byte blocks left to be read */S>/* Later, for unbuffered DMA, set up the map registers here */< if (ucb->ucb$l_2K_flag) /* A 2KB sector device? */c offset = ucb->ucb$l_media.lbn & 0x03;/* Calculate offset within our transfer buffer */o' else /* Else if no... */ 3 offset = 0; /* No offset required */S= status = read_dispatcher( ucb, xfer_req, &xfer_cnt );i /* Read this segment */? if ( $FAIL( status ) ) /* How did that segment go? */ {y_ if ( (ucb->ucb$l_asc==0x04) /* "Logical unit is in process of becoming ready" */e9 && (ucb->ucb$l_ascq==0x01) ) /* : */ @ sleep( ucb, 10 ); /* Hang out for 10 seconds */I if (ucb->ucb$l_asc==0x30) /* Incompatible medium in drive */*D return( status ); /* Not much point in re-trying */? if (ucb->ucb$l_asc==0x3A) /* No medium in drive */[D return( status ); /* Not much point in re-trying */T if (status==SS$_BADPARAM) /* Bad parameter (e.g., LBN out of range)? */C return( status ); /* We won't retry that either */hE if (status==SS$_VOLINV) /* Did the volume go invalid? */pC return( status ); /* We won't retry that either */c0 /* (A retry might erroneously succeed!) */ if (xfer_cnt == 0) { 7 retry_cnt++; /* Update retry count */ P if (retry_cnt == MAX_RETRY/2) /* Halfway through the retries? */ { P BPTRACE( 0x07010000 ); /* BREAK: read wants to do a reset */? reset_ctrl(ucb); /* If so, reset things */* } Q if (retry_cnt > MAX_RETRY) /* Were there too many retries yet? */*A return( status ); /* Yes, exit with error */o }u } * else {1@ if (xfer_cnt > 0) /* Was any data transferred ? */H retry_cnt = 0; /* Clear retry count on each success */ }e> if (xfer_cnt == 0) /* Check that we got something */) continue; /* Next retry */ 0/* Later, for unbuffered DMA, skip this block */7 /* This segment is now in the transfer buffer. */- /* Move the data segment to the user */yY byte_cnt = xfer_cnt << BLK_SHIFT; /* Calculate the byte count for this segment */ i if (byte_cnt > ucb->ucb$l_bcr) /* Check if the transfer exceeded the user's desired bytecount */s] byte_cnt = ucb->ucb$l_bcr; /* If so, minimize it to the user's actual request */ O buf_ofs = (ucb->ucb$l_media.lbn - ucb->ucb$l_org_media) * BLK_SIZE_512;v@ /* Calculate the offset (so far) into the user's buffer */< user_va = map_user_buffer( ucb, buf_ofs, byte_cnt );, /* Map that part of the user buffer */L TRACE( 0x07020000 + byte_cnt ); /* READ moving bytes to the user */B memcpy( user_va, &buffer[offset*BLK_SIZE_512], byte_cnt );0 /* And copy our data to the user buffer */J ucb->ucb$l_bcr -= byte_cnt; /* Update the byte count remaining */> ucb->ucb$l_media.lbn += xfer_cnt; /* Update the LBN */@ blks_xfrd += xfer_cnt; /* Update the for-loop index */" } /* Next segment */5 return( SS$_NORMAL ); /* Return with success */2 }g E/* READ_DISPATCHER - Figure out which routine to use for this segment2 *E * We have (up to) six different handlers for doing the actual reads.c1 * Based on three flags, dispatch to one of them.n * * Input: $ * ucb pointer to UCB< * xfer_req number of blocks remaining to transfer * * Output:: * xfer_cnt count of blocks actually transferred * status value * * Note: *D * You can modify this routine to select, on a case-by-case basis,, * which transfers will be done using DMA. * */ ?int read_dispatcher( DQ_UCB *ucb, int xfer_req, int *xfer_cnt ) {* int dispatch;yN dispatch = (ucb->ucb$l_atapi_flag << 2) /* Decide which routine to use */8 + (ucb->ucb$l_2K_flag << 1) /* : */> + (ucb->ucb$l_drive_dma_capable ); /* : */A switch (dispatch) /* Switch to the appropriate handler */n {e case (0x0):*e return read_ata_seg_pio( ucb, xfer_req, xfer_cnt ); /* ATA, 512-byte sectors, via PIO */e case (0x1):*e return read_ata_seg_dma( ucb, xfer_req, xfer_cnt ); /* ATA, 512-byte sectors, via DMA */  case (0x4):dm return read_atapi_512_seg( ucb, xfer_req, xfer_cnt, FALSE ); /* ATAPI, 512-byte sectors, via PIO */o case (0x5): l return read_atapi_512_seg( ucb, xfer_req, xfer_cnt, TRUE ); /* ATAPI, 512-byte sectors, via DMA */ case (0x6): h return read_atapi_2K_seg( ucb, xfer_req, xfer_cnt, FALSE ); /* ATAPI, 2KB sectors, via PIO */ case (0x7):rg return read_atapi_2K_seg( ucb, xfer_req, xfer_cnt, TRUE ); /* ATAPI, 2KB sectors, via DMA */ , default: /* Unexpected case */6 break; /* Fall into the bugcheck... */ }*; bug_check( INCONSTATE, FATAL, COLD ); /* So be it */ L return( SS$_ABORT ); /* (You should live so long as to get here) */ }t /B/* READ_ATA_SEG_PIO - Read one segment from an ATA drive using PIO *: * This routine performs the read of a single I/O segment.E * Each segment is a single read command of not more the MAX_ATA_XFERbD * sectors. The overall read I/O routine calls this routine for each6 * of the segments until the entire read is completed. * * Input:/$ * ucb pointer to UCB< * xfer_req number of blocks remaining to transfer * * Output:: * xfer_cnt count of blocks actually transferred * status value * * Note: * ? * o Some drives sometimes give the interrupt *VERY* quickly,EB * before I can get back to the WFIKPCH. (This probably occursA * when cached data is available in the drive.) I handle this(? * by caching the fact that an as-yet-unsolicited interruptP * occurred. * */B@int read_ata_seg_pio( DQ_UCB *ucb, int xfer_req, int *xfer_cnt ) { / int sec; /* Disk location (sector) */. int head; /* Disk location (head) */9 BYTE *buffer; /* Pointer to our transfer buffer */i, int cyl; /* Disk location (cyl) */6 int drv_head; /* Drive drive/head register */7 int status; /* Returned status from routine */_& int orig_ipl; /* Saved IPL */0 int drvsts; /* Drive status register *// int drverr; /* Drive error register */eD TRACE( 0x07100000 + xfer_req ); /* READ_ATA_SEG_PIO starting */T ucb->ucb$l_unsolicited_int = 0; /* Forget any pending unsolicited interrupts */D if (xfer_req > MAX_ATA_XFER) /* Check for too large a request*/@ xfer_req = MAX_ATA_XFER; /* and minimize if too big */R buffer = (BYTE *) ucb->ucb$ps_xfer_buffer; /* Initialize our buffer pointer */6 *xfer_cnt = 0; /* Clear count of blocks read */8 drv_head= ucb->ucb$l_drv_head; /* Get drive info */= if (ucb->ucb$l_drive_lba_capable) /* If LBA mode, ... */ 7 drv_head |= DRVHD_M_LBA; /* Set the LBA bit */EM compute_address( ucb, &sec, &head, &cyl ); /* Compute physical address */ /*) * Take out the device lock and raise IPLf * Write the registers * Then issue the command) * */ < device_lock( baseucb.ucb$l_dlck, RAISE_IPL, &orig_ipl );9 /* Take out the device lock for the first sector */ E out( ucb, WT_DRV_HD, drv_head|head ); /* Select drive and head */ @ out( ucb, WT_SEC_CNT, xfer_req ); /* Ask for "n" sectors */@ out( ucb, WT_SECTOR, sec ); /* Put in the sector number */? out( ucb, WT_CYL_LO, cyl ); /* Low order cylinder bits */VB out( ucb, WT_CYL_HI, cyl>>8 ); /* High order cylinder bits */K out( ucb, WT_CMD, CMD_ATA_READ_SECS ); /* Attempt to read the sector */(N for (;;) /* Do forever (for each sector in the transfer request)... */ {R< status = dq_wfikpch( ucb->ucb$ps_kpb, orig_ipl, 6 );" /* Wait for the interrupt */B if ( $FAIL( status ) ) /* Any error (timeout, etc.) ? */? return( status ); /* If so, return with status */eB drvsts = inp( ucb, RD_ALT_STS ); /* Get the status byte */A if ( IS_SET( drvsts, STS_M_ERR ) ) /* Check the status */h, { /* If any errors, then... */C drverr = inp( ucb, RD_ERROR ); /* Get the error byte */ T BPTRACE( 0x07110000 ); /* BREAK: Drive error during READ_ATA_SEG_PIO */A return( SS$_DRVERR ); /* Return with error status */f }nT move_sec_from_drive( ucb, &buffer[(*xfer_cnt)*BLK_SIZE_512], BLK_SIZE_512 );A /* Move the sector from the drive to our transfer buffer */t3 *xfer_cnt += 1; /* Count a block read */ 4 if (*xfer_cnt >= xfer_req ) /* Finished? */C break; /* If so, break out of the do-forever loop */ @ device_lock( baseucb.ucb$l_dlck, RAISE_IPL, &orig_ipl );> /* Else take out the deviced lock and go 'round again */8 } /* Next sector in the do-forever loop */= return( SS$_NORMAL ); /* Return to caller succeeding */e }  B/* READ_ATA_SEG_DMA - Read one segment from an ATA drive using DMA *: * This routine performs the read of a single I/O segment.E * Each segment is a single read command of not more the MAX_ATA_XFER D * sectors. The overall read I/O routine calls this routine for each6 * of the segments until the entire read is completed. * * Input: $ * ucb pointer to UCB< * xfer_req number of blocks remaining to transfer * * Output:: * xfer_cnt count of blocks actually transferred * status value * * Note: *? * o Some drives sometimes give the interrupt *VERY* quickly,uB * before I can get back to the WFIKPCH. (This probably occursA * when cached data is available in the drive.) I handle thise? * by caching the fact that an as-yet-unsolicited interrupti * occurred. * */t@int read_ata_seg_dma( DQ_UCB *ucb, int xfer_req, int *xfer_cnt ) {d/ int sec; /* Disk location (sector) */R. int head; /* Disk location (head) */9 BYTE *buffer; /* Pointer to our transfer buffer */, int cyl; /* Disk location (cyl) */6 int drv_head; /* Drive drive/head register */7 int status; /* Returned status from routine */& int orig_ipl; /* Saved IPL */0 int drvsts; /* Drive status register *// int drverr; /* Drive error register */oD TRACE( 0x07800000 + xfer_req ); /* READ_ATA_SEG_DMA starting */T ucb->ucb$l_unsolicited_int = 0; /* Forget any pending unsolicited interrupts */< *xfer_cnt = 0; /* Consider none of the blocks read */D if (xfer_req > MAX_ATA_XFER) /* Check for too large a request*/@ xfer_req = MAX_ATA_XFER; /* and minimize if too big */R buffer = (BYTE *) ucb->ucb$ps_xfer_buffer; /* Initialize our buffer pointer */8 drv_head= ucb->ucb$l_drv_head; /* Get drive info */= if (ucb->ucb$l_drive_lba_capable) /* If LBA mode, ... *//7 drv_head |= DRVHD_M_LBA; /* Set the LBA bit */ M compute_address( ucb, &sec, &head, &cyl ); /* Compute physical address */t, load_prdt( ucb ); /* Load the PRDT */< device_lock( baseucb.ucb$l_dlck, RAISE_IPL, &orig_ipl );B /* Take out the device lock so we can write the registers */C out( ucb, WT_DMA_CMD, DMA_CMD_M_INBOUND | DMA_CMD_M_INACTIVE );e7 /* Make sure the DMA controller is inbound */g7 /* (that is, reading disk -> writing memory) */$7 /* but not active yet */ K out( ucb, WT_DMA_AD0, ( ( (UINT) ucb->ucb$l_prdt_phy ) ) & 0xFF ); K out( ucb, WT_DMA_AD1, ( ( (UINT) ucb->ucb$l_prdt_phy ) >> 8 ) & 0xFF );MK out( ucb, WT_DMA_AD2, ( ( (UINT) ucb->ucb$l_prdt_phy ) >>16 ) & 0xFF );=K out( ucb, WT_DMA_AD3, ( ( (UINT) ucb->ucb$l_prdt_phy ) >>24 ) & 0xFF );)E /* Point the controller to the PCI address of our PRDT table */b\ out( ucb, WT_DMA_STS, DMA_STS_M_DRV1 | DMA_STS_M_DRV0 | DMA_STS_M_INT | DMA_STS_M_ERR );3 /* For now, set both drives as DMA-capable */uG /* Write "1"s to INT and ERR to clear them in case they're set */tE out( ucb, WT_DRV_HD, drv_head|head ); /* Select drive and head */c@ out( ucb, WT_SEC_CNT, xfer_req ); /* Ask for "n" sectors */@ out( ucb, WT_SECTOR, sec ); /* Put in the sector number */? out( ucb, WT_CYL_LO, cyl ); /* Low order cylinder bits */tB out( ucb, WT_CYL_HI, cyl>>8 ); /* High order cylinder bits */M out( ucb, WT_CMD, CMD_ATA_READ_DMA ); /* Attempt to read the sector(s) */*A out( ucb, WT_DMA_CMD, DMA_CMD_M_INBOUND | DMA_CMD_M_ACTIVE ); 7 /* Set the DMA controller inbound */e7 /* (that is, reading disk -> writing memory) */y7 /* and active */T8 status = dq_wfikpch( ucb->ucb$ps_kpb, orig_ipl, 7 );" /* Wait for the interrupt */> if ( $FAIL( status ) ) /* Any error (timeout, etc.) ? */; return( status ); /* If so, return with status */n? drvsts = inp( ucb, RD_ALT_STS ); /* Get the status byte */eQ if ( IS_SET( drvsts, STS_M_ERR ) ) /* Check the status (saved from above) */a) { /* If any errors, then... */ @ drverr = inp( ucb, RD_ERROR ); /* Get the error byte */Q BPTRACE( 0x07810000 ); /* BREAK: Drive error during READ_ATA_SEG_DMA */b> return( SS$_DRVERR ); /* Return with error status */ },< device_lock( baseucb.ucb$l_dlck, RAISE_IPL, &orig_ipl );% /* Take out the deviced lock *//U out( ucb, WT_DMA_CMD, DMA_CMD_M_INACTIVE ); /* Set the DMA controller inactive */u? device_unlock( baseucb.ucb$l_dlck, orig_ipl, SMP_RESTORE );L# /* Return the deviced lock */>A *xfer_cnt = xfer_req; /* Consider all of the blocks read */&= return( SS$_NORMAL ); /* Return to caller succeeding */l }r Y/* READ_ATAPI_512_SEG_PIO - Read one segment from a 512-byte-sector ATAPI drive using PIO *: * This routine performs the read of a single I/O segment.K * Each segment is a single read command of not more the MAX_ATAPI_512_XFERrD * sectors. The overall read I/O routine calls this routine for each6 * of the segments until the entire read is completed. * * Input:e$ * ucb pointer to UCB< * xfer_req number of blocks remaining to transfer/ * dma_flag whether or not to use DMA * / * * Output:: * xfer_cnt count of blocks actually transferred * status value * */ Pint read_atapi_512_seg( DQ_UCB *ucb, int xfer_req, int *xfer_cnt, int dma_flag ) {n9 BYTE *buffer; /* Pointer to our transfer buffer */e1 int offset; /* Offset within 2K block */s; BYTE *packet; /* The packet bytes within the UCB */ E int *packetl; /* The packet (as longwords) within the UCB */ / int status; /* Routine status value */C* int orig_ipl; /* Originial IPL */ _J TRACE( 0x07200000 + xfer_req ); /* READ_ATAPI_512_SEG_PIO starting */L if (xfer_req > MAX_ATAPI_512_XFER) /* Check for too large a transfer */A xfer_req = MAX_ATAPI_512_XFER; /* and limit it if so */*R buffer = (BYTE *) ucb->ucb$ps_xfer_buffer; /* Initialize our buffer pointer */^ packetl = (int *) ucb->ucb$b_packet; /* Bind onto packet in the UCB as a longword array */- packetl[0] = 0; /* Clear the packet */(" packetl[1] = 0; /* : */" packetl[2] = 0; /* : */[ packet = (BYTE *) ucb->ucb$b_packet; /* Bind onto packet in the UCB as a byte array */f; packet[0] = CMD_ATAPI_READ_12; /* Read(12) command */r*/* packet[1] = 0x00; /@ (Reserved) */C/* packet[2] = 0x00; /@ Address MSB (filled in momentarily) */ ,/* packet[3] = 0x00; /@ : : */,/* packet[4] = 0x00; /@ : : */,/* packet[5] = 0x00; /@ : LSB */D packet[6] = xfer_req>>24; /* Transfer length MSB in blocks */: packet[7] = xfer_req>>16; /* : : */9 packet[8] = xfer_req>>8; /* : : */ 6 packet[9] = xfer_req; /* : LSB */*/* packet[10] = 0x00; /@ (Reserved) */*/* packet[11] = 0x00; /@ (Reserved) */M offset = fill_packet_w_adx( ucb ); /* Fill the packet address cells*/cO status = atapi_packet_command( ucb, buffer, xfer_req, xfer_cnt, dma_flag ); J /* Do the common packet-command processing using appropriate mode */% if (dma_flag) /* Using DMA? */m! { /* If so, then... */ @ device_lock( baseucb.ucb$l_dlck, RAISE_IPL, &orig_ipl );% /* Take out the deviced lock */uY out( ucb, WT_DMA_CMD, DMA_CMD_M_INACTIVE ); /* Set the DMA controller inactive */ C device_unlock( baseucb.ucb$l_dlck, orig_ipl, SMP_RESTORE ); - } /* Return the deviced lock *//E if (status == SS$_DRVERR) /* Did it result in a drive error? *//! { /* If so, then... */ F atapi_request_sense( ucb, (BYTE *) ucb->ucb$ps_sense_buffer );% /* Get error info from drive */!1 status = atapi_xlate_error_to_vms( ucb ); ) /* Turn it into a VMS error code */a* return( status ); /* and exit */ }R< if ( $FAIL( status ) ) /* Check for any other error */1 return( status ); /* and exit if so */A /* All looks okay */2 return( SS$_NORMAL ); /* Return to caller */ }* H/* READ_ATAPI_2K_SEG - Read one segment from a 2Kbyte-sector ATAPI drive *: * This routine performs the read of a single I/O segment.J * Each segment is a single read command of not more the MAX_ATAPI_2K_XFERD * sectors. The overall read I/O routine calls this routine for each6 * of the segments until the entire read is completed. * * Input: $ * ucb pointer to UCB< * xfer_req number of blocks remaining to transfer/ * dma_flag whether or not to use DMAr * * Output:: * xfer_cnt count of blocks actually transferred * status value * */rOint read_atapi_2K_seg( DQ_UCB *ucb, int xfer_req, int *xfer_cnt, int dma_flag )e {<9 BYTE *buffer; /* Pointer to our transfer buffer */1 int offset; /* Offset within 2K block */ ; BYTE *packet; /* The packet bytes within the UCB */ E int *packetl; /* The packet (as longwords) within the UCB */c/ int status; /* Routine status value */e) int orig_ipl; /* Original IPL */5D int xfer_req_2K; /* Number of 2Kbyte sectors to transfer */E TRACE( 0x07300000 + xfer_req ); /* READ_ATAPI_2K_SEG starting */pK if (xfer_req > MAX_ATAPI_2K_XFER) /* Check for too large a transfer */E@ xfer_req = MAX_ATAPI_2K_XFER; /* and limit it if so */R buffer = (BYTE *) ucb->ucb$ps_xfer_buffer; /* Initialize our buffer pointer */^ packetl = (int *) ucb->ucb$b_packet; /* Bind onto packet in the UCB as a longword array */- packetl[0] = 0; /* Clear the packet */ " packetl[1] = 0; /* : */" packetl[2] = 0; /* : */[ packet = (BYTE *) ucb->ucb$b_packet; /* Bind onto packet in the UCB as a byte array */g offset = fill_packet_w_adx( ucb ); /* Fill the packet address cells and calculate the offset */dk xfer_req = xfer_req + offset - 1; /* Maximize the transfer so it spans any 2K-byte sectors needed */tg xfer_req_2K = (xfer_req >> 2) + 1; /* Divide the transfer request by 4 to account for 2K blocks */* /* and add 1 */re xfer_req = xfer_req_2K<<2; /* Now expand the 512-byte-block-oriented xfer_req to encompass */dH /* all of the 2K-byte block(s) to be transferred */; packet[0] = CMD_ATAPI_READ_12; /* Read(12) command */C*/* packet[1] = 0x00; /@ (Reserved) */?/* packet[2] = 0x00; /@ Address MSB (already filled-in) */a,/* packet[3] = 0x00; /@ : : */,/* packet[4] = 0x00; /@ : : */,/* packet[5] = 0x00; /@ : LSB */N packet[6] = xfer_req_2K>>24; /* Transfer length MSB in 2Kbyte sectors */< packet[7] = xfer_req_2K>>16; /* : : */; packet[8] = xfer_req_2K>>8; /* : : */,9 packet[9] = xfer_req_2K; /* : LSB */ */* packet[10] = 0x00; /@ (Reserved) */*/* packet[11] = 0x00; /@ (Reserved) */O status = atapi_packet_command( ucb, buffer, xfer_req, xfer_cnt, dma_flag );qF /* Do the common packet-command processing using desired mode */% if (dma_flag) /* Using DMA? */_! { /* If so, then... */R@ device_lock( baseucb.ucb$l_dlck, RAISE_IPL, &orig_ipl );* /* Take out the deviced lock */Y out( ucb, WT_DMA_CMD, DMA_CMD_M_INACTIVE ); /* Set the DMA controller inactive */ C device_unlock( baseucb.ucb$l_dlck, orig_ipl, SMP_RESTORE );*- } /* Return the deviced lock */aE if (status == SS$_DRVERR) /* Did it result in a drive error? */ ! { /* If so, then... */ F atapi_request_sense( ucb, (BYTE *) ucb->ucb$ps_sense_buffer );% /* Get error info from drive */o1 status = atapi_xlate_error_to_vms( ucb );A) /* Turn it into a VMS error code */A* return( status ); /* and exit */ }f< if ( $FAIL( status ) ) /* Check for any other error */1 return( status ); /* and exit if so */r2 return( SS$_NORMAL ); /* Return to caller */ }s t>/* DATACHECK - Performs data check function for read and write * requests. * * Input:f * ucb pointer to UCB * * Output: * status value( * SS$_NORMAL ----- success7 * SS$_DATACHECK -- data failed to compare  * */ int datacheck( DQ_UCB *ucb ) {g< int offset; /* Offset within a possible 2K block *// int xfer_size; /* Size (in sectors) */eT int xfer_cnt; /* Count of blocks read (to be compared) in latest segment */, int blks_xfrd; /* For-loop index */: BYTE *buffer; /* Pointer to our transfer buffer */5 int byte_cnt; /* Number of bytes compared */ 8 int xfer_req; /* Number of sectors requested */0 int status; /* Routine return status *// int retry_cnt; /* Error retry count */ 3 int buf_ofs; /* Offset into user buffer */ 9 BYTE *user_va; /* Returned user buffer address */n3 TRACE( 0x08000000 ); /* DATACHECK starting */e> ucb->ucb$l_bcr = ucb->ucb$l_org_bcnt; /* Get byte count */L ucb->ucb$l_media.lbn = ucb->ucb$l_org_media;/* Get first block number */B baseucb.ucb$l_bcnt = ucb->ucb$l_org_bcnt; /* Get byte count */A baseucb.ucb$l_svapte = ucb->ucb$l_org_svapte;/* Get SVAPTE */f< baseucb.ucb$l_boff = ucb->ucb$l_org_boff; /* Get BOFF */P status = wait_ready( ucb ); /* Wait for drive to be ready for a command */6 if ( $FAIL( status ) ) /* Check return status */4 return( status ); /* and exit on error *// /* Compute number of blocks and set up */GE xfer_size = (baseucb.ucb$l_bcnt + BLK_SIZE_512 - 1) >> BLK_SHIFT;,; if (xfer_size == 0) /* Was there any work to do ? */t> return( SS$_NORMAL ); /* Exit with success if not */R buffer = (BYTE *) ucb->ucb$ps_xfer_buffer; /* Initialize our buffer pointer */8 retry_cnt = 0; /* Initialize the retry counter */I for (blks_xfrd = 0; blks_xfrd < xfer_size;) /* For each segment... */e { W xfer_req = xfer_size - blks_xfrd; /* Compute 512-byte blocks left to be read */e< if (ucb->ucb$l_2K_flag) /* A 2KB sector device? */c offset = ucb->ucb$l_media.lbn & 0x03;/* Calculate offset within our transfer buffer */c' else /* Else if no... */ 3 offset = 0; /* No offset required */d= status = read_dispatcher( ucb, xfer_req, &xfer_cnt );  /* Read this segment */o? if ( $FAIL( status ) ) /* How did that segment go? */. { D if (xfer_cnt == 0) /* If no data was transferred... */ {h7 retry_cnt++; /* Update retry count */rP if (retry_cnt == MAX_RETRY/2) /* Halfway through the retries? */ {rS BPTRACE( 0x08010000 ); /* BREAK: datacheck wants to do reset */,A reset_ctrl( ucb ); /* If so, reset things */r } Q if (retry_cnt > MAX_RETRY) /* Were there too many retries yet? */0A return( status ); /* Yes, exit with error */  }R }n else { @ if (xfer_cnt > 0) /* Was any data transferred ? */H retry_cnt = 0; /* Clear retry count on each success */ } 6 /* This segment is now in our transfer buffer */" /* Do the data comparison */B byte_cnt = xfer_cnt << BLK_SHIFT; /* Compute byte count */i if (byte_cnt > ucb->ucb$l_bcr) /* Check if the transfer exceeded the user's desired bytecount */E] byte_cnt = ucb->ucb$l_bcr; /* If so, minimize it to the user's actual request */oO buf_ofs = (ucb->ucb$l_media.lbn - ucb->ucb$l_org_media) * BLK_SIZE_512;@ /* Calculate the offset (so far) into the user's buffer */< user_va = map_user_buffer( ucb, buf_ofs, byte_cnt );, /* Map that part of the user buffer */V TRACE( 0x08020000 + byte_cnt ); /* DATACHECK comparing bytes with the user */K status = memcmp( &buffer[offset*BLK_SIZE_512], user_va, byte_cnt );o; /* And compare user buffer and our transfer buffer */B9 if (status != 0) /* Check comparison results */fK return( SS$_DATACHECK ); /* Failed - return DATACHECK error */dJ ucb->ucb$l_bcr -= byte_cnt; /* Update the byte count remaining */< ucb->ucb$l_media.lbn += xfer_cnt; /* Bump the LBN */@ blks_xfrd += xfer_cnt; /* Update the for-loop index */" } /* Next segment */5 return( SS$_NORMAL ); /* Return with success */e }  r2/* WRITE - Performs IO$_WRITExBLK driver functions * * Input:i * ucb pointer to UCB * * Output: * status value * */ int write( DQ_UCB *ucb ) {e4 int byte_cnt; /* Number of bytes written *// int xfer_size; /* Size (in sectors) */cO int xfer_cnt; /* Count of blocks actually written in latest segment */ , int blks_xfrd; /* For-loop index */8 int xfer_req; /* Number of sectors requested */) int status; /* Routine status */ + int retry_cnt; /* Retry counter */(/ TRACE( 0x09000000 ); /* WRITE starting */tP status = wait_ready( ucb ); /* Wait for drive to be ready for a command */2 if ( $FAIL( status ) ) /* Check the error */; return( status ); /* if an error, then return */e/ /* Compute number of blocks and set up */AE xfer_size = (baseucb.ucb$l_bcnt + BLK_SIZE_512 - 1) >> BLK_SHIFT;u3 if (xfer_size == 0) /* Was there any work *//> return( SS$_NORMAL ); /* Exit with success if not */8 retry_cnt = 0; /* Initialize the retry counter */I for (blks_xfrd = 0; blks_xfrd < xfer_size;) /* For each segment... */( {cZ xfer_req = xfer_size - blks_xfrd; /* Compute 512-byte blocks left to be written */> status = write_dispatcher( ucb, xfer_req, &xfer_cnt );? if ( $FAIL( status ) ) /* How did that segment go? */ {(I if (ucb->ucb$l_asc==0x30) /* Incompatible medium in drive */uD return( status ); /* Not much point in re-trying */?  if (ucb->ucb$l_asc==0x3A) /* No medium in drive */ D return( status ); /* Not much point in re-trying */T if (status==SS$_BADPARAM) /* Bad parameter (e.g., LBN out of range)? */C return( status ); /* We won't retry that either */tE if (status==SS$_VOLINV) /* Did the volume go invalid? */_C return( status ); /* We won't retry that either */I0 /* (A retry might erroneously succeed!) */L if (status==SS$_O]~ DQDRIVER.BCKѡ![FREEWAREV5.DQDRIVER]DQDRIVER.C;1r|WRITLCK) /* Medium appers to be write-locked */D return( status ); /* Not much point in re-trying */C if (xfer_cnt == 0) /* If no data was transfered... */D {e7 retry_cnt++; /* Update retry count */CP if (retry_cnt == MAX_RETRY/2) /* Halfway through the retries? */ {eO BPTRACE( 0x09010000 ); /* BREAK: write wants to do reset */uA reset_ctrl( ucb ); /* If so, reset things */u }DQ if (retry_cnt > MAX_RETRY) /* Were there too many retries yet? */OA return( status ); /* Yes, exit with error */  }  }r else m { @ if (xfer_cnt > 0) /* Was any data transferred ? */H retry_cnt = 0; /* Clear retry count on each success */ }fB byte_cnt = xfer_cnt << BLK_SHIFT; /* Compute byte count */i if (byte_cnt > ucb->ucb$l_bcr) /* Check if the transfer exceeded the user's desired bytecount */ ] byte_cnt = ucb->ucb$l_bcr; /* If so, minimize it to the user's actual request */F ucb->ucb$l_bcr -= byte_cnt; /* Update byte count remaining */> ucb->ucb$l_media.lbn += xfer_cnt; /* Update the LBN */@ blks_xfrd += xfer_cnt; /* Update the for-loop index */ }E@ return( SS$_NORMAL ); /* Return to caller with success */ }b dF/* WRITE_DISPATCHER - Figure out which routine to use for this segment *F * We have (up to) six different handlers for doing the actual writes.1 * Based on three flags, dispatch to one of them.i * * Input:O$ * ucb pointer to UCB< * xfer_req number of blocks remaining to transfer * * Output:: * xfer_cnt count of blocks actually transferred * status value * * Note: *D * You can modify this routine to select, on a case-by-case basis,, * which transfers will be done using DMA. * */l@int write_dispatcher( DQ_UCB *ucb, int xfer_req, int *xfer_cnt ) {X int dispatch;sQ dispatch = (ucb->ucb$l_atapi_flag << 2) /* Decide which routine to use */t; + (ucb->ucb$l_2K_flag << 1) /* : */ < + (ucb->ucb$l_drive_dma_capable ); /* : */A switch (dispatch) /* Switch to the appropriate handler */  {  case (0x0):ue return write_ata_seg_pio( ucb, xfer_req, xfer_cnt ); /* ATA, 512-byte sectors, via PIO */e case (0x1):Be return write_ata_seg_dma( ucb, xfer_req, xfer_cnt ); /* ATA, 512-byte sectors, via DMA */a case (0x4):nn return write_atapi_512_seg( ucb, xfer_req, xfer_cnt, FALSE ); /* ATAPI, 512-byte sectors, via PIO */ case (0x5):hm return write_atapi_512_seg( ucb, xfer_req, xfer_cnt, TRUE ); /* ATAPI, 512-byte sectors, via DMA */l case (0x6):/i return write_atapi_2K_seg( ucb, xfer_req, xfer_cnt, FALSE ); /* ATAPI, 2KB sectors, via PIO */f case (0x7):Xh return write_atapi_2K_seg( ucb, xfer_req, xfer_cnt, TRUE ); /* ATAPI, 2KB sectors, via DMA */, default: /* Unexpected case */6 break; /* Fall into the bugcheck... */ }-; bug_check( INCONSTATE, FATAL, COLD ); /* So be it */aL return( SS$_ABORT ); /* (You should live so long as to get here) */ } kB/* WRITE_ATA_SEG_PIO - Write one segment to an ATA drive using PIO *A * This routine performs the write of a single I/O segment. Each=I * segment is a single read command of not more the MAX_ATA_XFER sectors.(B * The overall read I/O routine calls this routine for each of the/ * segments until the entire read is completed.  * * Input: & * ucb pointer to UCB> * xfer_req number of blocks remaining to transfer * * Output:< * xfer_cnt count of blocks actually transferred * status value * * Note: *? * o Some drives sometimes give the interrupt *VERY* quickly,tB * before I can get back to the WFIKPCH. (This probably occursA * when cached data is available in the drive.) I handle this ? * by caching the fact that an as-yet-unsolicited interrupts * occurred. * */aAint write_ata_seg_pio( DQ_UCB *ucb, int xfer_req, int *xfer_cnt )  {D3 int buf_ofs; /* Offset into user buffer */e9 BYTE *buffer; /* Pointer to our transfer buffer */ = int byte_cnt; /* Count of bytes to be transferred */d7 int cyl; /* Cylinder number and components */e6 int drv_head; /* Drive drive/head register *// int drverr; /* Drive error register */ 0 int drvsts; /* Drive status register */% int head; /* Head number */t( int idx; /* Zero fill index */) int orig_ipl; /* Original IPL */v8 int remainder; /* Bytes left at end of block */0 int sec; /* Sector number and count *// int status; /* Routine status value */*7 BYTE *user_va; /* Mapped user buffer address */ E TRACE( 0x09100000 + xfer_req ); /* WRITE_ATA_SEG_PIO starting */aT ucb->ucb$l_unsolicited_int = 0; /* Forget any pending unsolicited interrupts */F if (xfer_req > MAX_ATA_XFER) /* Check for too large a transfer */; xfer_req = MAX_ATA_XFER; /* and limit it if so *//R buffer = (BYTE *) ucb->ucb$ps_xfer_buffer; /* Initialize our buffer pointer */9 *xfer_cnt = 0; /* Clear count of blocks written */ > drv_head = ucb->ucb$l_drv_head; /* Get base drive info */= if (ucb->ucb$l_drive_lba_capable) /* If LBA mode, ... */i< drv_head |= DRVHD_M_LBA; /* ... set the LBA bit */H compute_address( ucb, &sec, &head, &cyl ); /* Compute the address *// /* Move the data segment from the user */i? byte_cnt = xfer_req << BLK_SHIFT; /* Compute byte count */= if (byte_cnt > ucb->ucb$l_bcr) /* Check for too large */t5 byte_cnt = ucb->ucb$l_bcr; /* Minimize it */aK buf_ofs = (ucb->ucb$l_media.lbn - ucb->ucb$l_org_media) * BLK_SIZE_512;@ /* Calculate the offset (so far) into the user's buffer */8 user_va = map_user_buffer( ucb, buf_ofs, byte_cnt );, /* Map that part of the user buffer */W TRACE( 0x09110000 + byte_cnt ); /* WRITE_ATA_SEG_PIO moving bytes from the user */tO memcpy( buffer, user_va, byte_cnt ); /* Copy the user data to our buffer */> /* If less than a full block, then zero the remainder */= remainder = byte_cnt & BLK_MASK; /* Compute remainder */E- if (remainder > 0) /* Is there any? */2! { /* If so, then... */rF remainder = BLK_SIZE_512 - remainder; /* Compute bytes left */J for (idx=0; idx < remainder; idx++) /* For each additional byte */8 buffer[byte_cnt+idx]=0; /* Zero the byte */ } /*) * Take out the device lock and raise IPLk * Write the registers * Then issue the command[ * */ < device_lock( baseucb.ucb$l_dlck, RAISE_IPL, &orig_ipl );9 /* Take out the device lock for the first sector */BE out( ucb, WT_DRV_HD, drv_head|head ); /* Select drive and head */i@ out( ucb, WT_SEC_CNT, xfer_req ); /* Ask for "n" sectors */@ out( ucb, WT_SECTOR, sec ); /* Put in the sector number */? out( ucb, WT_CYL_LO, cyl ); /* Low order cylinder bits */ B out( ucb, WT_CYL_HI, cyl>>8 ); /* High order cylinder bits */M out( ucb, WT_CMD, CMD_ATA_WRITE_SECS ); /* Attempt to write the sector */xN for (;;) /* Do forever (for each sector in the transfer request)... */ {c> status = wait_drq( ucb ); /* Wait for data request */6 if ( $FAIL( status ) ) /* Check for error */% { /* If any, then... */ G device_unlock( baseucb.ucb$l_dlck, orig_ipl, SMP_RESTORE );;# /* Release the device lock */ ? return( status ); /* And return failing status */  }aR move_sec_to_drive( ucb, &buffer[(*xfer_cnt)*BLK_SIZE_512], BLK_SIZE_512 );A /* Move the sector from our transfer buffer to the drive */x< status = dq_wfikpch( ucb->ucb$ps_kpb, orig_ipl, 8 );" /* Wait for the interrupt */B if ( $FAIL( status ) ) /* Any error (timeout, etc.) ? */? return( status ); /* If so, return with status */bB drvsts = inp( ucb, RD_ALT_STS ); /* Get the status byte */A if ( IS_SET( drvsts, STS_M_ERR ) ) /* Check the status */ , { /* If any errors, then... */C drverr = inp( ucb, RD_ERROR ); /* Get the error byte */pU BPTRACE( 0x09120000 ); /* BREAK: Drive error during WRITE_ATA_SEG_PIO */_A return( SS$_DRVERR ); /* Return with error status */e }u6 *xfer_cnt += 1; /* Count a block written */4 if (*xfer_cnt >= xfer_req ) /* Finished? */C break; /* If so, break out of the do-forever loop */t< device_lock( baseucb.ucb$l_dlck, RAISE_IPL, &orig_ipl );= /* Else take out the device lock and go 'round again */ 8 } /* Next sector in the do-forever loop */= return( SS$_NORMAL ); /* Return to caller succeeding */d }  B/* WRITE_ATA_SEG_DMA - Write one segment to an ATA drive using DMA *A * This routine performs the write of a single I/O segment. Each_I * segment is a single read command of not more the MAX_ATA_XFER sectors.oB * The overall read I/O routine calls this routine for each of the/ * segments until the entire read is completed.u * * Input:t& * ucb pointer to UCB> * xfer_req number of blocks remaining to transfer * * Output:< * xfer_cnt count of blocks actually transferred * status value * * Note: *? * o Some drives sometimes give the interrupt *VERY* quickly,rB * before I can get back to the WFIKPCH. (This probably occursA * when cached data is available in the drive.) I handle this;? * by caching the fact that an as-yet-unsolicited interrupt/ * occurred. * */*Aint write_ata_seg_dma( DQ_UCB *ucb, int xfer_req, int *xfer_cnt )  {s3 int buf_ofs; /* Offset into user buffer */ 9 BYTE *buffer; /* Pointer to our transfer buffer */ = int byte_cnt; /* Count of bytes to be transferred */u7 int cyl; /* Cylinder number and components */H6 int drv_head; /* Drive drive/head register *// int drverr; /* Drive error register */l0 int drvsts; /* Drive status register */% int head; /* Head number */$( int idx; /* Zero fill index */) int orig_ipl; /* Original IPL */ 8 int remainder; /* Bytes left at end of block */0 int sec; /* Sector number and count *// int status; /* Routine status value */ 7 BYTE *user_va; /* Mapped user buffer address *//E TRACE( 0x09800000 + xfer_req ); /* WRITE_ATA_SEG_DMA starting */eT ucb->ucb$l_unsolicited_int = 0; /* Forget any pending unsolicited interrupts */? *xfer_cnt = 0; /* Consider none of the blocks written */oF if (xfer_req > MAX_ATA_XFER) /* Check for too large a transfer */; xfer_req = MAX_ATA_XFER; /* and limit it if so */tR buffer = (BYTE *) ucb->ucb$ps_xfer_buffer; /* Initialize our buffer pointer */> drv_head = ucb->ucb$l_drv_head; /* Get base drive info */= if (ucb->ucb$l_drive_lba_capable) /* If LBA mode, ... */f< drv_head |= DRVHD_M_LBA; /* ... set the LBA bit */H compute_address( ucb, &sec, &head, &cyl ); /* Compute the address */H/* Later, for unbuffered DMA, skip this and set map registers instead */ /* : */ /* : */E/ /* Move the data segment from the user */o? byte_cnt = xfer_req << BLK_SHIFT; /* Compute byte count */e= if (byte_cnt > ucb->ucb$l_bcr) /* Check for too large */ 5 byte_cnt = ucb->ucb$l_bcr; /* Minimize it */.K buf_ofs = (ucb->ucb$l_media.lbn - ucb->ucb$l_org_media) * BLK_SIZE_512;.@ /* Calculate the offset (so far) into the user's buffer */8 user_va = map_user_buffer( ucb, buf_ofs, byte_cnt );, /* Map that part of the user buffer */W TRACE( 0x09810000 + byte_cnt ); /* WRITE_ATA_SEG_DMA moving bytes from the user */oO memcpy( buffer, user_va, byte_cnt ); /* Copy the user data to our buffer */ > /* If less than a full block, then zero the remainder */= remainder = byte_cnt & BLK_MASK; /* Compute remainder */e- if (remainder > 0) /* Is there any? */ ! { /* If so, then... */nF remainder = BLK_SIZE_512 - remainder; /* Compute bytes left */J for (idx=0; idx < remainder; idx++) /* For each additional byte */8 buffer[byte_cnt+idx]=0; /* Zero the byte */ } /* : */f /* : */ H/* Later, for unbuffered DMA, skip this and set map registers instead *//*) * Take out the device lock and raise IPLb * Write the registers * Then issue the command  * */s, load_prdt( ucb ); /* Load the PRDT */< device_lock( baseucb.ucb$l_dlck, RAISE_IPL, &orig_ipl );B /* Take out the device lock so we can write the registers */D out( ucb, WT_DMA_CMD, DMA_CMD_M_OUTBOUND | DMA_CMD_M_INACTIVE );7 /* Make sure the DMA controller is outbound */_7 /* (that is, reading memory -> writing disk) */e7 /* but not active yet */CK out( ucb, WT_DMA_AD0, ( ( (UINT) ucb->ucb$l_prdt_phy ) ) & 0xFF );LK out( ucb, WT_DMA_AD1, ( ( (UINT) ucb->ucb$l_prdt_phy ) >> 8 ) & 0xFF );tK out( ucb, WT_DMA_AD2, ( ( (UINT) ucb->ucb$l_prdt_phy ) >>16 ) & 0xFF );fK out( ucb, WT_DMA_AD3, ( ( (UINT) ucb->ucb$l_prdt_phy ) >>24 ) & 0xFF );dE /* Point the controller to the PCI address of our PRDT table */n\ out( ucb, WT_DMA_STS, DMA_STS_M_DRV1 | DMA_STS_M_DRV0 | DMA_STS_M_INT | DMA_STS_M_ERR );3 /* For now, set both drives as DMA-capable */ G /* Write "1"s to INT and ERR to clear them in case they're set */cE out( ucb, WT_DRV_HD, drv_head|head ); /* Select drive and head */f@ out( ucb, WT_SEC_CNT, xfer_req ); /* Ask for "n" sectors */@ out( ucb, WT_SECTOR, sec ); /* Put in the sector number */? out( ucb, WT_CYL_LO, cyl ); /* Low order cylinder bits */B out( ucb, WT_CYL_HI, cyl>>8 ); /* High order cylinder bits */O out( ucb, WT_CMD, CMD_ATA_WRITE_DMA ); /* Attempt to write the sector(s) */rB out( ucb, WT_DMA_CMD, DMA_CMD_M_OUTBOUND | DMA_CMD_M_ACTIVE );6 /* Set the DMA controller outbound */6 /* (that is, reading memory-> writing disk) */6 /* and active */8 status = dq_wfikpch( ucb->ucb$ps_kpb, orig_ipl, 9 );" /* Wait for the interrupt */> if ( $FAIL( status ) ) /* Any error (timeout, etc.) ? */; return( status ); /* If so, return with status */b? drvsts = inp( ucb, RD_ALT_STS ); /* Get the status byte */EQ if ( IS_SET( drvsts, STS_M_ERR ) ) /* Check the status (saved from above) */ ) { /* If any errors, then... */e@ drverr = inp( ucb, RD_ERROR ); /* Get the error byte */R BPTRACE( 0x09820000 ); /* BREAK: Drive error during WRITE_ATA_SEG_PIO */> return( SS$_DRVERR ); /* Return with error status */ }c< device_lock( baseucb.ucb$l_dlck, RAISE_IPL, &orig_ipl );% /* Take out the deviced lock */fU out( ucb, WT_DMA_CMD, DMA_CMD_M_INACTIVE ); /* Set the DMA controller inactive */(? device_unlock( baseucb.ucb$l_dlck, orig_ipl, SMP_RESTORE ); # /* Return the deviced lock */uD *xfer_cnt = xfer_req; /* Consider all of the blocks written */= return( SS$_NORMAL ); /* Return to caller succeeding */u }  nK/* WRITE_ATAPI_512_SEG - Write one segment to a 512-byte-sector ATAPI drivet *; * This routine performs the write of a single I/O segment.tL * Each segment is a single write command of not more the MAX_ATAPI_512_XFERE * sectors. The overall write I/O routine calls this routine for eacha7 * of the segments until the entire write is completed.! * * Input: $ * ucb pointer to UCB< * xfer_req number of blocks remaining to transfer= * buffer address of buffer to transfer data fromc/ * dma_flag whether or not to use DMA  * * * Output:: * xfer_cnt count of blocks actually transferred * status value * */RQint write_atapi_512_seg( DQ_UCB *ucb, int xfer_req, int *xfer_cnt, int dma_flag )  {(3 int buf_ofs; /* Offset into user buffer */ : BYTE *buffer; /* Pointer to our transfer buffer */= int byte_cnt; /* Count of bytes to be transferred */ ( int idx; /* Zero fill index */1 int offset; /* Offset within 2K block */ ; BYTE *packet; /* The packet bytes within the UCB */ E int *packetl; /* The packet (as longwords) within the UCB */ 8 int remainder; /* Bytes left at end of block *// int status; /* Routine status value *//7 BYTE *user_va; /* Mapped user buffer address */S) int orig_ipl; /* Original IPL */(G TRACE( 0x09200000 + xfer_req ); /* WRITE_ATAPI_512_SEG starting */rL if (xfer_req > MAX_ATAPI_512_XFER) /* Check for too large a transfer */A xfer_req = MAX_ATAPI_512_XFER; /* and limit it if so */cR buffer = (BYTE *) ucb->ucb$ps_xfer_buffer; /* Initialize our buffer pointer */; byte_cnt = 0; /* Clear count of bytes transferred */t/ /* Move the data segment from the user */S? byte_cnt = xfer_req << BLK_SHIFT; /* Compute byte count */= if (byte_cnt > ucb->ucb$l_bcr) /* Check for too large */ 5 byte_cnt = ucb->ucb$l_bcr; /* Minimize it */lK buf_ofs = (ucb->ucb$l_media.lbn - ucb->ucb$l_org_media) * BLK_SIZE_512;h@ /* Calculate the offset (so far) into the user's buffer */8 user_va = map_user_buffer( ucb, buf_ofs, byte_cnt );, /* Map that part of the user buffer */W TRACE( 0x09210000 + byte_cnt ); /* WRITE_ATA_SEG_PIO moving bytes from the user */hO memcpy( buffer, user_va, byte_cnt ); /* Copy the user data to our buffer */ > /* If less than a full block, then zero the remainder */= remainder = byte_cnt & BLK_MASK; /* Compute remainder */i- if (remainder > 0) /* Is there any? */c! { /* If so, then... */ F remainder = BLK_SIZE_512 - remainder; /* Compute bytes left */J for (idx=0; idx < remainder; idx++) /* For each additional byte */8 buffer[byte_cnt+idx]=0; /* Zero the byte */ }u^ packetl = (int *) ucb->ucb$b_packet; /* Bind onto packet in the UCB as a longword array */- packetl[0] = 0; /* Clear the packet */n" packetl[1] = 0; /* : */" packetl[2] = 0; /* : */[ packet = (BYTE *) ucb->ucb$b_packet; /* Bind onto packet in the UCB as a byte array */e= packet[0] = CMD_ATAPI_WRITE_12; /* Write(12) command */ */* packet[1] = 0x00; /@ (Reserved) */C/* packet[2] = 0x00; /@ Address MSB (filled in momentarily) */,,/* packet[3] = 0x00; /@ : : */,/* packet[4] = 0x00; /@ : : */,/* packet[5] = 0x00; /@ : LSB */D packet[6] = xfer_req>>24; /* Transfer length MSB in blocks */: packet[7] = xfer_req>>16; /* : : */9 packet[8] = xfer_req>>8; /* : : */ 6 packet[9] = xfer_req; /* : LSB */*/* packet[10] = 0x00; /@ (Reserved) */*/* packet[11] = 0x00; /@ (Reserved) */M offset = fill_packet_w_adx( ucb ); /* Fill the packet address cells*/O status = atapi_packet_command( ucb, buffer, xfer_req, xfer_cnt, dma_flag );iJ /* Do the common packet-command processing using appropriate mode */% if (dma_flag) /* Using DMA? */_! { /* If so, then... */r@ device_lock( baseucb.ucb$l_dlck, RAISE_IPL, &orig_ipl );% /* Take out the deviced lock */Y out( ucb, WT_DMA_CMD, DMA_CMD_M_INACTIVE ); /* Set the DMA controller inactive */sC device_unlock( baseucb.ucb$l_dlck, orig_ipl, SMP_RESTORE ); - } /* Return the deviced lock */vE if (status == SS$_DRVERR) /* Did it result in a drive error? */V! { /* If so, then... */ F atapi_request_sense( ucb, (BYTE *) ucb->ucb$ps_sense_buffer );% /* Get error info from drive */i1 status = atapi_xlate_error_to_vms( ucb );i) /* Turn it into a VMS error code */a* return( status ); /* and exit */ } < if ( $FAIL( status ) ) /* Check for any other error */1 return( status ); /* and exit if so */r2 return( SS$_NORMAL ); /* Return to caller */ }  sH/* WRITE_ATAPI_2K_SEG - Write one segment to a 2Kbyte-sector ATAPI drive * K * This routine would performs the write of a single I/O segment, but puntstN * because we don't yet know how to do the necessary Read/Modify/write(s) thatN * would be required to write VMS-sized 512-byte-blocks within 2Kbyte sectors. * * Input:I$ * ucb pointer to UCB< * xfer_req number of blocks remaining to transferM * dma_flag whether or not to use DMA (a dummy parameter right now)e * * Output:C * xfer_cnt count of blocks actually transferred (unused)  * status value * */Pint write_atapi_2K_seg( DQ_UCB *ucb, int xfer_req, int *xfer_cnt, int dma_flag ) { J TRACE( 0x09300000 + xfer_req ); /* WRITE_ATAPI_2K_SEG_PIO starting */_ return( SS$_WRITLCK ); /* We don't know how to write 2K (CD-ROM/DVD-ROM sized) blocks? */;6 /* (Presently, we'd need a read-modify-write) */ }e tA/* ATAPI_READ_CAPACITY - Read the drive capacity and bytes/sector  * * Input: $ * ucb pointer to UCB= * buffer address of buffer to transfer data into  * * Output: * status value< * Uninterpreted capacity data in the designated buffer * */ 4int atapi_read_capacity( DQ_UCB *ucb, BYTE *buffer ) {_; BYTE *packet; /* The packet bytes within the UCB */>E int *packetl; /* The packet (as longwords) within the UCB */cP int xfer_cnt; /* Count of sectors actually transferred (dummy here) */^ packetl = (int *) ucb->ucb$b_packet; /* Bind onto packet in the UCB as a longword array */- packetl[0] = 0; /* Clear the packet */ " packetl[1] = 0; /* : */" packetl[2] = 0; /* : */Z packet = (BYTE *) ucb->ucb$b_packet; /* Bind onto packet in the UCB as a byte array */E packet[0] = CMD_ATAPI_READ_CAPACITY; /* Read capacity command */e*/* packet[1] = 0x00; /@ (Reserved) */+/* packet[2] = 0x00; /@ Address MSB */*+/* packet[3] = 0x00; /@ : : */)+/* packet[4] = 0x00; /@ : : */s+/* packet[5] = 0x00; /@ : LSB */5*/* packet[6] = 0x00; /@ (Reserved) */*/* packet[7] = 0x00; /@ (Reserved) */*/* packet[8] = 0x00; /@ (Reserved) */*/* packet[9] = 0x00; /@ (Reserved) */*/* packet[10] = 0x00; /@ (Reserved) */*/* packet[11] = 0x00; /@ (Reserved) */G return( atapi_packet_command( ucb, buffer, 0, &xfer_cnt, FALSE ) );,= /* Do the common packet-command processing using PIO */  }  * :/* ATAPI_REQUEST_SENSE - Get the sense keys from the drive * * Input:u$ * ucb pointer to UCB= * buffer address of buffer to transfer data intoi * * Output: * status value2 * sense, asc, and ascq fields in ucb updated * */u4int atapi_request_sense( DQ_UCB *ucb, BYTE *buffer ) {u; BYTE *packet; /* The packet bytes within the UCB */eL int *packetl; /* The packet (as longwords) within the UCB */1 int status; /* Routine return status */rO int xfer_cnt; /* Count of blocks actually transferred (dummy here) */ ^ packetl = (int *) ucb->ucb$b_packet; /* Bind onto packet in the UCB as a longword array */- packetl[0] = 0; /* Clear the packet */e" packetl[1] = 0; /* : */" packetl[2] = 0; /* : */Z packet = (BYTE *) ucb->ucb$b_packet; /* Bind onto packet in the UCB as a byte array */E packet[0] = CMD_ATAPI_REQUEST_SENSE; /* Request_Sense command */ */* packet[1] = 0x00; /@ (Reserved) */*/* packet[2] = 0x00; /@ (Reserved) */*/* packet[3] = 0x00; /@ (Reserved) */1 packet[4] = 18; /* Allocation Length *//*/* packet[5] = 0x00; /@ (Reserved) */*/* packet[6] = 0x00; /@ (Reserved) */*/* packet[7] = 0x00; /@ (Reserved) */*/* packet[8] = 0x00; /@ (Reserved) */*/* packet[9] = 0x00; /@ (Reserved) */*/* packet[10] = 0x00; /@ (Reserved) */*/* packet[11] = 0x00; /@ (Reserved) */ F status = atapi_packet_command( ucb, buffer, 0, &xfer_cnt, FALSE );= /* Do the common packet-command processing using PIO */t< if ( $FAIL( status ) ) /* Check for any other error */1 return( status ); /* and exit if so */__ TRACE( 0x0C000000 + (buffer[2] & 0x0F) );/* ATAPI_REQUEST_SENSE storing the sense_key */pC ucb->ucb$l_sense_key = (buffer[2] & 0x0F); /* Save sense key */R TRACE( 0x0C010000 + buffer[12] ); /* ATAPI_REQUEST_SENSE storing the ASC */H ucb->ucb$l_asc = buffer[12]; /* Save additional sense code */\ TRACE( 0x0C020000 + buffer[13] ); /* ATAPI_REQUEST_SENSE storing the ASC Qualifier */R ucb->ucb$l_ascq = buffer[13]; /* Save additional sense code qualifier */5 return( status ); /* And return with status */m  }_ oZ/* ATAPI_PACKET_COMMAND - Do the common ATAPI packet command processing using desired mode * * Input: $ * ucb pointer to UCB= * buffer address of buffer to transfer data intorb * xfer_req number of blocks remaining to transfer (NOTE: 512-byte blocks! Not sectors!). * dma_flag whether or not to us DMA * * * Output: * vms status valueN * xfer_cnt count of blocks actually transferred. For IO$_DIAGNOSE,: * count of BYTES actually transferred. * * * Notes: *< * There are differences in the way drives from different? * vendors operate during the Packet command. In particular:  *? * - Some drives (including the Toshiba drives) don't givefD * an interrupt as DRQ asserts to request the command packet.B * Per the ATAPI spec dated 6/95, section 4.7, item 4, thisC * interrupt is optional. The code handles this situation by/ * explicitly waiting for DRQ to assert.e *@ * - The Sony drives don't seem to (always? ever?) give theC * expected interrupt as DRQ asserts to request that we readrB * data from the drive. It may be that the drive is alreadyA * ready with cached data and I'm squashing the 'rupt withd? * the read of the STATUS (STS) register. In any case, I = * handle this by checking for the drive to already be_> * non-busy and DRQ prior to waiting for the interrupt. *B * - Some drives sometimes give the interrupt *VERY* quickly,E * before I can get back to the WFIKPCH. (This probably occursxD * when cached data is available in the drive.) I handle thisB * by caching the fact that an as-yet-unsolicited interrupt * occurred.A * ;* */ `int atapi_packet_command( DQ_UCB *ucb, BYTE *buffer, int xfer_req, int *xfer_cnt, int dma_flag ) { 7 int drv_head; /* Drive drive/head register */0 int status; /* Routine status value */* int orig_ipl; /* Original IPL */4 int reason; /* Drive "interrupt reason" */5 int drvsts; /* Drive status register */n0 int drverr; /* Drive error register */9 int drvdrq; /* Drive DRQ bit from STS/ALTSTS */e/ int drvbytcnt; /* Drive byte count */f: int buffer_size; /* Size of the target buffer */ IRP *irp;uC TRACE( 0x02000000 ); /* ATAPI_PACKET_COMMAND_DMA starting */b[ ucb->ucb$l_unsolicited_int = 0; /* Forget any pending unsolicited interrupts */rT reason = 0xDEADDEAD; /* Invalidate this, just in case anyone's looking */L *xfer_cnt = 0; /* Consider none of the blocks transfered */N buffer_size = 0; /* Set this to a default that's guaranteed to fail */i if (buffer == (BYTE *) ucb->ucb$ps_xfer_buffer) /* Pointing to the transfer buffer? */ _ buffer_size = XFER_BUFFER_SIZE; /* If so, remember that size */Ij if (buffer == (BYTE *) ucb->ucb$ps_sense_buffer) /* Pointing to the sense buffer? */_ buffer_size = SENSE_BUFFER_SIZE; /* If so, remember that size */ < /* Else we don't know any other buffer sizes */< /* -- leave it zero */C status = wait_ready( ucb ); /* Wait for drive to be ready */ @ if ( $FAIL( status ) ) /* Check the status for failure */4 return( status ); /* Return with error */? drv_head = ucb->ucb$l_drv_head; /* Get base drive info */ > if (ucb->ucb$l_drive_lba_capable) /* If LBA mode, ... */= drv_head |= DRVHD_M_LBA; /* ... set the LBA bit */D/*) * Take out the device lock and raise IPLD * Write the registers * Then issue the packet command5 * Then follow the drive's lead as to what to do nextt * */ < device_lock( baseucb.ucb$l_dlck, RAISE_IPL, &orig_ipl );C /* Take out the device lock so we can write the registers */u if (dma_flag)> {x0 load_prdt( ucb ); /* Load the PRDT */G out( ucb, WT_DMA_CMD, DMA_CMD_M_INBOUND | DMA_CMD_M_INACTIVE );b8 /* Make sure the DMA controller is inbound */8 /* (that is, reading disk -> writing memory) */8 /* but not active yet */O out( ucb, WT_DMA_AD0, ( ( (UINT) ucb->ucb$l_prdt_phy ) ) & 0xFF ); O out( ucb, WT_DMA_AD1, ( ( (UINT) ucb->ucb$l_prdt_phy ) >> 8 ) & 0xFF );uO out( ucb, WT_DMA_AD2, ( ( (UINT) ucb->ucb$l_prdt_phy ) >>16 ) & 0xFF );TO out( ucb, WT_DMA_AD3, ( ( (UINT) ucb->ucb$l_prdt_phy ) >>24 ) & 0xFF ); F /* Point the controller to the PCI address of our PRDT table */` out( ucb, WT_DMA_STS, DMA_STS_M_DRV1 | DMA_STS_M_DRV0 | DMA_STS_M_INT | DMA_STS_M_ERR );> } /* For now, set both drives as DMA-capable */H /* Write "1"s to INT and ERR to clear them in case they're set */ if (dma_flag)tV out( ucb, WT_FEATURES, 0x01 ); /* No Overlap (bit <1>), Yes DMA (bit <0>) */ elseU out( ucb, WT_FEATURES, 0x00 ); /* No Overlap (bit <1>), No DMA (bit <0>) */bT out( ucb, WT_DRV_HD, drv_head ); /* Select drive, ignore head */W out( ucb, WT_CYL_LO, buffer_size ); /* Low order cylinder bits/bytecount */tY out( ucb, WT_CYL_HI, buffer_size>>8 ); /* High order cylinder bits/bytecount */(Z out( ucb, WT_CMD, CMD_ATA_PACKET_CMD ); /* Issue the "packet" command */ if (dma_flag).E out( ucb, WT_DMA_CMD, DMA_CMD_M_INBOUND WGcC~ DQDRIVER.BCKѡ![FREEWAREV5.DQDRIVER]DQDRIVER.C;1r>hZA| DMA_CMD_M_ACTIVE );8 /* Set the DMA controller inbound */8 /* (that is, reading disk -> writing memory) */8 /* and active */L status = wait_drq( ucb ); /* Explicitly wait for DRQ (Toshiba fix) */3 if ( $FAIL( status ) ) /* Check for error */I# { /* If any, then... */i\ BPTRACE( 0x02010000 ); /* BREAK: WAIT_DRQ() failed during atapi_packet_command */C device_unlock( baseucb.ucb$l_dlck, orig_ipl, SMP_RESTORE );e$ /* Release the device lock */? return( SS$_CTRLERR ); /* and return complaining */n }AJ for (;;) /* Now, forever process based on the drive's requests */ {f@ drvsts = inp( ucb, RD_ALT_STS ); /* Read status byte */S if ( (dma_flag) /* If we're doing a DMA transfer */of && ( (reason & ~STS_M_DRQ)==0x01) ) /* and the last state was "Get Packet" */F { /* */X TRACE( 0x02060000 ); /* ATAPI quashing interrupt-bypass 'cause of DMA */Q drvsts = 0; /* then quash the captured status byte so we */ J } /* don't allow the interrupt to be bypassed */O if ( ( (drvsts & STS_M_BSY) == 0 ) /* Is the drive already idle? */dX && ( (drvsts & STS_M_DRQ) != 0 ) ) /* and waiting with DRQ asserted? */2 { /* If so, bypass WFIKPCH, etc. */T TRACE( 0x02020000 ); /* ATAPI taking the already-DRQ WFIKPCH bypass */\ ucb->ucb$l_unsolicited_int = 0; /* Forget any pending unsolicited interrupts */` drvsts = inp( ucb, RD_STS ); /* Read status byte to quash any pending interrupts */G device_unlock( baseucb.ucb$l_dlck, orig_ipl, SMP_RESTORE ); 6 } /* And release the device lock */ else4 { /* Else wait for an interrupt... */A status = dq_wfikpch( ucb->ucb$ps_kpb, orig_ipl, 11 );_# /* Wait for the interrupt */(F if ( $FAIL( status ) ) /* Any error (timeout, etc.) ? */ {MW BPTRACE( 0x020F0000 ); /* BREAK: ATAPI is handling a WFIPTCH error */bC return( status ); /* If so, return with status */l }aG drvsts = inp( ucb, RD_ALT_STS ); /* Get the status byte */c }Kb if ( IS_SET( drvsts, STS_M_ERR ) ) /* Any errors? */G { /* If so, then ... */bb drverr = inp( ucb, RD_ERROR ); /* Get the error byte */c BPTRACE( 0x02030000 + drverr ); /* BREAK: ATAPI drive error, "sense_key" stored */ b ucb->ucb$l_sense_key = drverr; /* Save latest sense key */= /* (Note: Raw register -- not in justified form!) */oZ return( SS$_DRVERR ); /* Caller may fill in more detail later */ }aF drvdrq = ( drvsts & STS_M_DRQ ); /* Get DRQ bit (val=0x08) */H reason = inp( ucb, RD_SEC_CNT ); /* See what the drive wants */B reason &= 0x07; /* Keep just [0:0:0:0:0:RLS:IO:CoD] */#//#saythis "Inoring REL for now..."+: reason &= 0x03; /* Throw away RELease, too. */6 reason |= drvdrq; /* 'OR' in the DRQ bit */& /* [0:0:0:0:DRQ:RLS:IO:CoD] */H switch (reason) /* Dispatch based on that combined reason */ {tB case (0x00): /* Write-data (and no DRQ) to drive */6 { /* *THAT* would be a surprise! */a BPTRACE( 0x02040000 ); /* BREAK: ATAPI error: "Write-data" requested w/o DRQ */mF return( SS$_DRVERR ); /* Then, make that an error */ break; })> case (STS_M_DRQ+0x00): /* Write data to drive */ {:C TRACE( 0x02040002 ); /* ATAPI Write-Data phase */tn drvbytcnt = inp( ucb, RD_CYL_LO ) /* Get the bytecount now desired by the drive */r | inp( ucb, RD_CYL_HI )<<8; /* : */h if (drvbytcnt > buffer_size) /* Is it too big to transfer? */R { /* If so, then... */f BPTRACE( 0x02040008 ); /* BREAK: ATAPI error: Bytecount mismatch on write-data */e return( SS$_DRVERR ); /* Make that an error instead of possibly */,A /* over-running our buffer */o } M /* For DIAGNOSE operations, return count of BYTES tranfered. *O * For all other operations, return count of BLOCKS transfered.o */)( irp = baseucb.ucb$l_irp;6 if (irp->irp$l_func == IO$_DIAGNOSE) {M /* Move the sector from our transfer buffer to the drive.uK * Note that 1 was added to drvbytcnt to force odd byte C * transfers to be rounded up to the next word.  */ N move_sec_to_drive( ucb, buffer + *xfer_cnt, drvbytcnt+1 );L *xfer_cnt += drvbytcnt; /* Count of bytes transfered */ } else { *[ move_sec_to_drive( ucb, &buffer[(*xfer_cnt)*BLK_SIZE_512], drvbytcnt );2B /* Move the sector from our transfer buffer to the drive */Q *xfer_cnt += ( (drvbytcnt + BLK_SIZE_512 - 1) >> BLK_SHIFT ); }c /* Count blocks written */tH device_lock( baseucb.ucb$l_dlck, RAISE_IPL, &orig_ipl );* /* Take the device lock again */+ /* As we go back to WFIKPCH again */e break; }cB case (0x01): /* Command packet wanted but no DRQ */6 { /* *THAT* would be a surprise! */[ BPTRACE( 0x02040001 ); /* BREAK: ATAPI error: "Cmd Pkt wanted" w/o DRQ */F return( SS$_DRVERR ); /* Then, make that an error */ }' R@ case (STS_M_DRQ+0x01): /* Command packet wanted */ {oG BYTE *packet; /* The packet bytes within the UCB */'G TRACE( 0x02040009 ); /* ATAPI Cmd Pkt Wanted phase */RH device_lock( baseucb.ucb$l_dlck, RAISE_IPL, &orig_ipl );( /* Take the device lock again */g packet = (BYTE *) ucb->ucb$b_packet; /* Bind onto packet in the UCB as a byte array */ae outw_t( ucb, WT_DATA, packet[ 0]|(packet[ 1]<<8) );/* Push out packet to the drive */N outw_t( ucb, WT_DATA, packet[ 2]|(packet[ 3]<<8) );/* : */N outw_t( ucb, WT_DATA, packet[ 4]|(packet[ 5]<<8) );/* : */N outw_t( ucb, WT_DATA, packet[ 6]|(packet[ 7]<<8) );/* : */N outw_t( ucb, WT_DATA, packet[ 8]|(packet[ 9]<<8) );/* : */N outw_t( ucb, WT_DATA, packet[10]|(packet[11]<<8) );/* : */> break; /* And go back to WFIKPCH again */ };8 case (0x02): /* "Get data" without DRQ */6 { /* *THAT* would be a surprise! */U BPTRACE( 0x02040002 ); /* BREAK: ATAPI error: "Get data" w/o DRQ */BF return( SS$_DRVERR ); /* Then, make that an error */ break; }0] case (STS_M_DRQ+0x02): /* Get the data from the silo */@M { /* and into the transfer buffer */0p /* */_ TRACE( 0x0204000A ); /* ATAPI Get-Data phase */pY if (dma_flag) /* Doing a DMA transfer? */tP { /* If so, we shouldn't be here -- this is a bad thing!*/d BPTRACE( 0x02060001 ); /* ATAPI Get-Data phase quashed 'cause of DMA */p } /* */Q else /* We're doing PIO -- go ahead and get the data */ p { /* */p drvbytcnt = inp( ucb, RD_CYL_LO ) /* Get the bytecount now desired by the drive */p | inp( ucb, RD_CYL_HI )<<8;/* : */h if (drvbytcnt > buffer_size) /* Is it too big to transfer? */R { /* If so, then... */f BPTRACE( 0x02050000 ); /* BREAK: ATAPI error: Bytecount mismatch on get_data */e return( SS$_DRVERR ); /* Make that an error instead of possibly */ U } /* over-running our buffer */ap /* */O /* For DIAGNOSE operations, return count of BYTES tranfered. @Q * For all other operations, return count of BLOCKS transfered.s */(* irp = baseucb.ucb$l_irp;9 if (irp->irp$l_func == IO$_DIAGNOSE) { I /* Move the sector from the drive to our xfer buffer. K * Note that 1 was added to drvbytcnt to force odd byte ? * transfers to be rounded up to next word.- */rM move_sec_from_drive( ucb, buffer+*xfer_cnt, drvbytcnt+1); L *xfer_cnt += drvbytcnt; /* Count of bytes transfered */ } else {^ move_sec_from_drive( ucb, &buffer[(*xfer_cnt)*BLK_SIZE_512], drvbytcnt );? /* Move the sector from the drive to our xfer buffer */sR *xfer_cnt += ( (drvbytcnt + BLK_SIZE_512 - 1) >> BLK_SHIFT );W } /* Count blocks read */eJ device_lock( baseucb.ucb$l_dlck, RAISE_IPL, &orig_ipl );? /* Take the device lock again */r? /* As we go back to WFIKPCH again */O }t break; }tP case (0x03): /* Reason 0x03 *WITHOUT* DRQ indicates completion */ {tJ TRACE( 0x02040003 ); /* ATAPI Normal Completion phase */@ if (dma_flag) /* Using DMA? If so, then... */V *xfer_cnt = xfer_req; /* Consider all of the blocks transfered */N return( SS$_NORMAL ); /* So break out of the forever loop */3 break; /* (Break for safety) */t }*T case (STS_M_DRQ+0x03): /* DRQ + Message from drive (future feature) */6 { /* *THAT* would be a surprise! */b BPTRACE( 0x0204000B ); /* BREAK: ATAPI error: "Message" from drive (with DRQ) */F return( SS$_DRVERR ); /* Then, make that an error */ break; }a7 case (0x04): /* "Release" without DRQ */a6 { /* *THAT* would be a surprise! */a BPTRACE( 0x02040004 ); /* BREAK: ATAPI error: "Release" from drive (w/o DRQ) */rF return( SS$_DRVERR ); /* Then, make that an error */ break; }e= case (STS_M_DRQ+0x04): /* "Release" with DRQ */e6 { /* Either way, it's surprising */b BPTRACE( 0x0204000C ); /* BREAK: ATAPI error: "Release" from drive (with DRQ) */F return( SS$_DRVERR ); /* Then, make that an error */ break; }Q7 case (0x05): /* Undefined reason 0x05 */l6 { /* *THAT* would be a surprise! */X BPTRACE( 0x02040005 ); /* BREAK: ATAPI error: Reason=0x05 (w/o DRQ) */F return( SS$_DRVERR ); /* Then, make that an error */ break; } F case (STS_M_DRQ+0x05): /* Undefined reason 0x05 + DRQ */6 { /* *THAT* would be a surprise! */X BPTRACE( 0x0204000D ); /* BREAK: ATAPI error: Reason=0x5 (with DRQ) */F return( SS$_DRVERR ); /* Then, make that an error */ break; }_7 case (0x06): /* Undefined reason 0x06 */r6 { /* *THAT* would be a surprise! */W BPTRACE( 0x02040006 ); /* BREAK: ATAPI error: Reason=0x6 (w/o DRQ) */_F return( SS$_DRVERR ); /* Then, make that an error */ break; } F case (STS_M_DRQ+0x06): /* Undefined reason DRQ + 0x06 */6 { /* *THAT* would be a surprise! */X BPTRACE( 0x0204000E ); /* BREAK: ATAPI error: Reason=0x6 (with DRQ) */F return( SS$_DRVERR ); /* Then, make that an error */ break; } 7 case (0x07): /* Undefined reason 0x07 */b6 { /* *THAT* would be a surprise! */W BPTRACE( 0x02040007 ); /* BREAK: ATAPI error: Reason=0x7 (w/o DRQ) */ F return( SS$_DRVERR ); /* Then, make that an error */ break; } F case (STS_M_DRQ+0x07): /* Undefined reason DRQ + 0x07 */6 { /* *THAT* would be a surprise! */X BPTRACE( 0x0204000F ); /* BREAK: ATAPI error: Reason=0x7 (with DRQ) */F return( SS$_DRVERR ); /* Then, make that an error */ break; }8 default: /* Out-of-range combination? */? { /* *THAT* would *REALLY* be a surprise! */ W BPTRACE( 0x020400FF ); /* BREAK: ATAPI error: Out-of-bounds Reason */dD bug_check( INCONSTATE, FATAL, COLD ); /* So be it */F return( SS$_DRVERR ); /* Then, make that an error */ break; }_, } /* End of the switch */. } /* End of the forever loop */# } /* Never gets here */  uD/* ATAPI_XLATE_ERROR_TO_VMS - Map the sense keys to a VMS error code * * Input:u@ * sense_buffer address of buffer to transfer data from * * Output: * vms status value * */)+int atapi_xlate_error_to_vms( DQ_UCB *ucb )b {$ int asc; int ascq;T< asc = ucb->ucb$l_asc; /* Get additional sense code */G ascq = ucb->ucb$l_ascq; /* Get additional sense code qualifier */ \ if ( (asc==0x04) && (ascq==0x01) ) /* "Logical unit is in process of becoming ready" */C return( SS$_MEDOFL ); /* becomes "Medium is offline" */ / /* Higher-level code will handle this. */h /* More inclusively, */tC if (asc==0x04) /* Various "Logical unit not ready" errors */eF return( SS$_MEDOFL ); /* all become "Medium is offline" */@ if (asc==0x21) /* "Logical block address out of range" */A return( SS$_BADPARAM ); /* becomes "Bad Parameter" */ 5 if (asc==0x28) /* "Medium may have changed" */n= { /* becomes "Volume is not software enabled" */iD baseucb.ucb$v_valid = 0; /* Also clear the VALID bit and */; return( SS$_VOLINV ); /* And return the status */d } ; if (asc==0x29) /* Various "Reset occurred" errors */_B return( SS$_MEDOFL ); /* become "Medium is offline" */@ if (asc==0x30) /* Various "Incompatible medium" errors */B return( SS$_MEDOFL ); /* become "Medium is offline" */? if (asc==0x3A) /* Various "Medium not present" errors */TB return( SS$_MEDOFL ); /* become "Medium is offline" */` BPTRACE( 0x02060000 ); /* BREAK: Untranslated sense key during atapi_xlate_error_to_vms */M return( SS$_DRVERR ); /* All else defaults to a nice, safe, disaster */i3 /* "%SYSTEM-W-DRVERR, fatal drive error" */l }  uE/* COMPUTE_ADDRESS - This routine is used to compute the head, sector 5 * and track information from a logical block number.i *? * Note that on IDE disks, sector numbers start at 1. Head and  * cylinder numbers start at 0.  *G * LBA mode addressing is handled if the LBA flag is set. In LBA mode,*D * the address is still returned in the sec, head and cyl locations,A * but it is simply the sections of the LBA. The callers of this A * routine simply write these value to the registers, so this all0 * works just fine.u * * Input:t" * ucb pointer to the UCB * * Output:0 * CHS mode LBA mode0 * sec sector number LBA[0:7]2 * head head number LBA[24:27]1 * cyl cylinder number LBA[8:23]a * */*Bvoid compute_address( DQ_UCB *ucb, int *sec, int *head, int *cyl ) {R> if (ucb->ucb$l_drive_lba_capable) /* LBA or CSH mode? */ { /* LBA mode... */ E *sec = ucb->ucb$l_media.lbn & 0x00FF; /* Bits 0-7 */bH *cyl = (ucb->ucb$l_media.lbn >> 8) & 0xFFFF; /* Bits 8 - 23 */I *head = (ucb->ucb$l_media.lbn >> 24) & 0x000F; /* Bits 24 - 27 */p }u else { /* CSH mode... */l int temp;PA *sec = ucb->ucb$l_media.lbn % baseucb.ucb$b_sectors + 1;/= temp = ucb->ucb$l_media.lbn / baseucb.ucb$b_sectors;., *head = temp % baseucb.ucb$b_tracks;, *cyl = temp / baseucb.ucb$b_tracks; }u }  E/* FILL_PACKET_W_ADX - This routine is used to fill the address field E * in the packet based on the logical blck numberi * * Input:r" * ucb pointer to the UCB * * Output:9 * packet certain field in the packet are filled-in  * * * Notes:, *6 * o Packets always use SCSI-style (~LBA) addressing *& * o In packets, the MSB comes first * */A$int fill_packet_w_adx( DQ_UCB *ucb ) {  int cd_rom_lbn;  int offset; ; BYTE *packet; /* The packet bytes within the UCB */ Z packet = (BYTE *) ucb->ucb$b_packet; /* Bind onto packet in the UCB as a byte array */= cd_rom_lbn = ucb->ucb$l_media.lbn; /* Get desired LBN */t7 offset = 0; /* Assume no offset in buffer */ = if (ucb->ucb$l_2K_flag) /* 2K blocks on this device? */ ! { /* If so, then... */tM offset = cd_rom_lbn & 0x03; /* Calculate offset within buffer */e? cd_rom_lbn = cd_rom_lbn>>2; /* Then divide LBN by 4 */= }TC packet[2] = (cd_rom_lbn >> 24) & 0x00FF; /* LBN bits [24:31] */_C packet[3] = (cd_rom_lbn >> 16) & 0x00FF; /* LBN bits [16:23] */ C packet[4] = (cd_rom_lbn >> 8) & 0x00FF; /* LBN bits [8:15] */ C packet[5] = (cd_rom_lbn ) & 0x00FF; /* LBN bits [0:7] */8 return( offset ); /* Return the offset, if any */ }: :D/* LOAD_PRDT - This routine is used to load the PRDT with 8 pointersB * pointing to the 8 PCI pages that map through to our * transfer buffer.v * * Input: % * ucb pointer to the UCBe * * Output: * none * * * Note: *E * Right now, we just load the PRDT with (essentially) static data;eF * this could easily be done at unit_init_fork time. But when we getC * to the point of doing direct DMA, this routine could get a lot F * more sophisticated (see the "More PRDT Fun" note in the beginningH * comments). At that point, the work would definitely need to be doneE * at the point where it is done now, namely, the beginning of each  * transfer. * */ void load_prdt( DQ_UCB *ucb )< { # int i; /* Loop counter */ B int page_base_adx; /* Starting address of this PCI page */( PRDT *prdt_tbl; /* PRDT table */e prdt_tbl = (PRDT *) ucb->ucb$l_prdt; /* Bind onto the PRDT as a vector of PRDT entries */ l page_base_adx = (UINT) ucb->ucb$l_xfer_phy; /* Get the beginning PCI address of the xfer_buffer */j for ( i=0; i>1); i++) /* For all the requested words... */ {t% /* Future DRQ test goes here */0P w_bu"nffer[i] = inpw( ucb, RD_DATA ); /* Get and enbuffer the data word */ }c }L _C/* MOVE_SEC_TO_DRIVE - This routine is used to move a sector to thek# * disk drive on a WRITE operation.B * * Input: % * ucb pointer to the UCB 1 * buffer pointer to buffer to read data]5 * bytecount number of bytes to write to drive.  * * Output:) * buffer updated buffer pointer * none * * * Note: *@ * Right now, we just push data words out to the drive as fast@ * as our little Alpha Processor will let us. Some day, with a; * faster processor, it may be necessary to actually poll<< * whether DRQ is asserted before pushing each word out to * the drive.< *J * (DRQ is constantly asserted throughout the duration of this routine.) * */ Bvoid move_sec_to_drive( DQ_UCB *ucb, BYTE *buffer, int bytecount ) {# int i; /* Loop counter */ , volatile int j; /* Delay counter */9 WORD *w_buffer; /* Point to the buffer as words */ F TRACE( 0x0B000000 + bytecount ); /* MOVE_SEC_TO_DRIVE starting */E w_buffer = (WORD *) buffer; /* Bind onto the buffer as words */2J for (i=0; i<(bytecount>>1); i++) /* For all the requested words... */ { % /* Future DRQ test goes here */ H outw( ucb, WT_DATA, w_buffer[i] ); /* Write out the data word */&//#saythis "Delay kludge for Brick..."4 for (j=0; j<100; j++) /* Kill some time */ {} }  }  F/* MAP_USER_BUFFER - this routine is used to directly map a section of * the users buffer. * * Input: " * ucb pointer to the UCB6 * offset offset to start of buffer to be mapped# * length total length to mapD * * Output: * user_va ) * returned address of mapped buffer  * */ <BYTE *map_user_buffer( DQ_UCB *ucb, int offset, int length )  { int pfn; /* PFN */0 int first_pte; /* First PTE number */4 int pte_cnt; /* Number of pages to map */% int i; /* Loop counter */ * int byte_ofs; /* Byte offset */3 PTE *user_pte; /* Current PTE pointer */b/ BYTE *s0_va; /* Current S0 address */ 4 PTE *s0_pte; /* Current S0 PTE address */4 BYTE *user_va; /* Mapped buffer address */8 uint64 *clr_pte; /* Pointer used to clear PTE */@#define PTE_BITS PTE$C_KOWN + PTE$C_KW + PTE$M_VALID + PTE$M_ASM2/* Calculate sizes, base PTE addresses and such */F offset += baseucb.ucb$l_boff; /* Compute true offset from page */J byte_ofs = offset & mmg$gl_bwp_mask; /* Compute byte offset in page */C first_pte = (offset >> MMG$GL_VPN_TO_VA) * PTE$C_BYTES_PER_PTE;  /* Compute PTE offset */^ pte_cnt = ( ( (offset & mmg$gl_bwp_mask) + length) + mmg$gl_bwp_mask) >> MMG$GL_VPN_TO_VA; /* Compute page count */A user_pte = (PTE *) ( (int) baseucb.ucb$l_svapte + first_pte); % /* Compute first PTE address */ C s0_va = ucb->ucb$ps_s0_va; /* S0 address of mapped region */n> s0_pte = ucb->ucb$ps_s0_svapte; /* Get S0 PTE address */J/* Loop over all of the PTEs and set them to double map the user buffer */ for (i=0; ipte$v_valid) /* Check for VALID user PTE */E pfn = user_pte->pte$v_pfn; /* It is - get copy of PFN */s elseK pfn = ioc_std$ptetopfn( user_pte ); /* Find PFN the hard way */o9/* The following should be set field by field, but PTEDEF 9 * doesn't have a proper definition for this, and frankly = * it's a pain ! So, define some bits and use them directly.r * *= * s0_pte->pte$v_own = PTE$C_KOWN; /@ Owner = Kernel @//G * s0_pte->pte$v_prot = PTE$C_KW; /@ Protection = Kernel Write @/ 2 * s0_pte->pte$v_valid = 1; /@ Valid page @/: * s0_pte->pte$v_asm = 1; /@ Address space match @/ */a: clr_pte = (void *) s0_pte; /* Point to the PTE */H *clr_pte = PTE_BITS; /* Clear the PTE and set constant bits */< s0_pte->pte$v_pfn = pfn; /* Now, include the PFN */> mmg$tbi_single( s0_va ); /* Invalidate the address */@ s0_va += MMG$GL_PAGE_SIZE; /* Point to the next page *// s0_pte++; /* Point to next S0 PTE */*3 user_pte++; /* Point to next user PTE */ }C/* Now, make a guard page */6 clr_pte = (void *) s0_pte; /* Get PTE address */' *clr_pte= 0; /* and clear it */ ; mmg$tbi_single( s0_va ); /* Invalidate the address */s)/* Return the S0 VA of the user buffer */O> user_va = (BYTE *) ( (int) ucb->ucb$ps_s0_va + byte_ofs );7 return( user_va ); /* Return with the address */* }  e./* UNLOAD - Perform IO$_UNLOAD driver function * * Input:e * ucb pointer to UCB * * Output: * status value= * SS$_NORMAL -- function completed successfullye * */iint unload( DQ_UCB *ucb )  {u8 baseucb.ucb$v_valid = 0; /* Clear the VALID bit */5 return( SS$_NORMAL ); /* Return with success */  }: "C/* WAIT_READY - Wait Until The Drive Is Ready. This means that theu: * BSY status bit is clear and the DRDY status bit is set. * * Input:  * ucb pointer to UCB * * Output: * status value@ * SS$_NORMAL ----- function completed successfully3 * SS$_DEVACTIVE -- BUSY never cleared: * * Note: *F * If DRV_HD is currently pointing at a non-existent drive (because,B * perhaps, the user last tried to mount a non-existent device),E * all the registers (including ALT_STS) tend to say "0xFF". RatherTD * than wait forever for this fake "busy" signal to clear, we just@ * barge ahead and select the drive we really want to talk to. * */ int wait_ready( DQ_UCB *ucb )  {e/ int status; /* Routine status value */R0 int drvsts; /* Drive status register */> int cyl_hi; /* High-order byte of cylinder address */= int cyl_lo; /* Low-order byte of cylinder address */a4 TRACE( 0x03100000 ); /* WAIT_READY starting */? drvsts = inp( ucb, RD_ALT_STS ); /* Get the status byte */ B if (drvsts!=0xFF) /* If it looks like a real drive, then */ {C if ( IS_SET( drvsts, STS_M_BSY ) ) /* Is the drive busy? *//$ { /* If so, then... */Z status = wait_busy( ucb ); /* Make sure BUSY is clear on the current drive */@ if ( $FAIL( status ) ) /* Check status for error */ {Rb TRACE( 0x03110000 ); /* WAIT_BUSY failed for WAIT_READY before drive selection */A return( status ); /* Exit with the error code */  }R }e }Db out( ucb, WT_DRV_HD, ucb->ucb$l_drv_head ); /* Select the drive we really want (and head 0) */? drvsts = inp( ucb, RD_ALT_STS ); /* Get the status byte */ B if (drvsts==0xFF) /* If it looks like a real drive, then */ { ` TRACE( 0x03120000 ); /* WAIT_READY trying to select an apparently-nonexistent drive */A return( SS$_DEVOFFLINE ); /* Exit with the error code */ }0O if ( IS_SET( drvsts, STS_M_BSY ) ) /* Is the newly-selected drive busy? */ ! { /* If so, then... */aT status = wait_busy( ucb ); /* Make sure BUSY is clear on this drive, too */= if ( $FAIL( status ) ) /* Check status for error */ {w] TRACE( 0x03130000 ); /* WAIT_BUSY failed for WAIT_READY after drive selection */0> return( status ); /* Exit with the error code */ }r } D if ( IS_SET( drvsts, STS_M_DRDY ) ) /* Check for drive READY */ {r: TRACE( 0x03140000 ); /* WAIT_READY succeeding */@ return( SS$_NORMAL ); /* Return succeeding if ready */ }A? /* Collect the other two pieces of the ATAPI signature */CH cyl_hi = inp( ucb, RD_CYL_HI ); /* Read high order cylinder bits */G cyl_lo = inp( ucb, RD_CYL_LO ); /* Read low order cylinder bits */ ^ if ( (drvsts==ATAPI_SIG_STS) && (cyl_hi==ATAPI_SIG_CYL_HI) && (cyl_lo==ATAPI_SIG_CYL_LO) ) {/P TRACE( 0x03150000 ); /* WAIT_READY barging ahead on ATAPI signature */V return( SS$_NORMAL ); /* If we see ATAPI signature, barge ahead w/o ready */ }r_ if ( (drvsts==ATAPI_SIG_STSE) && (cyl_hi==ATAPI_SIG_CYL_HI) && (cyl_lo==ATAPI_SIG_CYL_LO) )s {*_ TRACE( 0x03160000 ); /* WAIT_READY bar7;~ DQDRIVER.BCKѡ![FREEWAREV5.DQDRIVER]DQDRIVER.C;1r±Dging ahead on ATAPI signature (w/ error bit) */ V return( SS$_NORMAL ); /* If we see ATAPI signature, barge ahead w/o ready */ }rF TRACE( 0x03170000 ); /* WAIT_READY failing on non-ready drive */D return( SS$_DEVACTIVE ); /* Otherwise, exit with failure if *// /* not ready and not ATAPI signature */  }  t'/* WAIT_BUSY - Wait for BSY to be cleart * * Input:0 * ucb pointer to UCB * * Output: * status value * */Oint wait_busy( DQ_UCB *ucb ) {i2 int status; /* Routine status value */2 int drvsts; /* Drive status register */5 __int64 delta_time; /* Timedwait delta time */24 __int64 end_value; /* Timedwait end value */2/* Check to see if the drive is ready right now */3 TRACE( 0x03200000 ); /* WAIT_BUSY starting */i? drvsts = inp( ucb, RD_ALT_STS ); /* Get the status byte */*@ if ( IS_CLEAR( drvsts, STS_M_BSY ) ) /* If not busy, then */ {sR TRACE( 0x03210000 ); /* WAIT_BUSY normal exit -- was already not-busy */; return( SS$_NORMAL ); /* Drive is ready - exit */ }S'/* Drive is busy - wait a bit for it */e/* Set up the timedwait */4 delta_time = DRQ_TIME; /* Set DRQ wait time */< status = exe$timedwait_setup( &delta_time, &end_value );3 if ( $FAIL( status) ) /* Check for success */  {gJ TRACE( 0x03220000 ); /* WAIT_BUSY exe$timedwait_setup failure */@ return( status ); /* Return with the failure status */ }o!/* Spin until ready or timeout */EL while ( ( status=exe$timedwait_complete( &end_value ) ) == SS$_CONTINUE) {o? drvsts = inp( ucb, RD_ALT_STS ); /* Read status byte */ K if ( IS_CLEAR( drvsts, STS_M_BSY ) ) /* Check for it to be clear */m {gK TRACE( 0x03230000 ); /* WAIT_BUSY "became not-busy" success */e= return( SS$_NORMAL ); /* BUSY is clear - exit */  }h }aE/* Ok - still not ready. Let's reset the controller and try again */tE BPTRACE( 0x03240000 ); /* BREAK: wait_busy wants to do reset */o/ reset_ctrl( ucb ); /* Attempt a reset */e? drvsts = inp( ucb, RD_ALT_STS ); /* Get the status byte */ @ if ( IS_CLEAR( drvsts, STS_M_BSY ) ) /* If not busy, then */ { L TRACE( 0x03250000 ); /* WAIT_BUSY "became not-busy" after reset */: return( SS$_NORMAL ); /* return with success */ }e else {LO TRACE( 0x03260000 ); /* WAIT_BUSY "still busy" after reset failure */$A return( SS$_CTRLERR ); /* Exit with controller error */> }b }& ;8/* WAIT_DRQ - Wait for DRQ to be set and BSY to be clear * * Input:; * ucb pointer to UCB * * Output: * status value * */*int wait_drq( DQ_UCB *ucb )  { 2 int status; /* Routine status value */3 int drvsts; /* Drive status register */c5 __int64 delta_time; /* Timedwait delta time */ 4 __int64 end_value; /* Timedwait end value */2/* Check to see if the drive is ready right now */2 TRACE( 0x03300000 ); /* WAIT_DRQ starting */? drvsts = inp( ucb, RD_ALT_STS ); /* Get the status byte */IA if ( IS_CLEAR( drvsts, STS_M_BSY ) ) /* Is the drive busy? */ " { /* If not, then... */A if ( IS_SET( drvsts, STS_M_DRQ ) ) /* get the DRQ bit */C {AO TRACE( 0x03310000 ); /* WAIT_DRQ normal exit -- was already DRQ */cN return( SS$_NORMAL ); /* Drive is ready and DRQ is set -- exit */ } } 6/* Drive is busy or DRQ not set - wait a bit for it *//* Set up the timedwait */4 delta_time = DRQ_TIME; /* Set DRQ wait time */< status = exe$timedwait_setup( &delta_time, &end_value );4 if ( $FAIL( status ) ) /* Check for success */ {_I TRACE( 0x03320000 ); /* WAIT_DRQ exe$timedwait_setup failure */t@ return( status ); /* Return with the failure status */ }e!/* Spin until ready or timeout */ K while ( (status=exe$timedwait_complete( &end_value ) ) == SS$_CONTINUE)) {;F drvsts = inp( ucb, RD_ALT_STS ); /* No, so read status byte */, if ( IS_CLEAR( drvsts, STS_M_BSY ) ) {&. if ( IS_SET( drvsts, STS_M_DRQ ) ) { K TRACE( 0x03330000 ); /* WAIT_DRQ "became ready" success */eF return( SS$_NORMAL ); /* Looks ok - set new status */ }w }* }pM TRACE( 0x03340000 ); /* WAIT_DRQ ending with TIMEOUT waiting for DRQ */r6 return( status ); /* Return with status code */ }O K/* DQ_WFIKPCH - Wait for Interrupt and Keep Channel (opionally w/Histogram)( *= * This routine is a jacket around the normal ioc$kp_wfikpch.iG * This routine may also keep the time completion histogram up to date.i * * Input: % * kpb pointer to the KPBeC * orig_ipl IPL to restore to when releasing the device locko@ * erl_param An arbitrary parameter passed from our callerC * which will be passed onto dumpreg if we timeout.D * We only use the param to identify (for posterity)! * who called us.e * * Output: * status value * */g7int dq_wfikpch( KPB *kpb, int orig_ipl, int erl_param )t {(- DQ_UCB *ucb; /* Pointer to UCB */ 8 int status; /* Returned routine status */= extern int EXE$GL_ABSTIM; /* Current time (seconds) */ A int time; /* Starting time, later, Elapsed time */ > ucb = (DQ_UCB *) kpb->kpb$ps_ucb; /* Get UCB pointer */5 TRACE( 0x03400000 ); /* DQ_WFIKPTCH starting */*T inp( ucb, RD_ALT_STS ); /* Get the status byte (just for tracing's benefit) */Z if (ucb->ucb$l_unsolicited_int!=0) /* Is an unsolicited interrupt already pending? */. { /* If so, bypass WFIKPCH, etc. */_ TRACE( 0x03410000 ); /* DQ_WFIKPCH taking the pending-unsolicited-interrupt bypass */CX ucb->ucb$l_unsolicited_int = 0; /* Forget any pending unsolicited interrupts */T inp( ucb, RD_STS ); /* Read status byte to quash any pending interrupts */C device_unlock( baseucb.ucb$l_dlck, orig_ipl, SMP_RESTORE );s# /* Release the device lock */i; return( SS$_NORMAL ); /* And return succeeding */n }t/ /* Else we'll wait for an interrupt... */ 6 time = EXE$GL_ABSTIM; /* Get the current time */; status = ioc$kp_wfikpch( kpb, TIMEOUT_TIME, orig_ipl );> time = EXE$GL_ABSTIM - time; /* Calculate elapsed time */; TRACE( 0x03420000 + time ); /* IOC$KP_WFIKPTCH end */w> ucb->ucb$l_int_hist[time]++; /* Bump a histogram entry */9 if (status == SS$_TIMEOUT) /* Interrupt timeout? */a! { /* If so, then... */*I ucb->ucb$l_int_tmo++; /* Bump the explicit timeout indicator */e3 erl_std$devictmo( erl_param, (UCB *) ucb );h% /* Handle the device timeout */4 exe$kp_fork( ucb->ucb$ps_kpb, (FKB *) ucb ); /* Fork, and... */= BPTRACE( 0x03430000 ); /* BREAK: WFIKPCH timeout *//; return( status ); /* Return with status intact */ };; if ( $FAIL( status ) ) /* Any other WFIKPCH error? */ ! { /* If so, then... */sC device_unlock( baseucb.ucb$l_dlck, orig_ipl, SMP_RESTORE );qG BPTRACE( 0x03440000 ); /* BREAK: Non-timeout WFIKPCH error */ 9 return( status ); /* and return with status */ } * /* All is well after the 'rupt... */T ucb->ucb$l_unsolicited_int = 0; /* Forget any pending unsolicited interrupts */9 status = exe$kp_fork( ucb->ucb$ps_kpb, (FKB *) ucb );h! /* Drop back to fork IPL */t5 return( status ); /* and return with status */t }  e$/* RESET_CTRL - Reset the controller *B * This routine issues a RESET to the controller. It does this forD * ATA devices by using the RESET bit and for ATAPI devices by using$ * the ATAPI Software REset command. *B * It then waits for the BUSY bit to clear. If the BUSY bit isn'tH * cleared within a certain time, we decide that the controller is dead. * * Input:* * ucb pointer to UCB * * Output: * status value/ * SS$_NORMAL --- successful resetc9 * SS$_CTRLERR -- controller failed to RESET  * */cint reset_ctrl( DQ_UCB *ucb )  {e) int orig_ipl; /* Original IPL */n/ int status; /* Routine status value */06 int drv_head; /* Drive drive/head register */0 int drvsts; /* Drive status register */& int loop; /* Loop counter */4 TRACE( 0x00070000 ); /* RESET_CTRL starting */; ucb->ucb$l_resets++; /* Count a reset issued by us */ < device_lock( baseucb.ucb$l_dlck, RAISE_IPL, &orig_ipl );$ /* Take out the device lock */: if (ucb->ucb$l_atapi_flag==0) /* ATAPI flag clear? */4 { /* If so, ATA RESET -- Use reset bit */: out( ucb, WT_DEV_CTL, (CTL_M_SRST | CTL_M_nIEN) );( /* Set the Reset + no_ints bits */K out( ucb, WT_DEV_CTL, 0x00 ); /* Cear the Reset + no_ints bits */e }d else3 { /* ATAPI RESET -- Use reset command */ B drv_head = ucb->ucb$l_drv_head; /* Get base drive info */@ if (ucb->ucb$l_drive_lba_capable) /* If LBA mode, ... */@ drv_head |= DRVHD_M_LBA; /* ... set the LBA bit */H out( ucb, WT_DRV_HD, drv_head ); /* Select drive, ignore head */5 out( ucb, WT_CMD, CMD_ATA_ATAPI_SOFT_RESET ); 3 } /* Issue the ATAPI reset command */_? device_unlock( baseucb.ucb$l_dlck, orig_ipl, SMP_RESTORE );n' /* And release the device lock */ P for (loop=0; loop>1; loop++) /* Now wait for a few seconds... */ {4H status = sleep( ucb, 2 ); /* Sleep a bit (up to two seconds) */1 /* (First sleep allows drive to go busy) */u: if ( $FAIL( status ) ) /* Check the KP status */; return( status ); /* Failed - exit w/error */ B drvsts = inp( ucb, RD_ALT_STS ); /* Get the status byte */D if ( IS_CLEAR( drvsts, STS_M_BSY ) ) /* If not busy, then */> return( SS$_NORMAL ); /* Drive is ready - exit */ }sF return( SS$_CTRLERR ); /* It never became ready again -- punt */ } r/* SLEEP - Kill some timeu * * * Usage:i * SLEEP (seconds)u * * Input:d * ucb pointer to UCBp! * seconds seconds to sleepe * * Output: * none * * Return value:+ * status of the exe$kp_fork_wait callh * */d%int sleep( DQ_UCB *ucb, int seconds )/ { int loop;n int status;h8 TRACE( 0x00080000 + seconds ); /* SLEEP starting */& for (loop=0; loopucb$ps_kpb, (FKB *) ucb );: if ( $FAIL( status ) ) /* Check the KP status */; return( status ); /* Failed - exit w/error */n }p return( SS$_NORMAL );r } "/* ISR - Interrupt Service Routine * * * * Usage:* * ISR (idb)t * * Input:_ * idb pointer to IDB * * Output: * none * * Return value: * none * */ void isr( IDB *idb ) {@, DQ_UCB *ucb; /* Pointer to the UCB *// int dummy; /* Place to dump STATUS */tK/* Get pointer to the UCB; If null, then there is none and we just exit */lK ucb = (DQ_UCB *) idb->idb$ps_owner; /* Get UCB address from the IDB */  if (ucb == NULL): return; /* Unowned and unexpected - dismiss */> ucb->ucb$l_total_ints++; /* Increment interrupt count */J/* There's an owner. If the interrupt is expected, then restart the KP */? device_lock( baseucb.ucb$l_dlck, NORAISE_IPL, NOSAVE_IPL );p# /* Acquire the device lock */tL if (baseucb.ucb$v_int) /* Is this an expected interrupt? */< { /* If so, then... */N TRACE( 0x0E000000 ); /* Expected interrupt */P baseucb.ucb$v_int = 0; /* Clear "interrupt expected" */P baseucb.ucb$v_tim = 0; /* Clear TIMEOUT expected bit */N fork( (void (*)()) exe$kp_restart, ucb->ucb$ps_kpb, SS$_NORMAL, ucb );? } /* Fork off a routine to restart the stalled */5 /* mainline kernel process */_< else /* Else unexpected interrupt... */< { /* */N TRACE( 0x0E100000 ); /* Unexpected interrupt! */X ucb->ucb$l_unsolicited_int = 1; /* An unsolicited interrupt is now pending */Q ucb->ucb$l_unsol_ints++; /* Increment unsolicited interrupt count */p }fQ dummy = inp( ucb, RD_STS ); /* Read STATUS to acknowledge the interrupt */ B device_unlock( baseucb.ucb$l_dlck, NOLOWER_IPL, SMP_RESTORE );# /* Release the device lock */a+ TRACE( 0x0E200000 ); /* ISR ending */s8 return; /* Return to the interrupt dispatcher */ }v t8/* INP - This routine is used to read a byte from a CSR. * * Input: " * ucb pointer to the UCB * reg register index * * Output: * none * * Return value:& * byte of data read from the CSR * */t BYTE inp( DQ_UCB *ucb, int reg ) {i- CRAM *cram_ptr; /* Pointer to CRAM */y) int status; /* Routine status */e# BYTE data; /* Data byte */ ? cram_ptr = ucb->ucb$ps_crams[reg]; /* Point to the CRAM */ < status = ioc$cram_io( cram_ptr ); /* Read the byte */C data = (cram_ptr->cram$q_rdata >> cram_init[reg].shift) & 0xFF;t; TRACE( 0x05000000 + (reg<<16) + data ); /* Byte read */I- return( data ); /* Return the value */  }M e9/* INPW - This routine is used to read a word from a CSR.A * * Input: " * ucb pointer to the UCB * reg register index * * Output: * none * * Return value:& * word of data read from the CSR * */n!WORD inpw( DQ_UCB *ucb, int reg )o {1 CRAM *cram_ptr; /* Pointer to the CRAM *// int status; /* Routine status value */ $ WORD data; /* Data value */; cram_ptr = ucb->ucb$ps_crams[reg]; /* Point to CRAM */e< status = ioc$cram_io( cram_ptr ); /* Read the word */C data = cram_ptr->cram$q_rdata >> cram_init[reg].shift & 0xFFFF;A#ifdef TRACE_DATA_TOO ; TRACE( 0x05400000 + (reg<<16) + data ); /* Word read */F#endif/ return( data ); /* Send back the data */  }E t7/* OUT - This routine is used to write a byte to a CSR.  * * Input:/" * ucb pointer to the UCB * reg register index2 * data data byte to be written to the CSR * * Output: * none * */r+void out( DQ_UCB *ucb, int reg, BYTE data )0 { 1 CRAM *cram_ptr; /* Pointer to the CRAM */l* int status; /* Returned status */> cram_ptr = ucb->ucb$ps_crams[reg]; /* Get correct CRAM */: cram_ptr->cram$q_wdata = data << cram_init[reg].shift; /* Position data */ @ status = ioc$cram_io( cram_ptr ); /* Perform the write */> TRACE( 0x06000000 + (reg<<16) + data ); /* Byte written */ }C t@/* OUTW - This routine is used to write a word of data to a CSR. * * Input:)" * ucb pointer to the UCB * reg register index2 * data data word to be written to the CSR * * Output: * none * */A,void outw( DQ_UCB *ucb, int reg, WORD data ) { - CRAM *cram_ptr; /* Pointer to CRAM */b) int status; /* Routine status */ ? cram_ptr = ucb->ucb$ps_crams[reg]; /* Point to the CRAM *//: cram_ptr->cram$q_wdata = data << cram_init[reg].shift; /* Position the data */ = status = ioc$cram_io( cram_ptr ); /* Write the word */1#ifdef TRACE_DATA_TOOb> TRACE( 0x06400000 + (reg<<16) + data ); /* Word written */#endif }  eO/* OUTW_T - This routine is used to write a word of ATAPI packet data to a CSR.0 * * Input:T" * ucb pointer to the UCB * reg register index2 * data data word to be written to the CSR * * Output: * none * * * Note: *B * This routine only exists because it provides an unconditionalC * trace of the outw() calls that write the ATAPI packet, even if*" * TRACE_DATA_TOO isn't defined. * */.void outw_t( DQ_UCB *ucb, int reg, WORD data ) {n- CRAM *cram_ptr; /* Pointer to CRAM */ ) int status; /* Routine status */i? cram_ptr = ucb->ucb$ps_crams[reg]; /* Point to the CRAM */C: cram_ptr->cram$q_wdata = data << cram_init[reg].shift; /* Position the data */t= status = ioc$cram_io( cram_ptr ); /* Write the word */sK TRACE( 0x06800000 + (reg<<16) + data ); /* ATAPI packet word written *// }#*[FREEWAREV5.DQDRIVER]DQDRIVER.COM;1+,. / 4U D-ѡ0123KPWO5<6H&⺘7B89GHJ.$ DEBUG_CC_OPT = "/DEFINE=(BASEALIGN_SUPPORT)"$ IF P1 .NES. ""$ THEN>$ IF P1 .NES. "DEBUG" THEN EXIT %X14 ! SS$_BADPARAMH$ DEBUG_CC_OPT = "/DEBUG/NOOPTIMIZE/DEFINE=(DEBUG,BASEALIGN_SUPPORT)"$ ENDIF$$! Compile the driver$H$ CC/STANDARD=RELAXED_ANSI89/INSTRUCTION=NOFLOATING_POINT/EXTERN=STRICT- 'DEBUG_CC_OPT' -* /LIS=DQDRIVER/MACHINE_CODE/OBJ=DQDRIVER -+ DQDRIVER+SYS$LIBRARY:SYS$LIB_C.TLB/LIBRARY$$! Link the driver$?$ LINK/ALPHA/USERLIB=PROC/NATIVE_ONLY/BPAGE=14/SECTION/REPLACE-3 /NODEMAND_ZERO/NOTRACEBACK/SYSEXE/NOSYSSHR-9 /SHARE=SYS$DQDRIVER- ! Driver image> /DSF=SYS$DQDRIVER- ! Debug symbol file9 /SYMBOL=SYS$DQDRIVER- ! Symbol table8 /MAP=SYS$DQDRIVER/FULL/CROSS - ! Map listing" SYS$INPUT:/OPTIONSSYMBOL_TABLE=GLOBALSCLUSTER=VMSDRIVER,,,- DQDRIVER.OBJ,-R SYS$LIBRARY:VMS$VOLATILE_PRIVATE_INTERFACES/INCLUDE=(BUGCHECK_CODES)/LIB,-@ SYS$LIBRARY:STARLET/INCLUDE:(SYS$DRIVER_INIT,SYS$DOINIT)5COLLECT=NONPAGED_EXECUTE_PSECTS/ATTRIBUTES=RESIDENT,- $CODE$PSECT_ATTR=$LINK$,WRTPSECT_ATTR=$INITIAL$,WRT$PSECT_ATTR=$LITERAL$,NOPIC,NOSHR,WRT%PSECT_ATTR=$READONLY$,NOPIC,NOSHR,WRT PSECT_ATTR=$$$105_PROLOGUE,NOPICPSECT_ATTR=$$$110_DATA,NOPICPSECT_ATTR=$$$115_LINKAGE,WRTQCOLLECT=NONPAGED_READWRITE_PSECTS/ATTRIBUTES=RESIDENT,$PLIT$,$INITIAL$,$GLOBAL$,-N $OWN$,$$$105_PROLOGUE,$$$110_DATA,$$$115_LINKAGE,$BSS$,$DATA$,$LINK$,- $LITERAL$,$READONLY$PSECT_ATTR=EXEC$INIT_CODE,NOSHR>COLLECT=INITIALIZATION_PSECTS/ATTRIBUTES=INITIALIZATION_CODE,-U EXEC$INIT_000, EXEC$INIT_001,EXEC$INIT_002,EXEC$INIT_CODE,EXEC$INIT_LINKAGE,-C EXEC$INIT_SSTBL_000,EXEC$INIT_SSTBL_001,EXEC$INIT_SSTBL_002$QUIT:$ EXIT $STATUS#*[FREEWAREV5.DQDRIVER]ENABLE-IDE.C;1+,.$/ 4P$-ѡ0123KPWO5=6d7B89GHJ.#pragma module ENABLE_IDE "X-1"J/************************************************************************/ /* */J/* Copyright Digital Equipment Corporation, 1994 All Rights Reserved. */H/* Unpublished rights reserved under the copyright laws of the United *//* States. */ /* */I/* The software contained on this media is proprietary to and embodies */C/* the confidential technology of Digital Equipment Corporation. */G/* Possession, use, duplication or dissemination of the software and */G/* media is authorized only pursuant to a valid written license from */(/* Digital Equipment Corporation. */ /* */G/* RESTRICTED RIGHTS LEGEND Use, duplication, or disclosure by the */A/* U.S. Government is subject to restrictions as set forth in */I/* Subparagraph (c)(1)(ii) of DFARS 252.227-7013, or in FAR 52.227-19, *//* as applicable. */ /* */J/************************************************************************/J/************************************************************************/ /* *//* Abstract: */@/* This program enables the IDE interface on systems with the *//* 8731x SuperI/O chip. */ /* *//* Author: *///* Benjamin J. Thomas III / November 1994 */ /* *//* Revision History: */ /* */1/* X-1 Benjamin J. Thomas III November, 1994 *//* Initial version. */ /* */J/************************************************************************/J/************************************************************************/ /* */:/* $ CC ENABLE-IDE + SYS$LIBRARY:SYS$LIB_C.TLB/LIBRARY */"/* $ LINK/SYSEXE ENABLE-IDE */ /* */J/************************************************************************/&#include adpdef /* Define the ADP */2#include dcdef /* Define device and ADP codes */+#include descrip /* Define descriptors */)#include iocdef /* Add I/O constants */4#include iogendef /* IOGEN symbols (load driver) */##include ipldef /* Define IPLs */1#include lib$routines /* Define LIB$ routines */&#include pcbdef /* Define the PCB */2#include ssdef /* System service statys codes */-#include starlet /* Define SYS$ routines */##include stdio /* Standard I/O */3#include stdlib /* Define standard definitions */7#include string /* Define string handling routines */+#include stsdef /* Define status codes */7#include varargs /* Handle variable argument lists */5#include ioc_routines /* Define the IOC$ routines */4#include sch_routines /* Deinf the SCH$ routines */1#include vms_macros /* Include the VMS macros */&int sys$load_driver(__unknown_params);+static ADP *isa_adp; /* ISA ADP address */0static __int64 isa_base; /* ISA base address */(static int isa_tr; /* ISA TR number */2static unsigned __int64 iohandle; /* I/O handle */6static int isa_offset; /* Offset to 87312 register */3extern ADP *IOC$GL_ADPLIST; /* Start of ADP list */;extern PCB *CTL$GL_PCB; /* Point to current process PCB */$/* Define what an IOSB looks like */2typedef struct { /* Standard I/O status block */+ short int status; /* Status word */7 short int byte_cnt; /* Transferred byte count */" int unused; /* Unused */ } IOSB_T;,/* Define what a VMS item list looks like */+typedef struct { /* VMS item list item */5 short int buf_len; /* Length of data buffer */2 short int item_code; /* Item code number */5 void *buff_addr; /* Pointer to data buffer */2 int *ret_addr; /* Returned data length */ } ITEM_T;5#define $SUCCESS(code) ( (code & STS$M_SUCCESS) == 1)2#define $FAIL(code) ( (code & STS$M_SUCCESS) == 0) $void chk_sts(status,string,va_alist)H/* This routine is used to check a VMS status code. If the code does */D/* not represent success, then the error string is output and the */G/* program is exited with that status code. If the code does show a */E/* success status, then this routine simply returns to the caller. */ /* *//* Input: */ /* string - error string *//* status - status code */%int status; /* Status to check */+char string[]; /* String to be output */*va_dcl /* Declare varying arg list */{+va_list ap; /* Argument list pointer *//* Check for error */$ if ($VMS_STATUS_SUCCESS(status))/ return; /* Success - just return */$/* Error - print message and exit */- va_start(ap); /* Start the arg list */9 fprintf(stderr,"? "); /* Output start of message */9 vfprintf(stderr,string,ap); /* Output the string */2 fprintf(stderr,"\n"); /* Terminate string */" va_end(ap); /* End list */* exit(status); /* Exit with error */} int check_system_type() {I/* This routine is used to check that this system supports an internal */-/* IDE controller that can be enabled. */ /* *//* Input: *//* none */ /* *//* Output: */-/* status SS$_NORMAL system is supported */1/* SS$_UNSUPPORTED no support on this system */+#include syidef /* Define GETSYI items */&IOSB_T iosb; /* IOSB for $GETSYI */*static int cpu_type; /* CPU type code */-static int sys_type; /* System type code */#int efn; /* Event flag number */(int status; /* Routine status code */1struct { /* $GETSYI for system and CPU type */, ITEM_T items[2]; /* Define the items */+ int end; /* Define an end item *//} syi_item = {4, SYI$_CPUTYPE, &cpu_type, 0,/ 4, SYI$_SYSTYPE, &sys_type, 0, 0 }; 4 status = lib$get_ef(&efn); /* Acquire an EFN */@ chk_sts(status,"Unable to acquire EFN, status = %X",status);L status = sys$getsyiw(efn,0,0,&syi_item,&iosb,0,0); /* Get information */N chk_sts(status,"GETSYI failed to obtain system type, status = %X",status);P chk_sts(iosb.status,"GETSYI failed to obtain system type, IOSB status = %X", iosb.status);/* Check types */? if ( (cpu_type == 2) && (sys_type == 13) ) { /* Avanti ? */6 isa_offset = 0x26e; /* The ISA CSR address */+ return SS$_NORMAL; /* Supported */ }C return SS$_UNSUPPORTED; /* None of the above - exit with ?? */} int set_ide() {D/* This routine is used to enable the IDE interface in the 8731x. */ /* *//* Input: *//* none */ /* *//* Output: */-/* status SS$_NORMAL system is supported */1/* SS$_UNSUPPORTED no support on this system */'int status; /* Status from MAP_IO */$int data; /* Data read/written */ int sts; /* Routine status */+int idx_shift; /* Index register shift */+int data_shift; /* Data register shift */int i; /* Loop counter */&int saved_ipl; /* Saved IPL value */.unsigned __int64 ofs; /* ISA Offset value *//* Precompute some values */E idx_shift = ( isa_offset & 3) << 3; /* Compute shift count */E data_shift = ((isa_offset+1) & 3) << 3; /* Compute shift count *//* Map the page */5 dsbint(IPL$_SYNCH, saved_ipl); /* Bump the IPL */0 ofs = isa_offset; /* Get the ISA offset */A sts = ioc$map_io(isa_adp, 0, &ofs, 2, IOC$K_BUS_IO_BYTE_GRAN,# &iohandle);//* Set the index register and read the value */K/* We'll do this a few times, just to be sure that we actually wrote it. */F/* The chip requires two back-to-back writes, and while IPL 31 will */1/* improve the odds, it isn't bullet-proof. */ for (i = 0; i < 10; i++) {- data = 0; /* Index register is 0 */N sts = ioc$write_io(isa_adp, &iohandle, 0, 1, &data); /* Write index */J sts = ioc$read_io(isa_adp, &iohandle, 1, 1, &data); /* Get data */< data = data >> data_shift; /* Shift the data down */= if ( (data & 0x40) != 0) { /* Was it already set ? */3 if (i == 0) /* If first time, then */A status = SS$_WASSET; /* indicate set already */ elseI status = SS$_NORMAL; /* Indicate success in setting it */*- break; /* and exit the loop */4 } 9/* Set IDE enable bit and write it twice (87312 rules) */gI data = (data|0x40)<adp$l_link) {$, if (adp->adp$l_adptype == AT$_ISA) {@ isa_base = adp->adp$q_csr; /* Get the CSR address */3 isa_adp = adp; /* Save ADP address */> isa_tr = adp->adp$l_tr; /* Save the TR number */F status = SS$_NORMAL; /* Set up status to show success */) break; /* and exit loop */l }  }c; sch_std$iounlock(CTL$GL_PCB); /* Unlock I/O database */i+ setipl(0); /* Lower IPL back to 0 */f6 return status; /* Return with the status code */}oint load_driver() {l(int status; /* Service status code *//char device_name[5]; /* Device name string */D(char driver_name[13]; /* Driver name */#int efn; /* Event flag number */t&IOSB_T iosb; /* I/O status block */ __int64 csr; /* Device CSR */int vector; /* SCB vector */sint node; /* Node number */ int ucb; /* Address of UCB */ int crb; /* Address of CRB */ int idb; /* Address of IDB */ int dpt; /* Address of DPT */ int ddb; /* Address of DDB */:struct dsc$descriptor_d /* Descriptor for device name */4 dev_dsc = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0};:struct dsc$descriptor_d /* Descriptor for driver name */6 name_dsc = { 0, DSC$K_DTYPE_T, DSC$K_CLASS_S, 0};(struct { /* $LOAD_DRIVER item list */. ITEM_T items[8]; /* Define the items */+ int end; /* Define an end item */*} list;D4/* Set up parameters - hardcoded for this example */$ csr = 0x1f0; /* and he CSR */+ vector = 4*0x0e; /* and the vector */r* node = 3; /* and the node number *//* Build the item list */*+ list.items[0].buf_len = sizeof(isa_tr);V- list.items[0].item_code = IOGEN$_ADAPTER; & list.items[0].buff_addr = &isa_tr; list.items[0].ret_addr = 0;u( list.items[1].buf_len = sizeof(csr);) list.items[1].~ DQDRIVER.BCKѡ#[FREEWAREV5.DQDRIVER]ENABLE-IDE.C;1P$0item_code = IOGEN$_CSR;/# list.items[1].buff_addr = &csr;  list.items[1].ret_addr = 0;l+ list.items[2].buf_len = sizeof(vector);c, list.items[2].item_code = IOGEN$_VECTOR;& list.items[2].buff_addr = &vector; list.items[2].ret_addr = 0;g) list.items[3].buf_len = sizeof(node);k* list.items[3].item_code = IOGEN$_NODE;$ list.items[3].buff_addr = &node; list.items[3].ret_addr = 0;p( list.items[4].buf_len = sizeof(ucb);) list.items[4].item_code = IOGEN$_UCB;,# list.items[4].buff_addr = &ucb;t list.items[4].ret_addr = 0;*( list.items[5].buf_len = sizeof(ddb);) list.items[5].item_code = IOGEN$_DDB;# list.items[5].buff_addr = &ddb;* list.items[5].ret_addr = 0;o( list.items[6].buf_len = sizeof(idb);) list.items[6].item_code = IOGEN$_IDB;g# list.items[6].buff_addr = &idb;r list.items[6].ret_addr = 0;_( list.items[7].buf_len = sizeof(crb);) list.items[7].item_code = IOGEN$_CRB;m# list.items[7].buff_addr = &crb;) list.items[7].ret_addr = 0; 1 list.end = 0; /* Terminate the item list */*/* Acquire an EFN */; status = lib$get_ef(&efn); /* Ask nicely for an EFN */)@ chk_sts(status,"Unable to acquire EFN, status = %X",status);7/* Build the device and driver names and descriptors */_ strcpy(device_name,"DQA0"); / dev_dsc.dsc$w_length = strlen(device_name);e' dev_dsc.dsc$a_pointer= device_name;n' strcpy(driver_name,"SYS$DQDRIVER"); 0 name_dsc.dsc$w_length = strlen(driver_name);( name_dsc.dsc$a_pointer= driver_name;/* Try to load the driver */, status = sys$load_driver(IOGEN$_CONNECT,& &dev_dsc,' &name_dsc, # &list,e# &iosb,y! efn,E" 0,0);H chk_sts(status,"Unable to load driver, service status = %X",status);J chk_sts(status,"Unable to load driver, IOSB status = %X",iosb.status);8 dpt = iosb.unused; /* Copy over the DPT address */H printf("Device %s loaded with driver %s\n",device_name,driver_name);A printf(" UCB - %X, CRB - %X, DDB - %X, IDB - %X, DPT - %X\n",F$ ucb, crb, ddb, idb, dpt);0 return SS$_NORMAL; /* Exit with success */}t w/* Main program */"int main(int argc, char *argv[]) {(int status; /* Service status code */= status = check_system_type(); /* Check the system type */aO chk_sts(status,"This system does not support an internal IDE interface\n");CH/* Note: This routine should probably lock down it's working set into */E/* memory to be sure that we don't catch any pagefaults at a high */ /* IPL. */E status = sys$cmkrnl(get_isa_base,0);/* Get the ISA bus address */bN chk_sts(status,"Error in obtaining ISA base address, status = %X",status);N printf("ISA base address is 0x%X, ISA TR number is %d\n",isa_base,isa_tr);5 status = sys$cmkrnl(set_ide,0); /* Set the bit */H chk_sts(status,"Error in setting IDE enable, status = %X\n",status);2 status = load_driver(); /* Load the driver */D chk_sts(status,"Error in loading driver, status = %X\n",status);/ exit(SS$_NORMAL); /* Exit with success */t}s%*[FREEWAREV5.DQDRIVER]ENABLE-IDE.COM;1+,. / 43 `-ѡ0123KPWO5<6\˝mw7B89GHJ $ Set Verify3$ CC ENABLE-IDE + SYS$LIBRARY:SYS$LIB_C.TLB/LIBRARY$ LINK/SYSEXE ENABLE-IDE **[FREEWAREV5.DQDRIVER]FREEWARE_README.TXT;1+,. / 4L t-ѡ0123KPWO56+c7fLB89GHJ G DQDRIVER, SOFTWARE, IDE/ATAPI Disk Driver Example for OpenVMS AlphaF DQDRIVER is an IDE disk driver for OpenVMS Alpha. The driver was K originally written and tested on an add-in IDE controller board as wellI as the on-board IDE controller found on the AlphaStation 400 4/233. E The driver has been substantially revised to include ATAPI CD-ROMF support. In addition, the driver has been incorporated in OpenVMSA Alpha and is supported on recent 21264 (EV6) based platforms.I A recent enhancement to DQDRIVER is the inclusion of the IO$_DIAGNOSEF interface which can be used for CD-ROM audio, or potentially otherI unique ATAPI devices, such as CD-RW. This enhancement is expected toE be included in future OpenVMS Alpha releases and in future update$ patch kit for V7.1-2 and V7.2-1.H The driver is provided as an example, and as with any hardware basedF tools, you should use at your own risk. The latest version of theJ source code is provide here to update previous release of the code andL to provide immediate access to CD-ROM audio support. The driver can be H used in an unsupported fashion to access CD-ROM audio until officialG patch kits or new release are available with the new version of the driver. J A new version of DECW$CDPLAYER been included in the kit. This versionD contains enhancements for ATAPI CD-ROM audio support via the newI IO$_DIAGNOSE interface in DQDRIVER. The new version of the CD playerE can be used to play CD-ROM audio from either ATAPI or SCSI CD-ROMJ drives. The new version of DECW$CDPLAYER is expected to be include in* future versions of the DECwindows kit./ This package has the following source code:& DQDRIVER.C - IDE driver source file./ DQDRIVER.COM - Build file for DQDRIVER0 SYS$DQDRIVER.EXE - Executable for V7.1-29 ENABLE-IDE.C - tool to enable the built in IDE for the0 AlphaStation 400 4/233 and load the IDE 5 driver. DQDRIVER is now autoconfigured on " supported Alpha systems./ ENABLE-IDE.COM - Build file for ENABLE-IDE.C9 IDE-INFO - tool to dump the identity page information  from an IDE drive.' IDE-INFO - Build file for IDE-INFO.C; DECW$CDPLAYER.C - New version of DECwindows CD-ROM audio 0 player with support for ATAPI CD-ROMs.@ DECW$CDPALYER.UIL - Interface definition file for CD player* DECW$CDPLAYER.EXE - Excutable for V7.1-2 E DECW$CDPALYER.UIL - Compiled Interface definition file. Must be@ in same directory as EXE file.!*[FREEWAREV5.DQDRIVER]IDE-INFO.C;1+,./ 4\K-ѡ0123KPWO5=6σ 7m)B89GHJ*#pragma module IDE_INFO "X-1" /************************************************************************/ /* */ /* Copyright Digital Equipment Corporation, 1994 All Rights Reserved. */ /* Unpublished rights reserved under the copyright laws of the United */ /* States. */ /* */ /* The software contained on this media is proprietary to and embodies */ /* the confidential technology of Digital Equipment Corporation. */ /* Possession, use, duplication or dissemination of the software and */ /* media is authorized only pursuant to a valid written license from */ /* Digital Equipment Corporation. */ /* */ /* RESTRICTED RIGHTS LEGEND Use, duplication, or disclosure by the */ /* U.S. Government is subject to restrictions as set forth in */ /* Subparagraph (c)(1)(ii) of DFARS 252.227-7013, or in FAR 52.227-19, */ /* as applicable. */ /* */ /************************************************************************/ /************************************************************************/ /* */ /* Abstract: */ /* This program dumps the identify page information from an IDE */ /* drive. This information is obtained through the READRCT driver */ /* function code. */ /* */ /* Author: */ /* Benjamin J. Thomas III / November 1994 */ /* */ /* Revision History: */ /* */ /* X-1 Benjamin J. Thomas III November, 1994 */ /* Initial version. */ /* */ /************************************************************************/ /************************************************************************/ /* */ /* $ CC IDE-INFO */ /* $ LINK IDE-INFO */ /* */ /************************************************************************/ #include descrip /* Define string descriptors */ #include iodef /* I/O function code */ #include lib$routines /* Define LIB$ routines */ #include ssdef /* Define SS$ status codes */ #include starlet /* Get system service definitions */ #include stdio /* Define standard I/O values */ #include stdlib /* Get standard definitions */ #include string /* String definitions */ #include stsdef /* Status values */ /* Define the Identify Drive information buffer */ typedef unsigned short int WORD; /* Define a WORD (16 bits) */ typedef unsigned char BYTE; /* Define a BYTE (8 bits) */ #define MODEL_LENGTH 40 #pragma member_alignment save #pragma nomember_alignment typedef struct { WORD config; /* 0 - Configuration information */ WORD cyls; /* 1 - Number of cylinders */ WORD rsvd2; /* 2 - Reserved word */ WORD heads; /* 3 - Number of heads */ WORD ubytes_track; /* 4 - Unformatted bytes/track */ WORD ubytes_sector; /* 5 - Unformatted bytes/sector */ WORD sectors; /* 6 - Number of sectors */ WORD unique7[3]; /* 7-9 - Vendor unique */ char serial_number[20]; /* 10-19 - ASCII serial number */ WORD buffer_type; /* 20 - Buffer type */ WORD buffer_size_blocks; /* 21 - Buffer size (in blocks) */ WORD ecc_bytes; /* 22 - Number of ECC bytes/sector */ char firmware_revision[8]; /* 23-26- ASCII firmware revision */ char model_number[MODEL_LENGTH];/* 27-46 - ASCII drive model */ BYTE rw_multiple; /* 47 - Number of sectors/interrupt */ BYTE unique47; /* 47 - Vendor unique */ WORD dblword_io; /* 48- Doubleword I/O flag */ WORD capabilities; /* 49 - Capabilities */ WORD rsvd50; /* 50 - Reserved */ WORD pio_cycle; /* 51 - Programmed I/O cycle times */ WORD dma_cycle; /* 52 - DMA I/O cycle times */ WORD valid54_58; /* 53 - Valid bit for next 4 fields */ WORD curr_cyls; /* 54 - 1) Current cylinder count */ WORD curr_heads; /* 55 - 2) Current head count */ WORD curr_sectors; /* 56 - 3) Current sector count */ unsigned int max_sectors; /* 57-58 - 4) Maximum sector number */ WORD multiple_sectors; /* 59 - Current sectors/interrupt setting */ unsigned int lba_maxblock; /* 60-61 - LBA mode maximum block number */ WORD single_word_dma; /* 62 - Single word DMA info */ WORD multi_word_dma; /* 63 - Multi word DMA info */ WORD rsvd64[64]; /* 64-127 - Reserved */ WORD unique128[32]; /* 128-159 - Vendor unique */ WORD rsvd160[96]; /* 160-255 - Reserved */ } ID_PAGE; ID_PAGE buffer; /* Data buffer */ #pragma member_alignment restore /* Define what an IOSB looks like */ typedef struct { /* Standard I/O status block */ short int status; /* Status word */ short int byte_cnt; /* Transferred byte count */ int unused; /* Unused */ } IOSB_T; void copy(char *out, char *in,int nchar) { /* This routine is used to copy a string with byte swapping */ /* */ /* Input: */ /* out pointer to destination */ /* in pointer to source */ /* nchar number of characters to copy */ /* */ /* Output: */ /* none */ int i; /* Loop index */ /* Move all the characters two at a time and swap them */ for (i=0; i\n"); exit(SS$_INSFARG); /* Exit */ } /* Assign a channel to the device */ dev_dsc.dsc$w_length = strlen(dev_name); /* Set string length */ dev_dsc.dsc$a_pointer= dev_name; /* Point to the string */ status = sys$assign(&dev_dsc,&chan,0,0,0); /* Assign the channel */ if (!$VMS_STATUS_SUCCESS(status)) { /* Check for success */ printf("? Failed to assign channel, status is %X\n",status); exit(status); /* Exit with status */ } /* Get an EFN to use */ status = lib$get_ef(&efn); /* Acquire an EFN */ if (!$VMS_STATUS_SUCCESS(status)) { /* Check for success */ printf("? Failed to acquire EFN, status = %X\n",status); exit(status); /* Exit with status */ } /* Ask for the ID page from the drive */ status = sys$qiow(efn,chan,IO$_READRCT,&iosb,0,0,&buffer,512,0,0,0,0); if (!$VMS_STATUS_SUCCESS(status)) { printf("? QIO service call failed, status is %X\n",status); exit(status); /* Exit with failure status */ } if (!$VMS_STATUS_SUCCESS(iosb.status)) { printf("? QIO operation failed, IOSB status is %X\n",iosb.status); exit(iosb.status); /* Exit with status */ } /* Print the information obtained from the page */ printf("ID Page information for %s:\n\n",dev_name); copy(string,buffer.model_number,MODEL_LENGTH); printf("Drive Model: \"%s\"\n",string); copy(string,buffer.serial_number,20); printf("\tS/N: \"%s\" ",string); copy(string,buffer.firmware_revision,8); printf("F/W rev: \"%s\"\n",string); printf("Config: %x\n",buffer.config); printf("Geometry: Cylinders: %d ",buffer.cyls); printf("Heads: %d ",buffer.heads); printf("Sectors: %d\n",buffer.sectors); printf("Unformatted: bytes/track: %d ",buffer.ubytes_track); printf("bytes/sector: %d\n",buffer.ubytes_sector); printf("Buffer type: %d ",buffer.buffer_type); printf("Buffer size (in blocks): %d\n",buffer.buffer_size_blocks); printf("Number of ECC bytes/sector: %d\n",buffer.ecc_bytes); printf("Number of sectors/interrupt: %d\n",buffer.rw_multiple); printf("Vendor unique: %d\n",buffer.unique47); printf("Doubleword I/O flag: %d\n",buffer.dblword_io); printf("Capabilities: %x (LBA - %d, DMA - %d)\n",buffer.capabilities, ((buffer.capabilities&0x100)>>8),((buffer.capabilities&0x200)>>9)); printf("Reserved: %d\n",buffer.rsvd50); printf("Cycle times: PIO %d ",buffer.pio_cycle); printf("DMA %d\n",buffer.dma_cycle); printf("Valid bit for next 4 fields: %d\n",buffer.valid54_58); printf("Current: Cylinders %d ",buffer.curr_cyls); printf("Heads %d ",buffer.curr_heads); printf("Sectors %d ",buffer.curr_sectors); printf("Maximum sector number %d\n",buffer.max_sectors); printf("Current sectors/interrupt setting: %d valid: %d\n",buffer.multiple_sectors&0xFF, ((buffer.multiple_sectors&0x100)>>8)); printf("LBA mode maximum block number: %d\n",buffer.lba_maxblock); printf("Single word DMA info: %d\n",buffer.single_word_dma); printf("Multi word DMA info: %d\n",buffer.multi_word_dma); /* Ask for the debug information from the driver */ printf("\n\nDebug Information:\n\n"); status = sys$qiow(efn,chan,IO$_RDSTATS,&iosb,0,0,&debug_info, sizeof(debug_info),0,0,0,0); if (!$VMS_STATUS_SUCCESS(status)) { printf("? QIO service call failed, status is %X\n",status); exit(status); /* Exit with failure status */ } if (!$VMS_STATUS_SUCCESS(iosb.status)) { if (iosb.status == SS$_NODATA) { printf("\t%% DEBUG driver is not loaded\n"); exit(SS$_NORMAL); /* Just exit at this point */ } else { printf("? QIO operation failed, IOSB status is %X\n",iosb.status); exit(iosb.status); /* Exit with status */ } } /* Print out the debug information */ printf("Total interrupts: %d",debug_info[0]); printf("\tTotal unexpected interrupts: %d\n",debug_info[1]); printf("Number of CRAMs - %d\n",debug_info[2]); printf("Transfer buffer address - %x\n",debug_info[3]); printf("Base SPTE address - %x",debug_info[4]); printf("\tBase S0 address - %x\n",debug_info[5]); tmo_time = debug_info[6]-2; printf("Timeout time: %d seconds\n",tmo_time); printf("\n\tSeconds\tCount\n"); for (i=0; i<=tmo_time; i++) { printf("\t%d\t%d\n",i,debug_info[7+i]); } printf("\t>%d\t%d\n",tmo_time,debug_info[7+tmo_time+1]); } #*[FREEWAREV5.DQDRIVER]IDE-INFO.COM;1+,. / 4 0-ѡ0123KPWO5;6|Qmw7R/B89GHJ $ Set Verify $ CC IDE-INFO$ LINK IDE-INFO'*[FREEWAREV5.DQDRIVER]SYS$DQDRIVER.EXE;1+,.Q/ 4QK-ѡ0123 KPWOL56p7HB89GHJ.Lh (o 0KG:o SYS$DQDRIVERX-21A11-39 X6N6-SSB-0000>@@@@@$l$9$E$JG<iSYS$BASE_IMAGE_001@xzSYS$PUBLIC_VECTORS_001;"; [!1R!B 1H3rJ2C2HDSFkG#TG~^^~ G({wCGGb#GC@ӌGb#CGTGw@ӅGC8b#?"TG@~Gb#CGTG@wGCb#?"TG@pGb#C"TG@iGCb#TG`"@bGb#CGTG@[GCb#4G@UbTGG"b#C@MGC"TGb#2@FG"Cb#TG'@?GC"TGb# @8Gb#C ?"TG@1G,?&(b#C1"TG@)GC"TGb#@"G"Cb#TG@GCXb#GTG@Gb#C4G@ bG4GBTGpb#CGh@GBCpb#GTGG_@GCB4Gpb#TGGV@GBCpb#TGTGGM@GCBGpb#TGGD@GBCpb#tGTGG;@GCBGpb#TGG2@Gpb#C@BTG4GG)@GCpb#tG@B4GG @ӼG@BCpb#G4GG@ӳGCpb#G0B4GG@ӪGpb#C0BG4GG@ӡGCpb#G0B4GG@ӘGpb#CB4G4GG@ӏGC B4Gpb#GG@ӆGpb#CBGGG@}GCpb#4G8BtGG@tGpb#C8B4GtGG@kGCpb#G8BtGG@bGBCpb#GtGG@YGCBGpb#tGG@PGBCpb#GtGG@GGCBTGpb#4GG@>GBCpb#G4GG@5GCBtGpb#tGG@,GBCpb#TGtGG@#GCpb#GHBtGG~@Gpb#CHBtGtGGu@GC(BGpb#GGl@G`b#C4G@G2DGG]]} 0#k:&E'?'5J@#B9#&6"FBGHR$?&L4B1"Xt;~KpCX4kGp";1'F1#C 1֢вk#"~G^^~ (G#[GGG{CdB dNZkBbCGTGNZk BG#CbGTGMZkG]  ]} (0#k#? ~C^TGG [0({MZkG]   #kG#G~^^ ~(08޴@G8[#CGG@{GGGGGGG`OZkC(BGGD0bGGMZk7â"B6%ve 6!BF%CV1WZk'BB8GCG GV$WZk6CXBG`bG%G 6G%Gd@CMZkHBCPbGTG~MZk  G]] }(08ݤ@P#kG4Gk#G~C^GGqtG [0dF({` G^MZkG]4G #k #ns$~kt&`^Se h^Pa"p~t!'xac?'Rs#޴Tr9#>^~޵G;pۦGxCgs_'8>G(@FlZ#H޶ts?&PIn1"X^so_&[st?$ UnR"<Hi! L;o!&T[Tm"\;d!_'0۠EnZ#8{$ &pC"ՒJ6J4BB6J;YBP۲[8sgJ`PB`;"["/1"X2B`BCCGGhbGGGLZkXPb#s@ ?"1#JX}B C X 4uB@ p X@BCGT&(!HbCG"tGGLZk=X=@BCT&G0!!HbCGtGGLZk0X= G¥,A!X!!GG, LCGBGGb@LZk,j0  $7H!F0 AJ 0`A !?2dAX@SABssBX A!/BG,ʲCGGG.Gb {LZk,j0@ 0`A E0J !XG(b#@XG(b#@ Hb$X TGPBAXbH(!CGYLZkCh!PBC 0AXbITGOLZkB!bCp@TGGLZk%H!cCTGBtGbTG,AA C)EH!wH)R%#H!kD hh!dh +LZkȡG BG(bG8Gd(LZkB!CbGGGLZk@"C0BGG(]8bE LZkB bCCGtGLZk{ȡBCbTGAEȱtGdtGKZknhG BG(bG8Gdh (%KZk`BCGbGGKZkHC)G0BG(k!]IE8bKZkK蠐BGbtG D`DT&4G&GKZk<B4G0TGb $KZk4BCbCG"tGKZk+]BG=#GLXZk]G=?"6JTG)v I`B-Ihb -E&AʴX$EtD*PFP ȡ0AȱhqDhX=KZkG`]h]p}xݤ=]}ݥ#kG# ~C^G^G ~G(08GG0G [T$({GXKZk)R?& B1"(bFCT$GGGGGHKZk4GDGG]] }(08@#k#tG~B^wJ^0B~0 @ uJ(0`B0޴0C8GG.{JGVJ0@F>A/0@0@ZAK[GA?.aJ{ JGPJF>G.WJF>@/Z@K[G@?b#@.qHb#PJF>GG@.qHb#PJ0@F>GG@.qHb#PJ0@F>GTG@.qHb#PJ0@F>GG@.qHb#PJ0@F>GG@.qHb#PJ0@F>GG{@.qHb#PJ0@F>GTGq@.qHb#PJ0@F>GGg@.qHb#PJ0@F>GG]@.qHb#PJ0@F>GTGS@.qHb#PJ0@F>GGI@.qHb#PJ0@F>GG?@.qHb#PJ0@F>GG5@.qHb#PJ0@F>GTG+@f,bHGCfH0@bDf<$.0 @yJ0@BQ$J$>.bJ0BdJTJ>/0@CaJGXKG?,@HD<,FHD<#.Q#J!F#>]]} (0ݤ8@#k#~^^~ (0޴8GG0TG8dѢFh䢸G㲰#`D:G CcC<cPH0D#v!J T"GG.D!U@ 1@ 2@7@B@XD8F Xc0JRJ֐`Jذ`J B CfJ tB`CGGX#8Ұ HRH0H2N HD3B!N @ 1@ H8D8tG8B@bTG@â㢸DòHLIZkGCGxGGGD@h \@Ck4GaGv@G]Gb#9@GXG4GUGGb#h@GOGb#@Ӵ vH$H@GG\@GCGb#@Ӵ<# @ vH?D H&#HCC4D31G4Gb#E@G,Ghb#@G'@? G8b#D!  @G<#%vH!B!"H@Gxb#c@G@? GD!  8b#@G%vH<#!@!"H@GJ HBPbC4GhIZkCGG(B0btGaIZkG]]} (0ݤ8@#kG#$~ 0^8^@~HPXG ;G({G0>sk? ~G G(>GG{#l@GTGb#@GGb#@GGb#@G4GGb#Z@ BCbCGGCTGGIZk  DGb#@@>G7GCGb#G/ @%b#GGE @/b#D #G8 F TGD@0@3@G@D 8D D XD C @0@3@Gm@ G ä GÈc eG4GG0]8]@}HPX`#k#~^^~ (0GGGG@ GPb#k @.BGB4Gr.rJ`4bHZk4PcG4GFhb#@4GDGtGhb#@TG4Gb#@ GGb#E@GGb#@@Ӭ 4GG]]} (0@#kG#_"~0^8^@~HPGGG0P{#Z @#?"GsA.a.!""QJS qJ !#SF Bg..s@J"JW JFB^ A/ a/YK[ yK[GGCW d#U@2v@JSR@X1J3FX.8J.d"JW JFyJFGX:J $,# .;HV J d#6Dq H?D5LQGX2L!!H#..JW JF"(&$/.8KV J6G"G $.9J D/;JZ [K:F'F xD.{$,DJA $HAFC 3C!D#wN6eJ4C3D6WJ8J6B66@CBzӀJ:GXGb#@G4 }X "0JBJXӰJАJSNPN0Jb"b.4Gc.JW J"FF"b$#bD/cd/YK[ yK?#[GYG9# ="7d"*]"/3,s#.Q#3HVJu1HF>4G/s"?F1/K`KY:K G1?1"2BQ$,Q ( $H5vJ!"H5D(=G2B4BP K9v?KDG@0 K29?KF?G2F?G22p J1 JdF`2R",0BHD2,A2HR"@2<@BCCGZk0BC8bCGGGGGZk4GG0]8]@}HP`#kG0q.Q.","tJ.UJ."HJ."J. H3bJ#J4J2AJD#q.J5JJD6J1"D#FqJ H8KFF@"H"q"`L k1" &4Gв"R_BPCFX@JC8KWB2FJ"3wdJXFSBXP4Gk#~^^~ (0޴8GGX0GP JĐ J 5@ HBhBGB4G2,2H 4bGZk4DG4Gb# @DG4Gb# @GtG4Gb# @TGTGb#n @GG(b# @GG(b# @4 DG4DG]]} (0ݤ8@#kG#~^^~ (0޴8GGGGGbG B4G.J4(bFZk4Gb#l @DGGb# @DG4Gb# @GtGGb# @TGtG0b# @GGHb#S @GGHb#N @4 DG4DG]]} (0ݤ8@#kG#~^^~ (0GGG{#1 @=8BG@B4Gr.rJ`4HbtFZk4PcG4G(b#F^ @GtG4G(b#Y @DHGG(b#S @DGG(b#N @GtGG(b#I @TGGPb# @ GGhb# @GGhb# @G4GG]]} (0@#k4Gk#~^^ ~(0GGG@{# @;BGpB4Gr.rJ`4xb FZk4PcG4GXb#F @GtGGXb# @TGGb# @GGb# @GGb# @Ӭ #G_"b#G @BC#CGbGEZk4GG]] }(0@#k#@P"C~^ ^(~08@G[GG0CC"_OZkCCGGGGGb#C@8DG C BC(bGC=GEZk @<G]G ](}08@P#kG#~^^ ~(08޴@HP>XGGG{#K @e?"GGB$J6H\CG @X`1@8âpFGCGGb#Z@Pv(_~ DQDRIVER.BCKѡ'[FREEWAREV5.DQDRIVER]SYS$DQDRIVER.EXE;1Qh Gpb#F#G8 F TG @"E)"b#D0 XD01G1 ] @0@D`Q @ 5@+= Db#(7I<c)7J8G@â C iBC5BF17JGC @ӨBC@GNZk<â)B<#8@C8G GGGG4GG]] }(08ݤ@HP=X`#k#~^^GGp0@@`B@;@`\@`CkGb#&@Gb#@Gb#t@4Gb#p@Gb#@4Gb#@ G`"4GpB4GxbtGGDZkGG]] #kG#/B~C ^(^0~8@H޴PX`>hGGG8GGPàoFCGCDTFCGGGG{#,@"G0B4GI.IJ@48bDZk4]G4GDDb# @DG4Gb# @ݠGtGDb# @ݠGGDb# @ݠGGHb#D @GtGGb# @TGG@b# @-GGXb#9 @!%G_"b#!7!H@g@0@ @I.G0B4GIJ@48brDZk4TGG@b# @GGXb# @G4GG ](]0}8@HݤPX`=hp#kG#/B~C ^(^0~8@H޴PX`GG8GPРGP/DGDCCSFGGG{#@Gb#@GPB4G.J4Xb$DZk4GGG8b# @#GG?D8b# @#GG0 H8b#?D @#G4G8b#P H?D @#GtGp H8b#?D @G4GG8b# @G4GDD8b# @DG4G8b# @ݠGtGD8b# @ݠGGD8b# @ݠGGH8b#D @GtGG8b# @GG4G8b# @TGG`b#E @,GGxb#x @GGxb#s @G!GH.GPB4GHJ@4XbCZk4GGG8b# @,B4GH4bCZkG4GG ](]0}8@HݤPX`p#k#G~^^~ (0޴8GG@"GCCGàG.u0H/BGTJOFF>cKF.0 uAJbKTJ#F>zJF0/aK "rJY8KvtK:G0?F0,A0H2D0<F .UJF >G@GGGGGb#@GGpB4GE.EJ@4xbBCZk4GGGXb#- @,B4GH4b1CZkGDCGb#Gg@Gg@G2DG]]} (0ݤ8@#k#~^^~ (0޴8@GG@"GCGGà/BGCOFG@G.`CG 1@sJRGJWHSFG>D.0@H"wUKVJHF>D'/#{8HHY8KrJ;G'? g"D.vHGGPJb#F>R@D .GUJGF >H@GGB4GD.DJ@4bBZk4GGGxb# @,4G BH4(bBZkGDGb#G@G@G2DG]]} (0ݤ8@P#k#~^^ ~(08޴@HP>XGGH0G@{#G<08GDL#G)@H?"B$J6HAC?G/1@8pFGCGGb#:_ = Gb# 0@DGD@ 5@4= D<cb#8(7I)7J@G C iB4BSF17JGC@ B(bC@GtGhGGG8GGPàoFCGCDTFCGGGG{#@<#(7H8#b#@CC (@12BF17!JGCG@B? CEGGJZk) "!BG% 8q @ C@ G.("h""PJ>EH.B B BRQJH>E.TJ>EH/ZUKH?!.0B BPJ!>?X"GB4GI.IJ@4bAZk4]G4Gb#DD@DG4Gb#@ݠGtGb#D@ݠGGb#D@ݠGGb#HD@GtGGb#@G`b#-@G3G_"Pb#07J@G@TGGb#T@3GGb#@%0 @% @( I.GB4GIJ@4b@Zk4àG`b#@GGGb#i@GG,4GBH4b@ZkGG4GG ](]0}8@HݤPX`=hp#kG#C~ ^(^0~8@H޴PX`GG8GGPР@PG0oFDTFCCGGGG{#@<#(7H8#b#@CC (@12BF17!JGCG@B? CEGGIZk) "!BG% 8q @ C@ G.'"g""PJ>DG.B B BRQJG>D.TJ>DG/ZUKG? '/0B BY'K '?Gb#@xGB4G/K4b+@Zk4GGGb#@#GGb#?D@#GGb#0 H?D @#G4GP Hb#?D@#GtGb#p H?D@G4GGb#@G4Gb#DD@DG4Gb#@ݠGtGb#D@ݠGGb#D@ݠGGb#HD@GtGTGb#@GG4Gb#@TG4G b#L@+GG8b#@GG8b#z@G G.GB4GGJ@4bOZk4GGGb#@,B4GH4bOZkG4GG ](]0}8@HݤPX`p#k#~^^~ (0޴8@HGG/BGGGOFG<c'7HG8C@C gB8{#0PB3F17JGCG@ B? CDGGXZk( "!B% 8q @ CGA .'"g""PJ>DG.B B BRQJG>D.TJ>DG/ZUKG? .0B BPJ >?@c"CCTG,xSKH3#@HgyKD<H3"3,`JH "A9HzJ'D3<F.gHGhb#WJF>F/XKG?F 3,A4H'D 3<@GGGGG@GG8B4G,H4@b OZk4àGGG b#@,B4GH4bNZkGDGb#G/@G/@G2DG]]} (0ݤ8@HP#k\ k#@0 C~C^GG.uaJG{#TJF>CGG?@G] #k@P"#C~^C^" ~(GGtG,s2JGG@H{#D<_F.TGvHCUJGF>GG@ .# c"JF C.SJB . c JbG]] }(0#kG#~^^~ (0޴8@H>P^X~`hGG8G0GGP&GDs"G3FDTBFGG{#@ӒPC?%)!HEF8 GB4GL.LJ@4bPNZk4C(Gpb#@GGGb#6@#GGb#?D0@#GGb#0 H?D)@#G4GP Hb#?D"@GtGb#pHD@G4GGb#@GGGb#@_EG4Gb# @ӊIEGGb#@_EGGb#@GtGGb#@GG4Gb#@G@b#A@ L-4G`BLI@4hbMZk`GG" !E5 AGGb# D@ EG D@D8TGb#@L- G`B4GLI@4hbMZk`G GTGtGb#E@GGb#x@ G GGb#r@ !E"pD  D!A@@\y7H_F/0 J"tJXK?FG?"_Fr.w5JSpJtFr>_F2,A5H7D2<k00GQ"Dq"/4vH24vJR"SB0`C4@{aC;B&0vJBkG#C~^^~ (0޴8G7@JGGGGGGb#@0@.qH @PJ F>_G]]} (0ݤ8@#kG. HP JF>ð#C~^^ ~(08޴@G7@JG#GGGF.FJ'GGhb#@G=!,@ =0 B=G}3lB`0BG7B0@ @ G]] }(08ݤ@P#k.P JFG#~^^~ (0޴8@H>P^X~`GHGG@ G Bàp_! D {@SBTJHH@h`HPBGXb4G}KZkG0BGF4G8b 0 AF@sKZk 'AA@?0BG8b4GhKZk cG]]d@} (0ݤ8@H=P]X}`p#kG0_"4G2F0k#G~^^~ (0GGGX{#@D DDGb#>@3P#G4G?Db#'@GGXb#@DG G$DFGb#(@D(D 4GGGXb#@GGGXb#@D xDD@4G8DxDD? 4GaD G]]} (0@#kG#G~^ ^(~0GGGx{#@D4G&B?&Cb@B1"=CGGTGJZkBCbG4GJZk8DGGxb#@D4G G0b#@GGxb#y@D4GjDG] ](}0@#kG#G~^ ^(~0GGG{#b@D DD0B?&8b@B1"=CCGTGJZk BC(bG4GJZk8D GGb#F@D?DD4GG] ](}0@#kG#~^^~ (0޴8@HGG0GGGGGG{#"@8&GTGb# 8@XbB4G,H4bbJZkG4G<G G@BGHbGtGSJZk"PBG'@G@TG`'0 @`'FCXbG0@BFAJZk0BTC8bTG;JZkGG`X⤀B4G,H4b.JZkGGG0BC8TGT8b!JZkG]]} (0ݤ8@HP#kG#~^^~ (0GG0G0 B0G B4G%,%H 4(bIZk4cG4G`tGb#@G4GG P#4Gb#(DDG?D@GtGGb#@,4GBH4bIZkGGGTGb#@ GGHb#y@D0@$U@G/G]]} (0@#kG4G#~^^~ (0GG GGG BCTTG(b0@IZk @?4GG]]} (0@#k#4G~^^~ (G G([0{,P0G0 BP0D.DJ@4|IZk#4GXB4G8BP D q D#CT#c C`b#lIZkGX#4G80 @X#GTGb#@,HB4GPbH4YIZkG]]} (0#kG#P0B~4G^^~ (GGX[G{G?IZk@@`@`CG]]H} (D0#kG#P0B~4G^^~ (GGX[G{GIZk@@`@`CG]]H} ( vH0#kG#A?B~a!@^P0BG4GX[C{!!AJP0HZkG] #kG#A?B~a!@^P0BG4GX[C{!!AJP0HZkG] #kG#A?B~a!@^P0BG84GX([C0{!!AJP0HZkG] #k404GkGP04GkGL04GkG,04GkGX04GkGT04GkG$04GkG04GkGh04GkGPt J4G3YJtF0kGPt J4G3GJtF0kGl04GkG(04GkG`04GkG\04GkGd04GkGD04GkG<04GkGp04GkG004GkG04GkG 04GkG4Gk 04GkG@04GkG04GkGPt J4G3_JtF0kG004GkGP4GQF0kGPt J4G3_JtF0kGPt J4G3GJtF0kGPtS J4G3v^JtF0kG0 BG.G HJ0@B 6/4`B7gJ{JY6K B;G6?0 BHF4GFFHpk(04GkG,04GkGPtS J4G3v^JtF0kGPt J4G3YJtF0kG404GkGD04GkG4GkP`F0`Fq`F`'FU?B5JCB4Jբ!1H4GFU0p4GaF0kGk4Gk#G~^^~ GF [XaDG({GZk0BG8bGGZk@G]]`G} 0#kG`#~8^@^H~PX`޴hpx>^~GG0~,GGGHG`GE.%"0@CPQJGJGFDTG0@@ZkwG D(GG GFTD0@%".PJJF @xF`xFDG4DD.JF0@ G/KXGa@0@C(]CGGa@C.JF0@D0/0KX%G a@0Bc }CGN@C ݢBFG G @D0@%#/XKK GD= C0@ C C _"@óC` 8 C\CCka@DGG7HJ7G1CFGDGFtFGG`CG8 0 @4G?GVGضGTGGb#K@ Ca@D#Cb#0v JCSFGTGG=@ Ca@D#&EG3v JDSF "C 0BxC4GGfCwGGGb#!@ CDa@D#&E GD2v JF` "C0BwCGGfCvrH. 0 CTGPJF>GGGb#@ E C`CG@\}"a@C 0`BCFG;GPa@DC G$@ @A@AG "B PA@.J? 0@A / K; 0@Aj/jK7` 0@A.J3 0@APA@A.@*,*H+ 0@A@A_'.J$ 0@A*/*K 0@Aj.jJD a@h.zHPJF>a@#Ѱ = .C" J 0 @C?GFsGa(,KAPD&E,BGC:.Z#GTGP:JDGJ(1A@Zk 0`A0@ DCGGGTG,1A@Zk0@jAGD 0`A , CP HGJTGjA@Zk0@ GDCGGGTG,1A@Zk0@-IwG8]G@]H}PX`ݤhpx=]}#k. HP JF>0,P"GGA2H'H&@ @.`"RSJBGJ=GB 0@" ,2FBPBA4HB'HB&@ ='@ `0@`".!&@PBBSJgJfBk0,G0H . "PJJ9 B ]B , HA H2DBr@J2D <0@D.J?kG# U`B~G^QB^~ (GGGG(B51`B0bGGBJZk bGGGGG_CHBGPbGt JZk8 H/GBXK.JyH G?F,DH<]]} (0#kG#~^G{#_G] #k(#> @h~C^GG [{# _G]P#kSODRIVERx0 0 \ 0^H0 0\ 0 [ 0 H[0 0 Z0 0 Y0 kk`0 (Y@< ]0 W@< @k0 UP ]]d0 U@ ]]0 T@ 0 R@< 0 R0 XQp Opi0 XPP| 0 O@| 0 0O0 N0 M `mn@mn0 M0 Ep 0 E0 0 D 0 D0 AP Pn0 =p 0 9p 0 9  0 7P 0 3P 0 1@| 0 .p 0 X,p 0 `+  0 5` 0 P)` 0 `(P< 0i0 '@ 0 '0 %@< 0 $@| 0 P#@| 0 ("00 ` ?@  0 @< 00 `< Nonexistent IDE/ATAPI di0 @| i]i0 @| 0 @@< j Generic d`^^pj0` p^[ehI diskIDE/ATAPko0 @ p0 8 0 P| d`0gg0 H e0 p0 0 @؅0 h]@^Hx]ؓ_8_hx^0_0h_X8`@`0 @0 _H0`]`^(^P_xPDQDRIVERp^`^P^8 ]H]0 \0 `0 @`0 8`0 (`0 `0 _0 _0 _0 _0 P_0 0_0 _0 ^0 ^0 ^0 ^0 ^0 ^0 x^0 p^0 `^0 P^0 @^0 0^0 ^0 ^0 ^0 ]0 ]0 ]0 ]0 ]0 ]0 p]0 `]0 P]0 @]0 0]0 ]0 ]0 ]0 `0 --00 j 0 h0 i0 `mn0 h08 0a 0 @jP Ȕmtx|<XhtXht| |||(@ ||||`||||||||||p| |||||||||||||||||||||xtXXXXXXXXXXDXXXXDXXXXxXXHXXXx0123456789abcdef #~(^0^8~@HP޴X` GhGC <D Q@G<DG 4G<7KG!.JWJ6KG{KF>@C=CC?#`#@ZkG%=4 G% DEGP@F<ĢTF<IJ@ G(0^8~@HPޤX`hp#k#~(^0^8~@ GHG \ b`(MZG@F  /8MCC@m?#OZkb@G G(0^8~@HP#kQG#G~^ ^(~0GG(b4G]`[@ZkG "0`G]}@R`A3G]4G ](}0@#k0( p 0(@P X)0@ TX@ " E""""" @"P k2TUUUUUUUUUEI(%@ % @@  ]~Ї؇ 0@e( `phpc@gHfP@hXc`ccpfPcȐgH`fPciP tT(6,6 6 6 6ā 6́ 6Ё 6ԁ 6܁i6666606066 6 f i$i(i,i0i4i8i<i@iDiHiLiPiTiXi\i`idihilipitixi|iiiiiiiiiiiiiiiiiiĂiȂîiЂiԂi؂i܂iiiiiiiiiiii iiiiix 0@ 0`І 0p P`pЍPЎ @ @SYS$BASE_IMAGE@SYS$PUBLIC_VECTORSHH SYS$DQDRIVERX-2117-APR-2000 12:3217-APR-2000 12:32Linker A11-39  .$$ABS$$. V ~ DQDRIVER.BCKѡ%[FREEWAREV5.DQDRIVER]RFV4AB,I#PP tXE;1"~63h+::r(7%c|d_u7.nfKQ]&cq4 7{I7L q]T\olU?nq\$\}W40;z΢exXCpz1VJ0-{%j+9)}RPMAIT< lGJ'7-z"^Fx,&MW'`z{;)Ktz _?Y|q?ZPI" &{g$],Kj&{i?_ exj\11|PvS8MshR5TD^~KSfx,w(%] :<%LV^_R.NV* e8tA'e_e`*4;c'g 8h&qƟ-?:DVк<$uI/_HlQM~d% ige=$XD07+vO>_`"c>YZTrfb$>HɗNzg&LiaPEL!o^u/cX|yhU&o=:*3d[/dIX92TH)rzDO<.u#{w_HY 62}Muu~@.4"Lݢ,x:ZOzi\ \Vmqa)(=57ݻ1kvUP\ J99AS[ZRX-W p59NcS A'&o^s@@G9vW*~vr7T;QVm]FZ}R{dX=$|]+HfE]`yVf{Z zD( oQ^Ng!+ ,i1aB?NoK[j*!?r,.WYybKb .c r}u_`.rLbGJ6ODE&}sky{V{eSoj S/tJ bL\$:TSPi);A/r4wzR,iyAWu|KbY rT(v 9OZ~UX+f :;'b!>~{) fPgs)i+A`gnoL{3%{'sAu,M: @Ix3{ET+"a+R!',H  kVc?O&GsnfX(t/;VAkHa~uT_EteaEIe*KoHVX%@J6 u' AKȜlrhzF&CEE6 z2hdM~2)8 @!H3D]CfPAQ yv vvg:tu >'\(Apӳ b]mx{8fOdRlTyPj{y$S j*n9uM^]]m ,|L=Z,v]Vx1i&i*RTpu?a}2Q77edUH ֌-sX_l2X/rKT5.{PwtQoA.mq*v=Y50Z"Hp.c ~s f# 3n` \x]R,AG^rn4:xjyxj7J^M`RO5Dq }gKBK7Q 3Tv=jdSVLZrya|fcT0U^zmRg4P+}#)_YVKk_^Wp"Yeyo;zf{TCLbuℍ "&7Xe+E6d.]Of7&MvIWke9;N&y ұ&ZL.ZJ];KpdP)CMMX?{L/]/ j8#BR`Ws:,-&z1nV}XBEOq\*mD LJ ]PBD_cP;;E."^vseE`QBa4]2>e{2Bp[lh^v~b l'?Ti>?I4p6u=CUa T@1senw'G]SE(0/-O* ,L/P vym;vBwQ?_ 'r K_.)/}tYdY0JifN _n)1^@"?=S>*?RO566m9& fY+3)dVl-gP6 lV0}CEw7v|Equ)y[KY MRntXjBy\5]q!vijE$|XV;aL,m.OGn$JOG[ `WW]Za_9$1܅*_ h`Ï&RHuYUY)kbNq[fiD GDm7`N7A1 7U^ϰ5Efg%=Og Q%-Jos^W gCT'f|: |"Na=?2"0GT #@&:1fV.@w[g+)( *q, UP SSz\aZu (kRIioARrHWJ;L)f*-8Xb -R9qNjIrAH!f$K_CN>B{8'7 MP\ ] K9}bJ'E.2aJiF]Rl"6i4ujT .+5.G:{kkXh ]*IxK gKU6WդNH̱mB@ l\8D P.5uya}b$HpkW `%,1*yHƋmC$׊EBWa:9SZ?~"/.4CBI/_j*ڶ ,G (~IJw+[|L!.0gJC/T1bO ~s(uHDh¡ Ӣ [K5IJ:NjU&(=;ڐRImYɄ*ΰ"(  FO!30j=(HGGXnE~A4Q}*$TzDucpG$ v`x+ ^WqFx{KU[:cWOa"k$ 2uod4ND@ \Ki"EBD@XIaMĊx8ZEPn#.D?9okLTG_.֓ngU SjSwTVVʹ'_י&/ #Tn͆˹NzՃdz,N~vFa|&"kd^.];z-BEOw(fVY{U'PknX{/An.0.o-SG.K<R4M!_{!<`nxVe)Ԋ@ul{0}c`^|F[*<;Wcxp~'f=3796YELbem$x''9AM@IJ#G|?x]K%imx! ={dGUZ&5Xi{M$)T #8d@orjBpKj O U42t[:zWUE]8Ʒocp#@jժ )?U~n~Xm[մ)nfYr/8f $4u 3$O-il / ySc @؝kezI!?q9?TKZ1 R"1MR2bzYh. ,eli?PvtEx!D%iQ3]$88[}2c%8ZCbWHWlCt2kyZ;`.>nRk6P`Oe7[9:k{!7l*eh"`t28@{Oĭe?n2 29&J7Gq ,қ|#(C [Q] uZ4DBzvzP`<]nGoH8<8 E*/ B\tcHf|AM6r7|&DD$Rv9N*MQ r`r8-6:sHF $(09B T?qC?Ur, 2S $/YZӃ~Yr%zah?d :k#v8YoJ2#lYFwѦ[ GN5IsD$ ={]Zy$V05PC;}9r=6`1B[QTr>yArMtslJA_+3MDreB'? UL'AULZ;m^Z]O WY^'I#j^-_;n nV~ob{hGyO  -A$DGS:4q@8[ AYO3nWRLX{}R3~l(DfI?&: dT+5Dc3^_L[U9bC"4/h\WA)OuytoiVb^ nn9fGw(i >~S&^NR۞;CzDh*_5S?` [g@1I1_W.U"C N wf#{hgtc.uvSPxj4d{sJaFc YW dF#-XeVh #2,W4cv{u.~w+RL5jrZHuڸzAC ZmG'nn L6YL3xMU"1TVFgSCz9 !-WOIeDtW6L)!d]ݍ/Z[}BQA ww=1Z3Mtrr<"bQi#plmNjM >|Jf47K5Am&t{Q #O)3M5HY!1Y>;M4;hڵNB1W[ZK94mit$` mQdc@'1B`ƥA6t} U MUa5d&YY~2M1I IgH mT!J y({JMe@Gƞ_]4- A 2@ّA2x lODg*4b.]م:K4]\@u}dQN#uR5 ODFEsF$VW^Z$ZI0PG<7gz%i4G.qUdCM[. |e;s X%y3(|=AVu|0njpd]L}_u$1 r|W & . (P=x =|}?Uce3wbzk5)&:p#.UsS%[S hm+yGUi" h(~X)S!oOU ÞEK#6rl1!5K^S(w> \%Fvst9s5?|K'_f=oqT{4@Xe|8] d,cj'[Q[n3ap,^ }RGIVL,1{8Ek-%hE+]@N{]I'VO χ[T@LGPXO*jMH~@O_pW^u?r >o5 I@cRo/Yp]`-5BOEsEX\;3rzP,I=SP@Mf,h^3$=WY%,&rn>:*4sk"r|*B!Z58=iz#7a+ M/rc|qBbW>. E DNjT)0 :mL0(iC.;eyP@L o#+k ( IYUcrY &q?pl<CFV+|*.[Yt=2Mf9]6COVL[weuYVSM dEK=>>+{HNHt;+S|^P9h0614ޘ gEgtH$ܺ `Х`3kKXX EIHzBʏBP3K'6fnAY1Zv \J{P^ilPtl/j|ep &;!U>,=Hp-7*OUX*?ڊ:hdP=iyc:q'Q![ 41aPfaYT~7;;/ ?IXFbD ?@o"3g\EyBP/?VVA3`2%EXn< _!xq>e"(RH9$Uq~,}8YL.}#E |uE_$XCmn9n OK8 9r^frLPMJ@c{u&3_^ Q=/yuBmq.t%4C-~A:znef/-h7'^+c9.^cTf*rU%_}jr4?}t]0'#z 4a~DWkAC$ 7B8@B.&aEwB ,,dH.w71DOV@J/{JY.$qsfxgK[A)1^S;+ 962R39BGBR@vYPzN2(@6>sFW M#P$:Q' dl*Xc\,regf 4חcHu# PQ"1KeHsi^E]W}}KmrA9 TZ3 ;=koUNA`_>@Q'WD O.6}E\](K*ZreJ&M"kEgpH9pClJ:[M Br,Yseux^CL_K=xj!2&$u3!L'XQvP6l|P)1J hkGVS1mT [r:Ze"FEoR)VDEBE`>@.H mT jF/qQ,6Jn"u~'gaM]R ERCzs~Z/NifR E?=.hgOR_dPV {> $X8 d1zp_ ME^&^YJ-X\{SDT&4('rH$-2Mk$9 C_k QgTgb=xKg?4yMZ\z 8|Uf':Zڹ luM+Ho45~v86,|GrD$ϯ|Z&r;w80 M%+rfO-lLH)m m=Z9ʾYtXw v-;hKd-]X`idc%xG- `Zc~80[^xp2l~ j?j]# Y4Xh(9dDFT9zk5VLuQlN'9:dJtm J$,G򴴸Ox/'W`Ja<~]9w3O!;wR&Z;vWB&(E.1?(Hok*I/Z=D-2H[# Mwu@/=$dz eCys tUI z~}t g8^"}daFq *]Z e3J ZE#zs?&3zFG@+3Ak2O^ COe]j8<:f%֎7\ ^reΊK~Jq;KR"-nvT0$)TcR>VI9x0{cLu)7" sYKO=HMR_e7LݾN͓O84k5$B2r@zow +wAV[*)?jA˛Zq6_W tmu SW~RK&qY#Wiy 4/_Q* ӆW5Y 4C[ qL'6Hު J@].^&=-SX\>@S e>e6FN$5 !MA {?>i5w[BNˇ^~ Eeuj(4?ML*`+, xN{ <t,4US_$U#`uGef?igH/0}g0D}( VN=2C+}l14 y\8OA"F@/-d{XnF\ԀEm!~FC9CtNhL$ZvZ.}7QSzm-AY:EVK?:wf-;H6^OO6)U?"ved^:Ϻ$weNRLgVp_<O.WwΑ9XH#4i2 286`dSD<4x(1~C_?g$PSY?%p*0ZUz:U&{ 78\0< 0A  hQZ9k!SS0 9 3 _pqOv"XNZ"9j#M~hJXwAZygT#nO/,z"C0SiFm55 \AE 2RYy59'%R`@;{;{^}(%xqGcX#5R}\k1|G+qK [11M"-m oo9s?=/jO+>k}d?CJW!ȯ`%1Z=x^$~67 /peDK0?#PYzYnLEq wW\!,e 2 $:?d%f XW[ebBSS]]Sh7d&@?Q\aS9ku.3zuN lOjs!W"E &*h|eR@kh`#h+C.G4El sWpD,Q TJB6s1U&.R;L&(%RJ2&:T0]+5P5!c5zkY `h;#_x) I)qŵ$$+NV|:h1!Fv}CL$=Y35J_lP6_GHb",+b9R7&0e@T:UY"oxaX,UrudVi8Ev~%{_V<&i8KTRKe^(sG8EVA%[FGyL{ 6)b>N3H~|_Xif5~Z58_Z:-?*,IF`,CPi)hbjD0TT_1AX4 Eg*}P1;5rU"wdoi3#BR=_g)y8q}g#fQ#zUUG$e/,o鶬LJ*)L' DA>yHS+yZ͛lۊ'} )AT,CL/9,ϿӍ/C=m Vgj`%jpf#% ?+&2a|eu]| xO6/A`<@Cr-arvl9rZr_cxDq4H[/D :_d/P4.5Gx[x2}Y&d]Q6o@Qgjb *)%ų6=CT Hf%1b2o|**Px?dP7[H~txp[?WgYrNaA u^FyL4,blw4\hrzbv #:@2c?8(JUqQF {T ˅K\Ym-&lH6p@ /E-}]J|hr5FP P%}s}I Q6N",_udk/yA;>G24&O"7hOn@ZkՊi8*qù;}52}C{ [T}8;!^^:_^v9+p)@C)i=J#~]V:tFbӵ9x*YqWd[J.TX[Og:J{nP?8xA{8h1ָiJ%"w8J}  < I|3f`dc9LH#s{kOw+NBTI'.cU I~|'9릛S.I)ko]T1m e5+" Mx iSv)+NFj;?/.{9**/*o*nYa/"cEp Tr-ܬt{4m1Qp[jg  &FYs!.8SJVګ$0 H#-YtFA5?r943\O]fնGpFs?i'U+t /\ 1ch@(K=#+S"jExV]cBy7Q?6XHk!Yp_3^7x}bM nfFdF}Zg0٩ Ro}E`POpE(|T;@DZ7y"L3Aje vfOP /cL%B k39W3o |*@XI7Yl)Y9V(A jmj8AT*~d,fjr?l`KPDt$KAmi G iIQ%se ?%]9 QA?Yj-fH\Zj |T4p]Xca{OVC!j}:=.H1d_70% ]*^-Rtw!k0CCMj!pRW+hP3D" hwChb]XrS1F?uXv9(TY<Qs>)+0v)j,N/A,WeOXnR85' m>_UkA/:=\UbP³=TZVcwu) U$ 7KK_`PBlnpx uH6u ob:S=1RBkWG"oE*|X#'v .Y[Xka/\ y0fFkVm9SC2,3T)ZZ'f T]r)iA&T9!c)mI^`[TYW! -iftxYl243q{7-cq`gxǫq+ \&,M0%5 :@b/fzԋ>1;EgO-3et(QD  i:; 0S˙\J6!Km/Q W;C>Hsv}K/:ZWs[F7CX-7xm*^Ed:tr^g1&\uih{uc{($ z @t1% 3OdJ%H 4)js Y j8oyQ2tQrA~V{Q,`c| e+)g?3i:{YSEcPqx;iOsk5ps;68~ +6}ur7Jmd- BW3 idpy-gxtA,;ThQ95 tVZ}MD=ZEh*y]m.a(Nk?eQ4~o*(oz9L1W(df}N4>"*`k~eH1rBr;JZ #ȩ{L`TWhzS !DkEMOmm[b(2&nI^!Gpi]NxlIn['#xsT\2(-X 0p6LEg{k fOF9/'8z.aic eMq/%w$7|leVM+#_X""-1>|wO`):,mJX?Fu!;3xffM0 Yh9.pkY!9]SrRcu;+UPDo`_[;*[voO|o_t4e IvnR7(Ik!a~O5!E }0ac+v#u60 r \A8I6BSu}.a^[EqY}T/9|fC}ykTQN*\VrZ5uU85&sMf?EKIQL saV/HZa 1zyJOe TLoOINhgAMx+[n/hRTE`nw{~o^j ;NVa#  LA|\\ e{6&L5 ]o?HV4 ,{2flgTi8{sII)X,yªTpSN-U F\ x]YLfAs"V %%hk9@@KoTPCKt[{ @Wk4#YB{MwC{MVT{:$q=;;C8)uR>AFx~I"IP=2loc)Q0Or&8u|Nwo'ED|uC4."!Z0A2*fg'-4=,6xJO*U)g6DUl mQ*VLכ_su{)L,DUI8K0 uOvk~{/x,N\L 0h-am& y}yzZ2=j'Nl")VyASB3F=7o@uj#nT7l\7he>ž?l>K `tW G+s\^$-=PPmSUl(ԥi0'(u2%8 ajyG1l-I`O +j20F ;Im:W&ޓ6H"$lpm+DP:&>J , $mwIWa#Uߣga{QFe0R0X}\=BVqEv|YG.\#K!lj3ZĴA=<FA YD[t{ضdy(}7Zma xjSe,=l GeM^?FR}l6Iv$RBghhwE;.cmP4.c46W73j[C38'u97WQ5G[Ww#ʁ()~.p5<2 ^%vm)jRqh~#,,&LF7Ooܪ?Mh([q9. &c*vl):orA VgIpA]_I^D5N,+9? ~A1;[srwcW,(ha2-Z>=yq`Q:; fKֵn_Ly^>c]qJ"4IgAPIQHP(jQxN /,;2KUS#l3h3nBi03 5p]p^mP+.tt4cyk&p0?aWf:FKO@Z^OS iY''Il!QQy9,tSjV_&C6/8gwZw#?% !<.3{lefYa{#z.fNGsE/{<%T}, "BJy U* N\ZgKoB;mA5W:L\E]j.vI׌gkhd ` |9b?%Ke xM;7(3-"Hp;VA@G2v91o6u8%btcS~|:4MSZLJ~4=ZFT /Ut z^Z F Y'V,@1= ) GS]7C?L%n UeDi6bs+gYR7^q&U%z<]`-3s~b()3xF"^YNWudAC7u}thk/MMbw1>Sw1{CpN2iQt^KMRE}63 g5JOHRJFM4 YZNWJj_W[E2 [@[_|;R`)lMWw,V4 b?Q h b5j{cNN,&Zi wjwJ` S9]GH2Q]T8o}C-lHV@FV=Xd=oHF"Hu5$8l4E+N]o+=vBN Qc;oC>p8R!&;*v{&C.MwzS6]0(> !n){iLVhVP Hvz6:tXZQF ZXodB3G%Jr} co#mKYac1f':VK =nEkHJc^(B #POL "x_#6W7mU(*Vb\yPFp!1{9h/%#R+|s^Iz^btV+ H@ PuS?Ol1EgQdpQdmolt{f5m#%duk326c+6H_+09q<)@C5=ukq>yA`EF{-ye/9Do}g | 22+=XJ0.c[//#6W[ w_T^~ Z1Bp9D53J a-*|ht+]K)?c.<;9:.3 aM~?%r*=#p=0 RS&,;Y3v 7|G jDYNwPQme=-g { )VyYaPn+$7;+^ `I<< X=3(f>yrW't=`h6'M#b4Fu=S#|h EBM_+ .s2 O=TmET`/'xqLsz0^ 8DOyQ?4_tlT. PF6{V_8ju`c_Cz qSHf N_jn~c/HJ2h u?or_"" W>%V7elfZ% xlO-]mYBuB-E+6B te~4kY*^!  uav;OK!UP,@z,A,CkPM8 K^_aJ>G_]dFI[n^RU;?e2=1Z| fdgsL{s8&\H %fWzu1)IW6XE^)Gj7GO1I katN%Ha5R=Q'c$0DtXeO@[6X(  EZnmq/} 6ua$ ,W"5 7I.$ ih:N}PAnj#0[YF&,=2I*a-8d$iRZ >fU  <1 PHC/Xq$qcjBgV C"Y~E|.H~uNAb_,4 CYFA# L_`h0}x!n 3r`<9SAu /6&=48)=gI{pnh[2OtmZ}lo jk^Z51HXxk`~24{aDC] p|QOIK,zE +P  9@x\[lSvX/ *p:W-:itzQ]{0q~}P 5Q4'a!Ysxg^?sz:P7`O0x% U%0DK.]n`nFq4:51w`iQ\pFY:N9\J-[ze_8?]zjggT]NxdE` ZVC*c  hbX(~;pm$L0?X,tGaq_" L'e1&J&TZ;7q|Cn ,ObaPytgrZpv qđY1DσyP*;4 q0P"bz|Ygk mm!"PVZ%>mh0*(@YNI>82 CN3\=3cuU|zlRk\Lv3Ad8B*e{/.P`tDFRY].=vIUi9QQ[EEdy ,kEz2 &?ʭG3VLw:#}*2"Ie P#C46BW QcZ dH ]G  UDzaxeD'[_SXTY 6@+mn>`17D 'g~^@Pa_XO+gd$9 nk>fGS87 CCW;t!>o'y'hb*" x]O@@"qo[`+$I~t#l_PLw>*KtE_f&"|8Epd-eZ\wow"DB[@$-$ZRNkmP1?=gQ3o.lM(3*kn{SHy qecstUnFF5@& X+ Ki8"t[zC 928b82:g>O/F HUXu,P VpQKS ag{)i:e'R9o _A%b%U\[UFK$>,#/qV4od[<:61itcDF8})#`Bᮠηّˆߨjq0bq,+kL7lK?G) Z`mH*IO"@Z!d%;'0u(xq_ϋH/hUo6ifeYSXS qxe7s`7D3U~>faOF2mzos29$;/:9^!CWMu\yihŜ看ӂ׋ӞW瀛ߔύƷȱő3鴯thΪ֤ܧǺkĈĊՖΫۀ߇띎ψNː苑͢ו͕ۄҰߑڵւɀẾĻɂ† Ԫϟ΂軟ĆՔ粚VOԻ݃)/ûݑ9ڗ.ׯH𰪉 ɚ:dR{_@Fzt_$O4cAa,94O%bF1Lۥ/I- [ %j{c#X}YM AHi&$%L4-}nev?e`/AhX;P ;BY?DtP@: <7Q:/Z*PW/lK.8iN3!6rT5r nSgl'n*tdL KG eoVXh>]I:WT ;V;Gjg pwy W{ɤ 8X``Scf> 9?6%\|"HoH\iBrgL7B)oijEV+K2=6Z7imKD/Q]/~s1hY :t*Y >$TA:ܪ-%Iӥ(NttZ7vIh*BLR5A`"~f>(-2U7Xi t 4(df~@G:?c^^,>JaCxMU{w>b>T:D:gQV+%~kW2\Þ~S 1~<7W!K&if" X/#:ygVK!;{x"8g }l404 O%xBH.%.mIYZnY9R|  }]j: _%ayqmxU7DVk%u nh\9L Bd'W>ܱxn='MYLl>*ArI?]~-M=Ry >j (wu Jt1CFjh2[6)"r5sKc3nyMpTpTS*O 9[1VyJlk#JB. 6n4P`BBso2 7$6k[3*5$LF+1CEkgK-q!&8oM]|Z!\Go}S oRf'O[A9~m3*d\)| m5})=wW'Z.8!@j"8;(PU5e`tFO2EWIN *DTdd0=nQ`7 $ _Tmf!"&daAj 3]&em8x-Dn m٤T/k()-|`8yUmu'3RB 10R(pLNf6ruR8i(JQ8߇fqMZK*X~m=wQ*=yj{n/LS e .1~L}/y_/dw2`*`޵bQ!2zM U>Rwg?jG-}a? aqta13irp'%\bwR@=XmN}3%'^(-2]}83M3#~.H)i{('_&DJr+ G}M,aF>3TO#BWM,ba{,;Wu9rS`5p< %0$&rgh;M:]E8? = 4FndcDSjuy,$k==ɽGj!w/eifLfmD,E eO Z/WIn  V'H3 0zpS9E2UhmWw&%"jRLD5K%GS6ߒ+XY2W[D91iaߵoi{Q Ds:g/DAnزsqmChM 6^?ީ1Gvo mgCDlSV$|y/x9j^|.J} d7pinz 2_eiXTBO$[&F(P iB< :2#l3P`&"`@MϜQx X (HH\a%b% U\bZڝgs2T:E}t/9KhU[YOЁ6 S1pt|4`@p& ofx7ag8?2g=hH #ͧbEv]GMMB &`pMĚZdEtQM芢s3y_ZdN7fJģC8ˇ >U݊YnvI7 +#*]df*zp:ˣLz&X3)iN8(} ^*l."r5;ks,1WL2&@a2#&tjynWZrbJ$.:@n|Kq@~"V]lUxl2KB<6ɉ(7,8;if~t^4(uY/k~g]2Q(d=lZ?F"]''Kh1;/*k߽GX"7 q-YRv{0cer>b V/gq//ubt"g)dt]*6o|8~S]FJB (DC@$gu?,bD SrygozI6bzhx)gc=" D*8PaL؎{0dw}K}UFk֜Gs<=q*Z}m̐nKͩ8{{-dxiL_RGb#T30[?3u4?E%^p('Zzܟu' z(ĠHx$+ec^gsTYFu^:jy8)wĪRrHsd{q&[F]dM>`6AjY"& ԏ =YR) X#ĮGiIγ^cMJt4j}S!+ӳg&Q?ܲaj;<DJSBN59lYDd_qo !w8UP^L 2m|$+ տoO\L9+tcyq*ItC(Fybiq1c1Pc?yIdۙMF7L)zD(ЕF$=uƒaCe\Lϯg nνhr[c!ЫOS"q̑kQ2N,dUmݑ㿅Мٛ+ Σ*^ RNͅTdʔ6ֺ͊+]Ό%tMI0;y V:Gieee:S L*ۮEqo{{8 (ШnЏҤw ʰn۹%Ḯ^u#B%t*8;\ XSYEZqt@/\Dw&DqP~:c7A;_NFYpeV-ob: ~!j)A*p_Z9ikzOxx,*u|:]X1,A R'jOYelc 0,1vukYw̤ ރ' K$+ɫFx)n_A2k,/G2|"T7 ,5@u>IcG2Hv};-'7xP`i8Wy.[mos;J[8mce7 HFb5{zA`{?p(4\ EEahbwK=HO,//0fd@ljcLQR1a_h<:(:Y$ew{nS}5mtZgZY-Q7^*/D*nW*Q'p,3GdM<-8=@*edEy%ea27cONД\0'0,=53 ڱn^+ 9'0DH*"AIy)3#1zE#uNAK"FZadc5 D(q&2 6VzMg*mpj{|f(c\lPr{[%"!+{;\"jmHF5"kiTRoQv~Ph@{?U@k>hYU2A0߾,)I1,B86~)dwɔjp6)nv\S@h-/mBpWxE 0#T}NpH(\MYH}4o; ,)|.uzM J|Wr l\BCR y\Sv&*=U*wcXO4'6H_$9WwKcS ^gSO,{Kߑ87~Q 3&t:]@4{mDN*+(]0.u"OMga Fv>awW.D$omH!wy=6:Z8OLdKlTPtj,O~&DS&Ue_%30!Iiq z&qxX+^$cn\!]' :5'1y}çSEP\d:)+|]^L17TI M{]7/E|+-)h*x(i#Ry2JEs} /)"/dƺFP^{KGxa b_G&˩h[P(J!bSiFM8=5 jjH }!P S)a#<W]mFIP(,VKUy~3M;%% MzBB744$ri}tu*O*NHVh9~;p`AT.H/OR,XtfG=({r,!g926,in5gjr\/x+YF#MZKNVd7 r51a?)hW:?pR_S3=# ;FNLeqY@>{@n2s(4 [SMMyPgU Clo"EV9\\N?Q0O3bb/WW$G%^BM*`mCK)xotyh,!9@; } $U;W7BKw/5a*F^^$3rxPK)ehwluG xa jPP+7.! 6@( ]B2T2- LXd@p='~e/=jX[ hV4~) QUo;JrL`if O>҃grHw)|SXVyJmw,4S26[H\oP$|'LU<(,s! IOjMh 1PmHrG^S}T0s`pӑ0I>y!61C+Xkt  ^|Uo\y:3c^u/I%@m.l~k*Onfhxi\ 60erYrw^36 8x&7oegfA,_ITV~ Zz ˬO)LQ#?,b0p0UF KZ;4+Eje,WjK2:O/5=['"j}!K+)?S~N9l*hE\ fuzX[vbQ3Hfh-Yj*Bk<+D'J19@P.GjTxVG.VN`e(U $Vw8MLL;b%xZ9G ʌzV:<\Axn{úr’$ ^&:|;XS9!3))xQhIh+4-[MyX9 B[4YO/'pS>qn$vD}K0_vc' aSGi+?3 G=:v| 3674Gi8kf4ζ*) %<\D&~B$\"~(DFoXP, T|FHo,q{DNH1\x2`n'/`Z.NWtZJ=NVo/gI8UAM)blhO*:d.FIo5)w[?+H%Lt?rWhaxzvSb:{>P7g^hEz/GP@{SY&a^M~]¿7/#6! 74$JGF+I1vA)q{ ;*ïS&DG dq=~D8;3&|[9 A5.f{-5dIn` [a&b'>f) K0ȘUުJ7HB}e<`+5"w-t (DP5m+Y/4?/2;_. _ Ξ#B9 / EuWINum=x@tgi}7e )JaeaT^5"v99k=5iT?ieL2|n&il$`bw*"&x]KX6 -]~(78Djam?4(a;Jz:G(sA kIFyO#6~WFbF)o>)7;/'9[j.ixb&&+!mF`WPxs`@b:~+ pE$ MPr(M_w`la uy, WwN-`xWy<] Ojg&sAgSSNWALTc%Dao'wntTeQJkfN6V<Њ=;I}8;x%3AX&Q @Ń,{ I %cӗd-Z #-Ex~"kFI r#D)>R f|(=o3Xz?N|[4rcf y()ooAcWIlF."`uij^ /R vdq= `L(%6dOO%)? W"iPf6 )*jBXe_!mA4TTfSXb/U'QaQ3.d]Y4=_gđHJdr)t2&E74ZcRgZ|0Au%jdP$?:kA#rzj51x)=*/d h4l @ QqwD>U=Su;L0ɢAezN}̗OgC@!  W5c3[!:-$r^ 2fgiC-1vu+Cyt nۨ-K%;0 `rc1/UD\J#'! p=qR>ooG-UlWYsi*7Mr0uCBD׬4\m#^}Cٲ3Het~^6zlRI mAWM-oy1 n>ZOSWR(EC=!K'` (n-z\Rh'iv$Xn9HD_!̄/0.DΤU./b"NCYe9Q5kVSN%Ws~0mMdD^grx6}H2gGn cQa&s}X'#pJI,iGg(,!M xK8'^V@7 gwI(YX`f3)3't/^W*K! jOH){_0uTBVbC0\z yAzy[5d oh7BKa<1i[ 5TX3;OZ81\=