home *** CD-ROM | disk | FTP | other *** search
/ The Best of Mecomp Multimedia 2 / MECOMP-CD-II.iso / amiga / emulation / qlsource / romsrc / par / par_asm
Encoding:
Text File  |  1997-01-03  |  12.4 KB  |  556 lines

  1.     SECTION    PAR
  2.  
  3.     INCLUDE    '/INC/QDOS_inc'
  4.     INCLUDE    '/INC/AMIGA_inc'
  5.     INCLUDE    '/INC/AMIGQDOS_inc'
  6.  
  7. ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  8. ; PAR1_asm - parallel device driver
  9. ;      - last modified 03/01/97
  10.  
  11. ; These are all the necessary parallel port related sources,
  12. ; required to implement a QDOS parallel device on the Amiga
  13. ; computer.
  14.  
  15. ; Amiga-QDOS sources by Rainer Kowallik
  16. ;  ...latest changes by Mark J Swift
  17.  
  18. ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  19. ;  device driver definitions
  20. ;  (for the driver's own pseudo system vars)
  21.  
  22. PV_PEND    EQU    $28    ; (long) pending test
  23. PV_FBYTE EQU    $2C    ; (long) fetch byte
  24. PV_SBYTE EQU    $30    ; (long) send byte
  25. PV_RTS    EQU    $34    ; (word) RTS (4E75)
  26. PV_NOP    EQU    $36    ; (word)
  27.             ;  ...(necessary for IO.SERIO)
  28.  
  29. PV_LLVL7 EQU    $38    ; (long) next link
  30. PV_ALVL7 EQU    $3C    ; (long) address
  31. PV_LRSET EQU    $40    ; (long) next link
  32. PV_ARSET EQU    $44    ; (long) address
  33.  
  34. PV_PARTQ EQU    $48    ; (long) address of output queue
  35.  
  36. PV.LEN    EQU    $4C    ; length of PAR device driver defs.
  37.  
  38. ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  39. ;  Channel definition block
  40.  
  41. PAR_PROT EQU    $18        ; EOL Protocol, -ve RAW
  42.                 ; 0 CR/LF, 1 CR, 2 LF
  43. PAR_EOF    EQU    $1A        ; EOF (CLOSE) protocol
  44.                 ; -ve none, 0 FormFeed
  45. PAR_TXD    EQU    $1C        ; last transmitted character
  46. PAR_FLGS EQU    $1E        ; Bit 0    0 = busy
  47.                 ;    1 = ready
  48. PAR_TXQ    EQU    $20        ; Transmit queue header
  49. PAR.TXQL EQU    81        ; Transmit buffer len - odd!
  50.  
  51. PAR.LEN    EQU    PAR_TXQ+Q_QUEUE+(PAR.TXQL+1)&$FFFE
  52.  
  53. ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  54. ;  ROM header
  55.  
  56. BASE:
  57.     dc.l    $4AFB0001    ; ROM recognition code
  58.     dc.w    0
  59.     dc.w    ROM_START-BASE
  60.     dc.b    0,36,'Amiga-QDOS PAR device driver v1.08 ',$A
  61.  
  62. ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  63. ;  start of ROM code
  64.  
  65. ROM_START:
  66.     movem.l    d0-d2/a0-a3,-(a7)
  67.  
  68. ; --------------------------------------------------------------
  69. ;  allocate memory for parallel device variables
  70.  
  71.     move.l    #PV.LEN,d1
  72.     moveq    #MT.ALCHP,d0
  73.     moveq    #0,d2
  74.     trap    #1
  75.  
  76.     tst.l    d0
  77.     bne    ROM_EXIT
  78.  
  79. ; --------------------------------------------------------------
  80. ;  address of PAR variables
  81.  
  82.     move.l    a0,a3
  83.  
  84. ; --------------------------------------------------------------
  85. ;  enter supervisor mode and disable interrupts
  86.  
  87.     trap    #0
  88.  
  89.     ori.w    #$0700,sr    ; disable interrupts
  90.  
  91. ; --------------------------------------------------------------
  92. ;  link a custom routine into level 7 interrupt server
  93.     ifd    dolvl7
  94.     lea    AV.LVL7link,a1
  95.     lea    PV_LLVL7(a3),a2
  96.  
  97.     move.l    (a1),(a2)
  98.     move.l    a2,(a1)
  99.  
  100.     lea    MY_LVL7(pc),a1
  101.     move.l    a1,$04(a2)
  102.     endif
  103. ; --------------------------------------------------------------
  104. ;  routines necessary for device driver
  105.  
  106.     lea    PAR_IO(pc),a2    ; Input/Output routine
  107.     move.l    a2,SV_AIO(a3)
  108.  
  109.     lea    PAR_OPEN(pc),a2    ; OPEN routine
  110.     move.l    a2,SV_AOPEN(a3)
  111.  
  112.     lea    PAR_CLOSe(pc),a2    ; CLOSE routine
  113.     move.l    a2,SV_ACLOS(a3)
  114.  
  115. ; --------------------------------------------------------------
  116. ;  routines necessary for IO.SERIO. (used by PAR I/O routine)
  117.  
  118.     lea    ERR_BP(pc),a2    ; test for pending input
  119.     move.l    a2,PV_PEND(a3)    ; (not provided)
  120.  
  121.     lea    ERR_BP(pc),a2    ; fetch byte
  122.     move.l    a2,PV_FBYTE(a3)    ; (not provided)
  123.  
  124.     lea    PAR_SBYT(pc),a2    ; send byte
  125.     move.l    a2,PV_SBYTE(a3)
  126.  
  127.     move.w    #$4E75,PV_RTS(a3) ; RTS instruction at $34
  128.  
  129. ; --------------------------------------------------------------
  130. ;  set up hardware
  131.  
  132.     bsr.s    INIT_HW
  133.  
  134.     clr.l    PV_PARTQ(a3)
  135.  
  136. ; --------------------------------------------------------------
  137. ;  link in device driver
  138.  
  139.     lea    SV_LIO(a3),a0    ; link address
  140.     moveq    #MT.LIOD,d0    ; link in IO device driver
  141.     trap    #1
  142.  
  143. ; -------------------------------------------------------------
  144. ; link in external interrupt to act on port free
  145.  
  146.     lea    XINT_SERv(pc),a2    ; address of routine
  147.     move.l    a2,SV_AXINT(a3)
  148.     lea    SV_LXINT(a3),a0
  149.     moveq    #MT.LXINT,d0
  150.     trap    #1
  151.  
  152. ; -------------------------------------------------------------
  153. ; link in poll interrupt to re-enable parallel send
  154.  
  155.     lea    POLL_SERv(pc),a2    ; address of routine
  156.     move.l    a2,SV_APOLL(a3)
  157.     lea    SV_LPOLL(a3),a0
  158.     moveq    #MT.LPOLL,d0
  159.     trap    #1
  160.  
  161. ; --------------------------------------------------------------
  162. ;  enable interrupts and re-enter user mode
  163.  
  164.     andi.w    #$D8FF,sr
  165.  
  166. ; --------------------------------------------------------------
  167. ROM_EXIT:
  168.     movem.l    (a7)+,d0-d2/a0-a3
  169.  
  170.     rts
  171.  
  172. ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  173. ;  set up hardware
  174.  
  175. INIT_HW:
  176.     movem.l    d7/a3,-(a7)
  177.  
  178.     move.b    CIAB_DDRA,d7
  179.     andi.b    #%11111000,d7
  180.     move.b    d7,CIAB_DDRA    ; SEL,POUT,BUSY to inputs
  181.  
  182.     MOVE.B    #$FF,CIAA_DDRB    ; set PRB to all output
  183.  
  184.     move.b    CIAA_ICR,d7    ; read & clear CIA-A ICR
  185.     or.b    AV.CIAA_ICR,d7
  186.     bclr    #4,d7        ; clear FLAG bit
  187.     move.b    d7,AV.CIAA_ICR    ; store for another program
  188.  
  189.     move.w    #%0000000000001000,INTREQ ; clear and enable
  190.     move.w    #%1000000000001000,INTENA ; CIA-A interrupts
  191.  
  192.     move.b    #%10010000,CIAA_ICR ; enable FLAG interrupt
  193.  
  194.     ori.b    #%00010000,AV.CIAA_MSK ; take note
  195.  
  196.     ifd    dolvl7
  197.     move.l    AV.PARV,a3    ; address of PAR variables
  198.     move.l    PV_PARTQ(a3),d7    ; address of transmit Q
  199.     beq.s    INIT_HWX     ; exit if Q doesn't exist
  200.  
  201.     move.l    d7,a3
  202.     bset    #0,1+PAR_FLGS-PAR_TXQ(a3) ; port ready
  203.  
  204. INIT_HWX:
  205.     endif
  206.     movem.l    (a7)+,d7/a3
  207.     rts
  208.  
  209. ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  210. ;  Poll interrupt server for parallel port.
  211. ;
  212. ;  Enters: A3 = assumed start of driver definition block
  213. ;      A6 = system variables
  214.  
  215. POLL_SERv:
  216.     bsr    PAR_SEND     ; if port ready, send byte
  217.  
  218. POLL_X:
  219.     rts
  220.  
  221. ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  222. ;  External interrupt server
  223. ;
  224. ;  Enters: A3 = assumed start of driver definition block
  225. ;      A6 = system variables
  226.  
  227. XINT_SERv:
  228.     movem.l    d7/a0,-(a7)
  229.  
  230.     move.w    INTENAR,d7    ; read interrupt enable reg
  231.     btst    #3,d7        ; branch if ints not on
  232.     beq    XINT_OTHer
  233.  
  234.     move.w    INTREQR,d7    ; read interrupt request reg
  235.     btst    #3,d7        ; branch if from CIA-A or
  236.     bne    CIAA_SERv    ; expansion ports
  237.  
  238. ; --------------------------------------------------------------
  239. ;  otherwise let another external interrupt server handle it
  240.  
  241. XINT_OTHer:
  242.     movem.l    (a7)+,d7/a0
  243.     rts
  244.  
  245. ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  246. ;  Interrupt from CIA-A or expansion port
  247.  
  248. CIAA_SERv:
  249.     move.b    CIAA_ICR,d7    ; read CIA-A ICR
  250.     or.b    AV.CIAA_ICR,d7
  251.     move.b    d7,AV.CIAA_ICR    ; store for another program
  252.  
  253.     bclr    #4,d7        ; port ready? (FLAG bit=1)
  254.     beq    XINT_OTHer    ; no
  255.  
  256. ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  257. ;  External interrupt server for acting on par port ready
  258. ;  (CIAA FLAG bit=1).
  259.  
  260. PAR_RDY:
  261.     move.b    d7,AV.CIAA_ICR
  262.  
  263.     and.b    AV.CIAA_MSK,d7    ; don't clear INTREQ if
  264.     bne.s    PAR_RDY0     ; other CIAA ints occured
  265.  
  266.     move.w    #%0000000000001000,INTREQ ; clear interrupts
  267.  
  268. ; -------------------------------------------------------------
  269. PAR_RDY0:
  270.     move.l    PV_PARTQ(a3),d7    ; address of transmit Q
  271.     beq.s    XINT_EXIt    ; exit if Q doesn't exist
  272.  
  273.     move.l    d7,a0
  274.     bset    #0,1+PAR_FLGS-PAR_TXQ(a0) ; port ready
  275.  
  276.     bsr    PAR_SEND
  277.  
  278. ; -------------------------------------------------------------
  279. XINT_EXIt:
  280.     bra    XINT_OTHer
  281.  
  282. ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  283. ;  write next byte from queue to parallel port
  284.  
  285. PAR_SEND:
  286.     movem.l    d1/d7/a2-a4,-(a7)
  287.  
  288.     moveq    #ERR.NC,d0    ; assume no can do
  289.  
  290.     move.l    PV_PARTQ(a3),d1    ; address of transmit Q
  291.     beq    PAR_S_X        ; exit if Q doesn't exist
  292.  
  293.     move.l    d1,a2
  294.     btst    #0,1+PAR_FLGS-PAR_TXQ(a2) ; port ready?
  295.     beq.s    PAR_S_X
  296.  
  297.     btst    #0,CIAB_PRA    ; printer busy?
  298.     bne.s    PAR_S_X
  299.  
  300.     btst    #1,CIAB_PRA    ; paper out?
  301.     bne.s    PAR_S_X
  302.  
  303.     btst    #2,CIAB_PRA    ; printer select?
  304.     beq.s    PAR_S_X
  305.  
  306.     moveq    #0,d1
  307.     move.w    IO.QOUT,a4
  308.     jsr    (a4)        ; get byte d1 from queue a2
  309.     tst.l    d0
  310.     bne.s    PAR_S_X        ; exit if error
  311.  
  312.     bclr    #0,1+PAR_FLGS-PAR_TXQ(a2) ; port busy
  313.  
  314.     move.b    CIAA_ICR,d7    ; read CIA-A ICR
  315.     or.b    AV.CIAA_ICR,d7
  316.     bclr    #4,d7        ; clear FLAG bit
  317.     move.b    d7,AV.CIAA_ICR    ; store for another program
  318.     move.w    #%0000000000001000,INTREQ ; clear interrupts
  319.  
  320.     move.b    d1,CIAA_PRB    ; write data to par port
  321.  
  322. PAR_S_X:
  323.     movem.l    (a7)+,d1/d7/a2-a4
  324.     rts
  325.  
  326. ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  327. ;  OPEN routine for parallel device.
  328. ;
  329. ;  Enters: D3 = open type
  330. ;      A0 = start of device name
  331. ;      A3 = assumed start of driver definition block
  332. ;      A6 = system variables
  333.  
  334. PAR_OPEN:
  335.     movem.l    a3,-(a7)
  336.     suba.w    #4,a7        ; room for 2 words on stack
  337.  
  338.     movea.l    a7,a3        ; address for parameters
  339.     move.w    IO.NAME,a4    ; decode device name
  340.     JSR    (A4)
  341.  
  342.     BRA.S    PAR_O_X        ; not found
  343.     BRA.S    PAR_O_X        ; bad device name
  344.     BRA.S    PAR_O_CHK    ; OK, name was PAR_
  345.  
  346.     dc.w    3        ; length of device name
  347.     dc.b    'PAR',0        ; definition is PAR
  348.     dc.w    2        ; 2 parameters
  349.  
  350.     dc.w    4        ; char value, 4 options
  351.     dc.b    'RNCL'        ; eol protocol
  352.  
  353.     dc.w    1        ; char value, 2 options
  354.     dc.b    'F',0        ; eof protocol
  355.  
  356. ; --------------------------------------------------------------
  357. PAR_O_X:
  358.     adda.w    #8,a7
  359.     ANDI.W    #$F8FF,SR
  360.     RTS
  361.  
  362. ; --------------------------------------------------------------
  363. PAR_O_IU:
  364.     moveq    #ERR.IU,d0    ; in use
  365.     bra    PAR_O_X
  366.  
  367. ; --------------------------------------------------------------
  368. PAR_O_CHK:
  369.     move.l    4(a7),a3     ; address of driver def blok
  370.  
  371.     move.l    PV_PARTQ(a3),d0    ; address set?
  372.     beq.s    PAR_O_CHK1    ; no queue, no chan def blk
  373.  
  374.     movea.l    d0,a0
  375.     suba.w    #PAR_TXQ,a0    ; addrs of old chan def blk
  376.     bclr    #7,PAR_TXQ(a0)    ; output queue empty?
  377.     bne.s    PAR_O_CHK2    ; yes, so continue
  378.     bra.s    PAR_O_IU     ; no, so exit with error
  379.  
  380. PAR_O_CHK1:
  381.     move.w    #(PAR.LEN),d1    ; allocate chan def block
  382.     move.w    MM.ALCHP,a4    ; for first time
  383.     JSR    (A4)
  384.     bne.s    PAR_O_X        ; exit if error occured
  385.  
  386.     moveq    #PAR.TXQL,d1    ; length of transmit queue
  387.     lea    PAR_TXQ(a0),a2    ; address of transmit queue
  388.     move.w    IO.QSET,a4    ; set up queue (not used
  389.     jsr    (a4)        ; before
  390.  
  391.     move.l    4(a7),a3     ; address of PAR variables
  392.     move.l    a2,PV_PARTQ(a3)
  393.  
  394. PAR_O_CHK2:
  395.     move.w    (a7),PAR_PROT(a0) ; store handshake, protocol
  396.     move.w    2(a7),PAR_EOF(a0) ; store EOF protocol
  397.  
  398.     subq.w    #2,PAR_PROT(a0)    ; -ve raw : 0 CR/LF : 1 CR : 2 LF
  399.     subq.w    #1,PAR_EOF(a0)    ; -ve none : 0 FF : CTRL-Z
  400.  
  401.     bset    #0,1+PAR_FLGS(a0) ; set 'port ready' flag
  402.  
  403. ; --------------------------------------------------------------
  404. PAR_O_OK:
  405.     moveq    #ERR.OK,d0    ; signal "no error"
  406.     bra    PAR_O_X
  407.  
  408. ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  409. ;  CLOSE routine for parallel device.
  410. ;
  411. ;  Enters: A0 = start of device name
  412. ;      A3 = assumed start of driver definition block
  413. ;      A6 = system variables
  414.  
  415. PAR_CLOSe:
  416.     tst.b    1+PAR_EOF(a0)
  417.     blt.s    PAR_C2        ; cont if no eof protocol
  418.  
  419.     move.w    #12,d1        ; send Form Feed
  420.  
  421. PAR_CLUP:
  422.     bsr    PAR_SBOK
  423.     cmp.w    #ERR.NC,d0
  424.     beq.s    PAR_CLUP
  425.  
  426. PAR_C2:
  427.     lea    PAR_TXQ(a0),a2
  428.     move.w    IO.QEOF,a4    ; put EOF marker in queue
  429.     jsr    (a4)
  430.  
  431.     moveq    #ERR.OK,d0    ; signal "no errors"
  432.     rts
  433.  
  434. ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  435. ;  I/O routine for parallel device.
  436. ;
  437. ;  Enters: A0 = start of device name
  438. ;      A3 = assumed start of driver definition block
  439. ;      A6 = system variables
  440. ;      D0 = operation type
  441.  
  442. PAR_IO:
  443.     CMP.B    #$7,D0        ; trap file operations
  444.     BHI    ERR_BP
  445.  
  446.     pea    PV_PEND(a3)    ; pretend call just before
  447.     move.w    IO.SERIO,a4
  448.     JMP    (A4)
  449.  
  450. ; --------------------------------------------------------------
  451. PAR_SBYT:
  452.     lea    PAR_TXQ(a0),a2
  453.  
  454.     move.l    d1,d3
  455.     move.w    IO.QTEST,a4
  456.     jsr    (a4)
  457.     move.l    d3,d1
  458.  
  459.     cmpi.w    #ERR.EF,d0
  460.     beq.s    PAR_SB4
  461.  
  462.     moveq    #ERR.OK,d0
  463.  
  464.     cmpi.w    #$6,d2        ; reasonable space in Q?
  465.     bge.s    PAR_SB5
  466.  
  467.     moveq    #ERR.NC,d0
  468.  
  469. PAR_SB4:
  470.     rts
  471.  
  472. PAR_SB5:
  473.     move.b    1+PAR_TXD(a0),d3    ; remember old TX code
  474.     move.b    d1,1+PAR_TXD(a0)    ; save new TX code
  475.  
  476.     tst.b    1+PAR_PROT(a0)
  477.     blt.s    PAR_SBOK     ; branch if no eol protocol
  478.  
  479.     cmpi.b    #$0A,d1        ; Line Feed?
  480.     beq.s    PAR_SB1
  481.  
  482.     cmpi.b    #$0D,d1        ; Carriage Return?
  483.     bne.s    PAR_SBOK     ; branch if neither
  484.  
  485. PAR_SB1:
  486.     cmpi.b    #$0A,d3        ; Line Feed?
  487.     beq.s    PAR_SB2
  488.  
  489.     cmpi.b    #$0D,d3        ; Carriage Return?
  490.     bne.s    PAR_SB3
  491.  
  492. PAR_SB2:
  493.     cmp.b    d1,d3
  494.     beq.s    PAR_SB3
  495.  
  496.     move.b    #$FF,1+PAR_TXD(a0) ; ignore LF in CR/LF couple
  497.     moveq    #ERR.OK,d0
  498.     rts
  499.  
  500. PAR_SB3:
  501.     tst.b    1+PAR_PROT(a0)
  502.     beq.s    PAR_SB6        ; branch if CR/LF protocol
  503.  
  504.     cmp.b    #2,1+PAR_PROT(a0)
  505.     beq.s    PAR_SB7        ; branch if LF
  506.  
  507.     moveq    #$0D,d1        ; else use CR
  508.     bra.s    PAR_SBOK
  509.  
  510. PAR_SB6:
  511.     moveq    #$0D,d1
  512.     bsr    PAR_SBOK
  513.  
  514. PAR_SB7:
  515.     moveq    #$0A,d1
  516.  
  517. PAR_SBOK:
  518.     lea    PAR_TXQ(a0),a2
  519.  
  520.     move.w    IO.QIN,a4    ; put byte d1 into queue a2
  521.     jsr    (a4)
  522.     rts
  523.  
  524. ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  525. ERR_OK:
  526.     moveq    #ERR.OK,d0    ; "no error"
  527.     rts
  528.  
  529. ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  530. ERR_NC:
  531.     moveq    #ERR.NC,d0    ; "not complete"
  532.     rts
  533.  
  534. ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  535. ERR_BP:
  536.     moveq    #ERR.BP,d0    ; "bad parameter"
  537.     rts
  538.  
  539. ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  540. ;  Custom LVL7 routine to re-initialise hardware
  541.     ifd    dolvl7
  542. MY_LVL7:
  543.     bsr    INIT_HW
  544.  
  545.     subq.l    #4,a7
  546.     movem.l    a3,-(a7)
  547.     move.l    AV.PARV,a3
  548.     move.l    PV_LLVL7(a3),a3
  549.     move.l    4(a3),4(a7)    ; address of next routine
  550.     movem.l    (a7)+,a3
  551.  
  552.     rts
  553.     endif
  554. ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  555.     END
  556.