home *** CD-ROM | disk | FTP | other *** search
/ Media Share 9 / MEDIASHARE_09.ISO / network / pcb121.zip / WD8003E.INC < prev   
Text File  |  1992-01-23  |  19KB  |  496 lines

  1. ;;******************************************************************************
  2. ;;                         wd8003e.inc      wd8003e.inc
  3. ;;******************************************************************************
  4. ;;
  5. ;;  Copyright (C) 1989 Northwestern University, Vance Morrison
  6. ;;
  7. ;;
  8. ;; Permission to view, compile, and modify for LOCAL (intra-organization) 
  9. ;; USE ONLY is hereby granted, provided that this copyright and permission 
  10. ;; notice appear on all copies.  Any other use by permission only.
  11. ;;
  12. ;; Northwestern University makes no representations about the suitability 
  13. ;; of this software for any purpose.  It is provided "as is" without expressed 
  14. ;; or implied warranty.  See the copywrite notice file for complete details.
  15. ;;
  16. ;;******************************************************************************
  17. ;; wd8003 holds the interface routines for the western digital ethernet card 
  18. ;; WD8003E or the starlan card WD8003S.  Althougth this routine will work 
  19. ;; for any of the above cards, it has been optimized for the Ethernet card.
  20. ;;
  21. ;; The functions provided by this file are
  22. ;;
  23. ;;   WDE_DECLARE name,io_address,shr_seg,shr_off,promiscuous,total_pgs,bits16
  24. ;;   WDE_DEFINE name
  25. ;;   WDE_IF_R_ACCESS_out_BX_CX_ES name, no_packet
  26. ;;   WDE_IF_R_CONT_in_BX_CX_ES_const_BX_CX_DX_BP_SI_DI_ES name, ok
  27. ;;   WDE_IF_R_FREE_const_BX_CX_BP_SI_DI_ES name
  28. ;;   WDE_IF_W_ACCESS_in_CX_out_DI_ES_const_BX_CX_BP name, no_buffer
  29. ;;   WDE_IF_W_WRITE_in_CX_const_BX_BP_ES name
  30. ;;   WDE_IF_SET_ADDRESS_in_SI_const_BX_CX_BP_DI_ES name
  31. ;;   WDE_IF_COPY_in_CX_SI_DI_ES_out_SI_DI_const_BX_BP_ES name
  32. ;;
  33. ;; Variables set by this module
  34. ;;
  35. ;;   wde_&name&_declared                     ;; one if this interface exists
  36. ;;   if_&name&_address                       ;; the hardware address
  37. ;;   if_&name&_mtu                           ;; the maximum trans unit
  38. ;;
  39. ;;******************************************************************************
  40.  
  41.     include wd.inc
  42.  
  43. ;;******************************************************************************
  44. ;; data storage needed by this module
  45.  
  46. wde_data  STRUC
  47.    wde_new_bndry      DB 0
  48. wde_data ENDS
  49.  
  50.  
  51. ;;******************************************************************************
  52. ;;   IF_DECLARE name, io_address, shr_seg, shr_off    
  53. ;;       declares an interface object.  'io_address' is the address of the
  54. ;;       start of the 8003E control registers.  'shr_seg'  and
  55. ;;       'shr_off' is the address of the WD8003 card buffer
  56. ;;       This address must be a multiple of 512.  If 'promiscuous' is not
  57. ;;       zero (or blank),the interface is configured so that every packet on 
  58. ;;       the ethernet is received.  'total_pg' if not blank, is the total
  59. ;;       amount of pages of shared memory.  (default = 32 pages = 8K)
  60. ;;       if 'bits16' is non-blank and equal to 1, then the card is assumed
  61. ;;       to be a WD8013EBT card.  It it is 2, then it is a WD8013 card but
  62. ;;       we have disabled 16bit transfers (some hardware doesn't like it)
  63. ;;
  64. WDE_DECLARE MACRO name, io_address, shr_seg, shr_off, promiscuous, total_pg, bits16
  65.     .errb <name>
  66.     .errb <io_address>
  67.     .errb <shr_seg>
  68.     .errb <shr_off>
  69.  
  70.     .DATA
  71.     wde_&name&_declared     = 1
  72.     wde_&name&_io           = io_address          ;; set compile time values
  73.     wde_&name&_shared_off   = shr_off
  74.     wde_&name&_shared_seg   = shr_seg
  75.     if shr_seg lt 8000h
  76.         .err Shared memory MUST be above 80000H
  77.     endif
  78.  
  79.     wde_&name&_stop_pg      = STOP_PG
  80.     ifnb <total_pg>
  81.         wde_&name&_stop_pg  = 0&total_pg
  82.     endif
  83.  
  84.     wde_&name&_promiscuous = 0
  85.     ifnb <promiscuous>
  86.         wde_&name&_promiscuous = 0&promiscuous
  87.     endif
  88.  
  89.     wde_&name&_bits16       = 0
  90.     ifnb <bits16>
  91.         wde_&name&_bits16   = 0&bits16
  92.     endif
  93.  
  94.     if wde_&name&_bits16 eq 1
  95.             ;; note these can only touch registers AX and DX
  96.         WDE_16BIT_ON MACRO myname
  97.             mov AL,LAN16ENB or LA19 or MEM16ENB
  98.             WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES LAAR, wde_&&myname&&_io
  99.         ENDM
  100.  
  101.         WDE_16BIT_OFF MACRO  myname
  102.             mov AL,LAN16ENB or LA19
  103.             WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES LAAR, wde_&&myname&&_io
  104.         ENDM
  105.  
  106.         MEM_DECLARE_16BIT WDE_16BIT_ON, WDE_16BIT_OFF, name
  107.     else
  108.         MEM_DECLARE_8BIT 
  109.     endif
  110.  
  111.     if_&name&_mtu = 1514
  112.     global wde_&name&_data:wde_data
  113.     global if_&name&_address:word 
  114.     .CODE
  115.     global wde_&name&_real_define:near
  116. ENDM
  117.  
  118.  
  119. ;;******************************************************************************
  120. ;;   IF_DEFINE name
  121. ;;      sets asside memory an name object and initializes it.  This
  122. ;;      routine is a no-op if 'name' was not declared
  123. ;;
  124. WDE_DEFINE MACRO name
  125. ifdef wde_&name&_declared
  126.     call wde_&name&_real_define
  127. endif
  128. ENDM
  129.  
  130. WDE_REAL_DEFINE MACRO name
  131.     LOCAL loop1, loop2, loop3, around, resetwait
  132.     .errb <name>
  133.  
  134. ifdef wde_&name&_declared
  135.     .DATA
  136.     if_&name&_address DW 3 DUP (0)
  137.     wde_&name&_data    wde_data      <>  ;; create storage needed
  138.  
  139.     .CODE
  140.     wde_&name&_real_define:
  141.     mov cx, 6                    ;; get the ethernet address
  142.     mov bx, OFFSET if_&name&_address
  143.     mov dx, wde_&name&_io+ADDROM ;; point to the Ethernet address ROM
  144.     loop1:                       
  145.         in AL, DX
  146.         mov [BX], AL        
  147.         inc DX
  148.         inc BX
  149.     loop loop1
  150.  
  151.     mov AL, 80h                  ;; reset the card
  152.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES 0, wde_&name&_io   
  153.     mov AL, 00h
  154.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES 0, wde_&name&_io
  155.  
  156. ;;   this sets bit 6 (0 justified) of register offset 0x05, it will enable
  157. ;;   the lan controller to access shared RAM 16 bits at a time
  158. ;;   In addition, this routine maintains address bit 19
  159. ;;   (previous cards assumed this bit high...we must do it manually)
  160.  
  161. ;;          note: this is a write only register and only exists on the WD8013
  162.     if wde_&name&_bits16 eq 1
  163.         mov AL, LAN16ENB + LA19  ; set bit19 of address and 16 bit mode for card
  164.         WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES LAAR, wde_&name&_io
  165.     endif
  166.  
  167.            ;; register 0 is the MSR (Memory base reg)
  168.        ;; this will enable the on board ram
  169.     mov AL, (wde_&name&_shared_seg+(wde_&name&_shared_off/16))/512 
  170.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES 0, wde_&name&_io
  171.  
  172.     mov AL, MSK_STP + MSK_PG0 + MSK_RD2     ;; RESET, goto page 0
  173.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES CMDR, wde_&name&_io
  174.  
  175.     xor AL, AL                              ;; clear RBCR0,1
  176.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES RBCR0, wde_&name&_io
  177.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES RBCR1, wde_&name&_io
  178.  
  179.     resetwait:          ;; make sure reset is complete
  180.     READ_PORT_out_AL_const_BX_CX_BP_SI_DI_ES ISR, wde_&name&_io
  181.     test AL, MSK_RST
  182.     jz resetwait
  183.  
  184.     mov AL, MSK_BMS + MSK_FT10              ;; select FIFO threshold = 8 bytes
  185.     if wde_&name&_bits16 eq 1
  186.         or AL, MSK_WTS              ;; FOR 16 BIT OPERATION
  187.     endif
  188.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES DCR, wde_&name&_io
  189.  
  190.     xor AL, AL                              ;; turn off receiving
  191.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES RCVR, wde_&name&_io
  192.     mov AL, MSK_LBm1                        ;; enter loopback operation mode 1
  193.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES TCR, wde_&name&_io
  194.  
  195.     mov AL, STRT_PG                    ;; start of input buffer (in 256b pages)
  196.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES PSTART, wde_&name&_io  
  197.     mov AL, wde_&name&_stop_pg          ;; end of input buffer (in 256b pages)
  198.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES PSTOP, wde_&name&_io
  199.     mov AL, STRT_PG                    
  200.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES BNRY, wde_&name&_io    
  201.  
  202.     mov AL, -1                              ;; clear all status bits
  203.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES ISR, wde_&name&_io 
  204.     mov AL, 0                               ;; no interupts
  205.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES IMR, wde_&name&_io 
  206.  
  207.     mov AL, MSK_STP + MSK_PG1 + MSK_RD2     ;; make sure we are on page 1
  208.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES CMDR, wde_&name&_io
  209.  
  210.     mov CX, 6                               ;; set the ethernet address
  211.     mov BX, OFFSET if_&name&_address
  212.  
  213.     mov DX, wde_&name&_io+PAR0
  214.     loop2:
  215.         mov AL, [BX]        ;; get 1 byte into AL
  216.         out DX, AL          ;; write to PAR
  217.         inc BX
  218.         inc DX
  219.     loop loop2
  220.  
  221.     if wde_&name&_promiscuous ne 0
  222.         mov AL, 0FFH
  223.     else
  224.         xor AL, AL          
  225.     endif
  226.     mov CX, 8                           ;; set the multicast address to all 0's
  227.     mov DX, wde_&name&_io+MAR0
  228.     loop3:
  229.         out DX, AL
  230.         inc DX
  231.     loop loop3                  
  232.  
  233.     mov AL, STRT_PG+1                       ;; Set input pointer for queue
  234.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES CURR, wde_&name&_io
  235.  
  236.     mov AL, MSK_STA + MSK_PG0 + MSK_RD2           ;; make sure we are on page 0
  237.                           ;; and start the NIC 8390
  238.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES CMDR, wde_&name&_io
  239.  
  240.     xor AL, AL                                    ;; loopback off
  241.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES TCR, wde_&name&_io
  242.  
  243.                           ;; set receiver mode
  244.     if wde_&name&_promiscuous ne 0
  245.         mov AL, MSK_AB+MSK_AM+MSK_PRO             ;; promiscuous
  246.     else
  247.         mov AL, MSK_AB                            ;; just broadcasts + to me
  248.     endif
  249.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES RCVR, wde_&name&_io 
  250.  
  251.     RET
  252. endif
  253. ENDM
  254.  
  255.  
  256. ;;******************************************************************************
  257. ;;   IF_R_ACCESS_out_BX_ES name, no_packet
  258. ;;       IF_R_ACCESS waits for the next packet to come from the the board
  259. ;;       associated with 'name' and returns a pointer to the begining of 
  260. ;;       an ethernet packet in BX:ES.  CX holds the length of the packet
  261. ;;       R_ACCESS jumps to 'no_packet' if there are no packets waiting to 
  262. ;;       be read in
  263. ;;       
  264. WDE_IF_R_ACCESS_out_BX_CX_ES MACRO name, no_packet
  265.     local inside, good_packet, wrapped, new_wrapped, bad_packet, ok_status
  266.     local good_length, truncate
  267.     .errb <no_packet>
  268.  
  269.     mov AL, MSK_PG0 + MSK_RD2              ;; read the BNRY register into AL
  270.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES CMDR, wde_&name&_io
  271.     READ_PORT_out_AL_const_BX_CX_BP_SI_DI_ES BNRY, wde_&name&_io
  272.  
  273.     inc AL                                  ;; increment with wrap around
  274.     cmp AL, wde_&name&_stop_pg
  275.     jb inside
  276.         mov AL, STRT_PG     
  277.     inside:
  278.     mov BH, AL                              ;; save it in BH
  279.  
  280.     mov AL, MSK_PG1 + MSK_RD2               ;; read CURR register into AL
  281.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES CMDR, wde_&name&_io
  282.     READ_PORT_out_AL_const_BX_CX_BP_SI_DI_ES CURR, wde_&name&_io
  283.  
  284.     cmp AL, BH
  285.     je no_packet
  286.     xor BL, BL                                 ;; BX now holds pointer to packet
  287.  
  288.     mov DX, wde_&name&_shared_seg          ;; ES = segment address
  289.     mov ES, DX
  290.     mov AH, ES:[BX+wde_&name&_shared_off]   ;; get the status
  291.     cmp AH, SMK_PRX                              ;; is it good
  292.     jz ok_status
  293.         cmp AH, SMK_PRX+SMK_PHY
  294.     jnz bad_packet
  295.  
  296.     ok_status:
  297.     mov AH, ES:[BX+1+wde_&name&_shared_off] ;; pointer to the next packet
  298.         ;; sanity check on next packet pointer AH
  299.     cmp BH, AL                              ;; is BNDRY+1 <= CURR?
  300.     ja wrapped
  301.         cmp AH, BH
  302.         jb bad_packet
  303.         cmp AH, AL
  304.         jbe good_packet
  305.         jmp bad_packet
  306.     wrapped:
  307.         cmp AH, BH
  308.         jb new_wrapped
  309.             cmp AH, wde_&name&_stop_pg
  310.             jnb bad_packet
  311.             jmp good_packet
  312.         new_wrapped:
  313.             cmp AH, STRT_PG
  314.             jb bad_packet
  315.             cmp AH, AL
  316.             jbe good_packet
  317.     bad_packet:
  318.         ;; set BNDRY = BNDRY+1 and try again
  319.         mov AL, MSK_PG0 + MSK_RD2             ;; make sure we are on page 0
  320.         WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES CMDR, wde_&name&_io
  321.  
  322.         mov AL, BH                            ;; write the new BNRY register
  323.         WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES BNRY, wde_&name&_io    
  324.         jmp no_packet                         ;; return bad status
  325.     good_packet:
  326.  
  327.     mov wde_&name&_data.wde_new_bndry, AH      ;; save it for R_FREE
  328.     mov CX, ES:[BX+wde_&name&_shared_off+2]    ;; load the length
  329.     add BX, wde_&name&_shared_off+4 ;; BX point to begining of the packet
  330.     sub CX, 4
  331.  
  332.     cmp CX, 1536                               ;; sanity check
  333.     jle good_length
  334.         cmp CH, CL
  335.         jne truncate
  336.             xor CH, CH                         ;; fix western digital bug
  337.             jmp good_length
  338.         truncate:
  339.         mov CX, 1536
  340.     good_length:
  341. ENDM
  342.  
  343.  
  344. ;;******************************************************************************
  345. ;;   IF_R_FREE_const_BX_CX_BP_SI_DI_ES  name
  346. ;;       After the client is through processing the packet returned by 
  347. ;;       IF_R_ACCESS, IF_R_FREE must be called to inform 'name' that the 
  348. ;;       memory that the packet was in can be reused for future packets.
  349. ;;
  350. WDE_IF_R_FREE_const_BX_CX_BP_SI_DI_ES MACRO name
  351.     local inside
  352.     .errb <name>
  353.  
  354.     mov AL, MSK_PG0 + MSK_RD2                    ;; make sure we are on page 0
  355.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES CMDR, wde_&name&_io
  356.  
  357.     mov AL, wde_&name&_data.wde_new_bndry       ;; Retreive NEW_BOUNDRY
  358.     dec AL 
  359.     cmp AL, STRT_PG
  360.     jge inside
  361.         mov AL, wde_&name&_stop_pg-1
  362.     inside:                         ;; write the new BNRY register
  363.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES BNRY, wde_&name&_io    
  364. ENDM
  365.  
  366.  
  367. ;;******************************************************************************
  368. ;;   WDE_IF_R_CONT_in_BX_CX_ES name, ok
  369. ;;       IF_R_CONT determines if the packet returned by R_READ in BX:ES
  370. ;;       of length CX is continuous.  If it is it jumps to 'ok' otherwise
  371. ;;       it just returns
  372. ;;
  373. WDE_IF_R_CONT_in_BX_CX_ES_const_BX_CX_DX_BP_SI_DI_ES MACRO name, ok
  374.     .errb <ok>
  375.  
  376.     mov AX, BX
  377.     add AX, CX
  378.     cmp AX, OFFSET wde_&name&_shared_off+wde_&name&_stop_pg*256
  379.     jb ok
  380. ENDM
  381.  
  382.  
  383. ;;******************************************************************************
  384. ;;   IF_W_ACCESS_in_CX_out_DI_ES name, no_buffer
  385. ;;       IF_W_ACCESS returns a pointer to an output buffer for a packet.  The 
  386. ;;       pointer is returned in DI:ES.  If the ouptut buffer is busy, this 
  387. ;;       routine will jump to 'no_buffer'.  The output buffer  min(CX, 1536) 
  388. ;;       bytes long
  389. ;;
  390. WDE_IF_W_ACCESS_in_CX_out_DI_ES_const_BX_CX_BP MACRO name, no_buffer
  391.     local wait_loop
  392.     .errb <no_buffer>
  393.  
  394.     mov DI, 65000       ;; so we don't wait forever
  395.     wait_loop:
  396.         dec DI
  397.         jz no_buffer
  398.  
  399.         READ_PORT_out_AL_const_BX_CX_BP_SI_DI_ES CMDR, wde_&name&_io
  400.         test AL, MSK_TXP
  401.     jnz wait_loop
  402.  
  403.     mov DI, wde_&name&_shared_off       ;; return DI:ES pointer
  404.     mov DX, wde_&name&_shared_seg  
  405.     mov ES, DX
  406. ENDM
  407.  
  408.  
  409. ;;******************************************************************************
  410. ;;   IF_W_WRITE_in_CX name
  411. ;;       IF_W_WRITE actually signals the ethernet board to write a packet to 
  412. ;;       the ethernet.  The packet is assumed to be in the buffer returned by 
  413. ;;       IF_W_ACCESS. CX is the length of the packet to send.  
  414. ;;
  415. WDE_IF_W_WRITE_in_CX_const_BX_BP_ES MACRO name
  416.     .errb <name>
  417.  
  418.     mov AL, MSK_PG0 + MSK_RD2        ;; make sure we are in register page 0
  419.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES CMDR, wde_&name&_io
  420.     mov AL, CL                        ;; set length
  421.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES TBCR0, wde_&name&_io
  422.     mov AL, CH
  423.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES TBCR1, wde_&name&_io
  424.  
  425.     xor AL, AL                        ;; tell card packet begins at page 0
  426.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES TPSR, wde_&name&_io
  427.     mov AL, MSK_TXP + MSK_RD2         ;; send the packet
  428.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES CMDR, wde_&name&_io
  429. ENDM
  430.  
  431.  
  432. ;;******************************************************************************
  433. ;;   IF_SET_ADDRESS_in_SI name
  434. ;;       IF_SET_ADDRESS_in_SI sets the hardware address to be the value
  435. ;;       pointed to by SI.  Note this function may be a no-op if the
  436. ;;       hardware address cannot be set (ETHERNET for example)
  437. ;;
  438.  
  439. WDE_IF_SET_ADDRESS_in_SI_const_BX_CX_BP_DI_ES MACRO name
  440.     .err    ;; we don't support setting ethernet addresses (yet)
  441.     ENDM
  442.  
  443.  
  444. ;;******************************************************************************
  445. ;;   IF_COPY_in_CX_SI_DI_ES_out_SI_DI name
  446. ;;      IF_COPY_in_CX_SI_DI_ES copys a packet from the input buffer (pointed 
  447. ;;      to by SI and the segement register given in IF_DECLARE) to an output 
  448. ;;      buffer (pointed to by DI and dest_reg) of length CX.   It assumes the
  449. ;;      output buffer is contiguous.  (and the caller shouln't care if the 
  450. ;;      input buffer is contiguous)
  451. ;;
  452.  
  453. WDE_IF_COPY_in_CX_SI_DI_ES_out_SI_DI_const_BX_BP_ES MACRO name
  454.     local wrap, done, no_time1, no_time2
  455.     .errb <name>
  456.  
  457.     mov DX, DS                           ;; save DS
  458.     mov AX, wde_&name&_shared_seg  
  459.     mov DS, AX
  460.  
  461.     mov AX, OFFSET wde_&name&_shared_off+wde_&name&_stop_pg*256
  462.     sub AX, SI                            ;; AX holds length to wrap line
  463.     cmp AX, CX
  464.     jl wrap                               ;; wrap if AX less than packet lenght
  465.         MEM_COPY_in_CX_SI_DI_ES_out_SI_DI_const_AX_BX_DX_BP_ES
  466.         jmp done
  467.     wrap:
  468.         xchg AX, CX                       ;; length is now length to wrap line
  469.         sub AX, CX                        ;; AX holds remainder
  470.         MEM_COPY_in_CX_SI_DI_ES_out_SI_DI_const_AX_BX_DX_BP_ES
  471.  
  472.         mov SI, OFFSET wde_&name&_shared_off+STRT_PG*256
  473.         mov CX, AX
  474.  
  475.         MEM_COPY_in_CX_SI_DI_ES_out_SI_DI_const_AX_BX_DX_BP_ES
  476.     done:
  477.  
  478.     mov DS, DX                            ;; restore DS
  479. ENDM
  480.  
  481.  
  482. ;;******************************************************************************
  483. ;; utility functions needed only within this module
  484.  
  485. READ_PORT_out_AL_const_BX_CX_BP_SI_DI_ES MACRO port, if_io
  486.     mov DX, if_io+port
  487.     in  AL, DX                              ;; AL contains data read from port
  488. ENDM
  489.  
  490. ;;******************************************************************************
  491. WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES MACRO port, if_io
  492.     mov DX, if_io+port
  493.     out DX, AL                              ;; AL contains data read from port
  494. ENDM 
  495.  
  496.