home *** CD-ROM | disk | FTP | other *** search
/ ProfitPress Mega CDROM2 …eeware (MSDOS)(1992)(Eng) / ProfitPress-MegaCDROM2.B6I / MISC / NETWORK / PCRTE220.ZIP / WD8003E.INC < prev   
Encoding:
Text File  |  1991-02-19  |  18.4 KB  |  494 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.         ;; note these can only touch registers AX and DX
  95.     WDE_&name&_16BIT_ON MACRO 
  96.         mov AL,LAN16ENB or LA19 or MEM16ENB
  97.         WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES LAAR, wde_&name&_io
  98.     ENDM
  99.  
  100.     WDE_&name&_16BIT_OFF MACRO 
  101.         mov AL,LAN16ENB or LA19
  102.         WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES LAAR, wde_&name&_io
  103.     ENDM
  104.  
  105.     if wde_&name&_bits16 eq 1
  106.         MEM_DECLARE_16BIT WDE_&name&_16BIT_ON WDE_&name&_16BIT_OFF
  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.     mov AL, LAN16ENB + LA19  ; set bit19 of address and 16 bit mode for card
  163.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES LAAR, wde_&name&_io
  164.  
  165.            ;; register 0 is the MSR (Memory base reg)
  166.        ;; this will enable the on board ram
  167.     mov AL, (wde_&name&_shared_seg+(wde_&name&_shared_off/16))/512 
  168.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES 0, wde_&name&_io
  169.  
  170.     mov AL, MSK_STP + MSK_PG0 + MSK_RD2     ;; RESET, goto page 0
  171.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES CMDR, wde_&name&_io
  172.  
  173.     xor AL, AL                              ;; clear RBCR0,1
  174.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES RBCR0, wde_&name&_io
  175.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES RBCR1, wde_&name&_io
  176.  
  177.     resetwait:          ;; make sure reset is complete
  178.     READ_PORT_out_AL_const_BX_CX_BP_SI_DI_ES ISR, wde_&name&_io
  179.     test AL, MSK_RST
  180.     jz resetwait
  181.  
  182.     mov AL, MSK_BMS + MSK_FT10              ;; select FIFO threshold = 8 bytes
  183.     if wde_&name&_bits16 eq 1
  184.         or AL, MSK_WTS              ;; FOR 16 BIT OPERATION
  185.     endif
  186.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES DCR, wde_&name&_io
  187.  
  188.     xor AL, AL                              ;; turn off receiving
  189.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES RCVR, wde_&name&_io
  190.     mov AL, MSK_LBm1                        ;; enter loopback operation mode 1
  191.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES TCR, wde_&name&_io
  192.  
  193.     mov AL, STRT_PG                    ;; start of input buffer (in 256b pages)
  194.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES PSTART, wde_&name&_io  
  195.     mov AL, wde_&name&_stop_pg          ;; end of input buffer (in 256b pages)
  196.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES PSTOP, wde_&name&_io
  197.     mov AL, STRT_PG                    
  198.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES BNRY, wde_&name&_io    
  199.  
  200.     mov AL, -1                              ;; clear all status bits
  201.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES ISR, wde_&name&_io 
  202.     mov AL, 0                               ;; no interupts
  203.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES IMR, wde_&name&_io 
  204.  
  205.     mov AL, MSK_STP + MSK_PG1 + MSK_RD2     ;; make sure we are on page 1
  206.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES CMDR, wde_&name&_io
  207.  
  208.     mov CX, 6                               ;; set the ethernet address
  209.     mov BX, OFFSET if_&name&_address
  210.  
  211.     mov DX, wde_&name&_io+PAR0
  212.     loop2:
  213.         mov AL, [BX]        ;; get 1 byte into AL
  214.         out DX, AL          ;; write to PAR
  215.         inc BX
  216.         inc DX
  217.     loop loop2
  218.  
  219.     if wde_&name&_promiscuous ne 0
  220.         mov AL, 0FFH
  221.     else
  222.         xor AL, AL          
  223.     endif
  224.     mov CX, 8                           ;; set the multicast address to all 0's
  225.     mov DX, wde_&name&_io+MAR0
  226.     loop3:
  227.         out DX, AL
  228.         inc DX
  229.     loop loop3                  
  230.  
  231.     mov AL, STRT_PG+1                       ;; Set input pointer for queue
  232.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES CURR, wde_&name&_io
  233.  
  234.     mov AL, MSK_STA + MSK_PG0 + MSK_RD2           ;; make sure we are on page 0
  235.                           ;; and start the NIC 8390
  236.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES CMDR, wde_&name&_io
  237.  
  238.     xor AL, AL                                    ;; loopback off
  239.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES TCR, wde_&name&_io
  240.  
  241.                           ;; set receiver mode
  242.     if wde_&name&_promiscuous ne 0
  243.         mov AL, MSK_AB+MSK_AM+MSK_PRO             ;; promiscuous
  244.     else
  245.         mov AL, MSK_AB                            ;; just broadcasts + to me
  246.     endif
  247.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES RCVR, wde_&name&_io 
  248.  
  249.     RET
  250. endif
  251. ENDM
  252.  
  253.  
  254. ;;******************************************************************************
  255. ;;   IF_R_ACCESS_out_BX_ES name, no_packet
  256. ;;       IF_R_ACCESS waits for the next packet to come from the the board
  257. ;;       associated with 'name' and returns a pointer to the begining of 
  258. ;;       an ethernet packet in BX:ES.  CX holds the length of the packet
  259. ;;       R_ACCESS jumps to 'no_packet' if there are no packets waiting to 
  260. ;;       be read in
  261. ;;       
  262. WDE_IF_R_ACCESS_out_BX_CX_ES MACRO name, no_packet
  263.     local inside, good_packet, wrapped, new_wrapped, bad_packet, ok_status
  264.     local good_length, truncate
  265.     .errb <no_packet>
  266.  
  267.     mov AL, MSK_PG0 + MSK_RD2              ;; read the BNRY register into AL
  268.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES CMDR, wde_&name&_io
  269.     READ_PORT_out_AL_const_BX_CX_BP_SI_DI_ES BNRY, wde_&name&_io
  270.  
  271.     inc AL                                  ;; increment with wrap around
  272.     cmp AL, wde_&name&_stop_pg
  273.     jb inside
  274.         mov AL, STRT_PG     
  275.     inside:
  276.     mov BH, AL                              ;; save it in BH
  277.  
  278.     mov AL, MSK_PG1 + MSK_RD2               ;; read CURR register into AL
  279.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES CMDR, wde_&name&_io
  280.     READ_PORT_out_AL_const_BX_CX_BP_SI_DI_ES CURR, wde_&name&_io
  281.  
  282.     cmp AL, BH
  283.     je no_packet
  284.     xor BL, BL                                 ;; BX now holds pointer to packet
  285.  
  286.     mov DX, wde_&name&_shared_seg          ;; ES = segment address
  287.     mov ES, DX
  288.     mov AH, ES:[BX+wde_&name&_shared_off]   ;; get the status
  289.     cmp AH, SMK_PRX                              ;; is it good
  290.     jz ok_status
  291.         cmp AH, SMK_PRX+SMK_PHY
  292.     jnz bad_packet
  293.  
  294.     ok_status:
  295.     mov AH, ES:[BX+1+wde_&name&_shared_off] ;; pointer to the next packet
  296.         ;; sanity check on next packet pointer AH
  297.     cmp BH, AL                              ;; is BNDRY+1 <= CURR?
  298.     ja wrapped
  299.         cmp AH, BH
  300.         jb bad_packet
  301.         cmp AH, AL
  302.         jbe good_packet
  303.         jmp bad_packet
  304.     wrapped:
  305.         cmp AH, BH
  306.         jb new_wrapped
  307.             cmp AH, wde_&name&_stop_pg
  308.             jnb bad_packet
  309.             jmp good_packet
  310.         new_wrapped:
  311.             cmp AH, STRT_PG
  312.             jb bad_packet
  313.             cmp AH, AL
  314.             jbe good_packet
  315.     bad_packet:
  316.         ;; set BNDRY = BNDRY+1 and try again
  317.         mov AL, MSK_PG0 + MSK_RD2             ;; make sure we are on page 0
  318.         WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES CMDR, wde_&name&_io
  319.  
  320.         mov AL, BH                            ;; write the new BNRY register
  321.         WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES BNRY, wde_&name&_io    
  322.         jmp no_packet                         ;; return bad status
  323.     good_packet:
  324.  
  325.     mov wde_&name&_data.wde_new_bndry, AH      ;; save it for R_FREE
  326.     mov CX, ES:[BX+wde_&name&_shared_off+2]    ;; load the length
  327.     add BX, wde_&name&_shared_off+4 ;; BX point to begining of the packet
  328.     sub CX, 4
  329.  
  330.     cmp CX, 1536                               ;; sanity check
  331.     jle good_length
  332.         cmp CH, CL
  333.         jne truncate
  334.             xor CH, CH                         ;; fix western digital bug
  335.             jmp good_length
  336.         truncate:
  337.         mov CX, 1536
  338.     good_length:
  339. ENDM
  340.  
  341.  
  342. ;;******************************************************************************
  343. ;;   IF_R_FREE_const_BX_CX_BP_SI_DI_ES  name
  344. ;;       After the client is through processing the packet returned by 
  345. ;;       IF_R_ACCESS, IF_R_FREE must be called to inform 'name' that the 
  346. ;;       memory that the packet was in can be reused for future packets.
  347. ;;
  348. WDE_IF_R_FREE_const_BX_CX_BP_SI_DI_ES MACRO name
  349.     local inside
  350.     .errb <name>
  351.  
  352.     mov AL, MSK_PG0 + MSK_RD2                    ;; make sure we are on page 0
  353.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES CMDR, wde_&name&_io
  354.  
  355.     mov AL, wde_&name&_data.wde_new_bndry       ;; Retreive NEW_BOUNDRY
  356.     dec AL 
  357.     cmp AL, STRT_PG
  358.     jge inside
  359.         mov AL, wde_&name&_stop_pg-1
  360.     inside:                         ;; write the new BNRY register
  361.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES BNRY, wde_&name&_io    
  362. ENDM
  363.  
  364.  
  365. ;;******************************************************************************
  366. ;;   WDE_IF_R_CONT_in_BX_CX_ES name, ok
  367. ;;       IF_R_CONT determines if the packet returned by R_READ in BX:ES
  368. ;;       of length CX is continuous.  If it is it jumps to 'ok' otherwise
  369. ;;       it just returns
  370. ;;
  371. WDE_IF_R_CONT_in_BX_CX_ES_const_BX_CX_DX_BP_SI_DI_ES MACRO name, ok
  372.     .errb <ok>
  373.  
  374.     mov AX, BX
  375.     add AX, CX
  376.     cmp AX, OFFSET wde_&name&_shared_off+wde_&name&_stop_pg*256
  377.     jb ok
  378. ENDM
  379.  
  380.  
  381. ;;******************************************************************************
  382. ;;   IF_W_ACCESS_in_CX_out_DI_ES name, no_buffer
  383. ;;       IF_W_ACCESS returns a pointer to an output buffer for a packet.  The 
  384. ;;       pointer is returned in DI:ES.  If the ouptut buffer is busy, this 
  385. ;;       routine will jump to 'no_buffer'.  The output buffer  min(CX, 1536) 
  386. ;;       bytes long
  387. ;;
  388. WDE_IF_W_ACCESS_in_CX_out_DI_ES_const_BX_CX_BP MACRO name, no_buffer
  389.     local wait_loop
  390.     .errb <no_buffer>
  391.  
  392.     mov DI, 65000       ;; so we don't wait forever
  393.     wait_loop:
  394.         dec DI
  395.         jz no_buffer
  396.  
  397.         READ_PORT_out_AL_const_BX_CX_BP_SI_DI_ES CMDR, wde_&name&_io
  398.         test AL, MSK_TXP
  399.     jnz wait_loop
  400.  
  401.     mov DI, wde_&name&_shared_off       ;; return DI:ES pointer
  402.     mov DX, wde_&name&_shared_seg  
  403.     mov ES, DX
  404. ENDM
  405.  
  406.  
  407. ;;******************************************************************************
  408. ;;   IF_W_WRITE_in_CX name
  409. ;;       IF_W_WRITE actually signals the ethernet board to write a packet to 
  410. ;;       the ethernet.  The packet is assumed to be in the buffer returned by 
  411. ;;       IF_W_ACCESS. CX is the length of the packet to send.  
  412. ;;
  413. WDE_IF_W_WRITE_in_CX_const_BX_BP_ES MACRO name
  414.     .errb <name>
  415.  
  416.     mov AL, MSK_PG0 + MSK_RD2        ;; make sure we are in register page 0
  417.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES CMDR, wde_&name&_io
  418.     mov AL, CL                        ;; set length
  419.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES TBCR0, wde_&name&_io
  420.     mov AL, CH
  421.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES TBCR1, wde_&name&_io
  422.  
  423.     xor AL, AL                        ;; tell card packet begins at page 0
  424.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES TPSR, wde_&name&_io
  425.     mov AL, MSK_TXP + MSK_RD2         ;; send the packet
  426.     WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES CMDR, wde_&name&_io
  427. ENDM
  428.  
  429.  
  430. ;;******************************************************************************
  431. ;;   IF_SET_ADDRESS_in_SI name
  432. ;;       IF_SET_ADDRESS_in_SI sets the hardware address to be the value
  433. ;;       pointed to by SI.  Note this function may be a no-op if the
  434. ;;       hardware address cannot be set (ETHERNET for example)
  435. ;;
  436.  
  437. WDE_IF_SET_ADDRESS_in_SI_const_BX_CX_BP_DI_ES MACRO name
  438.     .err    ;; we don't support setting ethernet addresses (yet)
  439.     ENDM
  440.  
  441.  
  442. ;;******************************************************************************
  443. ;;   IF_COPY_in_CX_SI_DI_ES_out_SI_DI name
  444. ;;      IF_COPY_in_CX_SI_DI_ES copys a packet from the input buffer (pointed 
  445. ;;      to by SI and the segement register given in IF_DECLARE) to an output 
  446. ;;      buffer (pointed to by DI and dest_reg) of length CX.   It assumes the
  447. ;;      output buffer is contiguous.  (and the caller shouln't care if the 
  448. ;;      input buffer is contiguous)
  449. ;;
  450.  
  451. WDE_IF_COPY_in_CX_SI_DI_ES_out_SI_DI_const_BX_BP_ES MACRO name
  452.     local wrap, done, no_time1, no_time2
  453.     .errb <name>
  454.  
  455.     mov DX, DS                           ;; save DS
  456.     mov AX, wde_&name&_shared_seg  
  457.     mov DS, AX
  458.  
  459.     mov AX, OFFSET wde_&name&_shared_off+wde_&name&_stop_pg*256
  460.     sub AX, SI                            ;; AX holds length to wrap line
  461.     cmp AX, CX
  462.     jl wrap                               ;; wrap if AX less than packet lenght
  463.         MEM_COPY_in_CX_SI_DI_ES_out_SI_DI_const_AX_BX_DX_BP_ES
  464.         jmp done
  465.     wrap:
  466.         xchg AX, CX                       ;; length is now length to wrap line
  467.         sub AX, CX                        ;; AX holds remainder
  468.         MEM_COPY_in_CX_SI_DI_ES_out_SI_DI_const_AX_BX_DX_BP_ES
  469.  
  470.         mov SI, OFFSET wde_&name&_shared_off+STRT_PG*256
  471.         mov CX, AX
  472.  
  473.         MEM_COPY_in_CX_SI_DI_ES_out_SI_DI_const_AX_BX_DX_BP_ES
  474.     done:
  475.  
  476.     mov DS, DX                            ;; restore DS
  477. ENDM
  478.  
  479.  
  480. ;;******************************************************************************
  481. ;; utility functions needed only within this module
  482.  
  483. READ_PORT_out_AL_const_BX_CX_BP_SI_DI_ES MACRO port, if_io
  484.     mov DX, if_io+port
  485.     in  AL, DX                              ;; AL contains data read from port
  486. ENDM
  487.  
  488. ;;******************************************************************************
  489. WRITE_PORT_in_AL_const_AX_BX_CX_BP_SI_DI_ES MACRO port, if_io
  490.     mov DX, if_io+port
  491.     out DX, AL                              ;; AL contains data read from port
  492. ENDM 
  493.  
  494.