\ *****************************************************************************
\ * Copyright (c) 2011 IBM Corporation
\ * All rights reserved.
\ * This program and the accompanying materials
\ * are made available under the terms of the BSD License
\ * which accompanies this distribution, and is available at
\ * http://www.opensource.org/licenses/bsd-license.php
\ *
\ * Contributors:
\ *     IBM Corporation - initial implementation
\ ****************************************************************************/

." Populating " pwd

0 CONSTANT vscsi-debug

0 VALUE vscsi-unit

\ -----------------------------------------------------------
\ Direct DMA conversion hack
\ -----------------------------------------------------------
: l2dma ( laddr - dma_addr)      
;

\ -----------------------------------------------------------
\ CRQ related functions
\ -----------------------------------------------------------

0    VALUE     crq-base
0    VALUE     crq-dma
0    VALUE     crq-offset
1000 CONSTANT  CRQ-SIZE

CREATE crq 10 allot

: crq-alloc ( -- )
    \ XXX We rely on SLOF alloc-mem being aligned
    CRQ-SIZE alloc-mem to crq-base 0 to crq-offset
    crq-base l2dma to crq-dma
;

: crq-free ( -- )
    vscsi-unit hv-free-crq
    crq-base CRQ-SIZE free-mem 0 to crq-base
;

: crq-init ( -- res )
    \ Allocate CRQ. XXX deal with fail
    crq-alloc

    vscsi-debug IF
        ." VSCSI: allocated crq at " crq-base . cr
    THEN

    \ Clear buffer
    crq-base CRQ-SIZE erase

    \ Register with HV
    vscsi-unit crq-dma CRQ-SIZE hv-reg-crq

    \ Fail case
    dup 0 <> IF
        ." VSCSI: Error " . ."  registering CRQ !" cr
	crq-free
    THEN
;

: crq-cleanup ( -- )
    crq-base 0 = IF EXIT THEN

    vscsi-debug IF
        ." VSCSI: freeing crq at " crq-base . cr
    THEN
    crq-free
;

: crq-send ( msgaddr -- true | false )
    vscsi-unit swap hv-send-crq 0 =
;

: crq-poll ( -- true | false)
    crq-offset crq-base + dup
    vscsi-debug IF
        ." VSCSI: crq poll " dup .
    THEN
    c@
    vscsi-debug IF
        ."  value=" dup . cr
    THEN
    80 and 0 <> IF
        dup crq 10 move
	0 swap c!
	crq-offset 10 + dup CRQ-SIZE >= IF drop 0 THEN to crq-offset
	true
    ELSE drop false THEN
;

: crq-wait ( -- true | false)
    \ FIXME: Add timeout
    0 BEGIN drop crq-poll dup not WHILE d# 1 ms REPEAT
    dup not IF
        ." VSCSI: Timeout waiting response !" cr EXIT
    ELSE
        vscsi-debug IF
            ." VSCSI: got crq: " crq dup l@ . ."  " 4 + dup l@ . ."  "
	    4 + dup l@ . ."  " 4 + l@ . cr
        THEN
    THEN
;

\ -----------------------------------------------------------
\ CRQ encapsulated SRP definitions
\ -----------------------------------------------------------

01 CONSTANT VIOSRP_SRP_FORMAT
02 CONSTANT VIOSRP_MAD_FORMAT
03 CONSTANT VIOSRP_OS400_FORMAT
04 CONSTANT VIOSRP_AIX_FORMAT
06 CONSTANT VIOSRP_LINUX_FORMAT
07 CONSTANT VIOSRP_INLINE_FORMAT

struct
   1 field >crq-valid
   1 field >crq-format
   1 field >crq-reserved
   1 field >crq-status
   2 field >crq-timeout
   2 field >crq-iu-len
   8 field >crq-iu-data-ptr
constant /crq

: srp-send-crq ( addr len -- )
    80                crq >crq-valid c!
    VIOSRP_SRP_FORMAT crq >crq-format c!
    0                 crq >crq-reserved c!
    0                 crq >crq-status c!
    0                 crq >crq-timeout w!
    ( len )           crq >crq-iu-len w!
    ( addr ) l2dma    crq >crq-iu-data-ptr x!
    crq crq-send
    not IF
        ." VSCSI: Error sending CRQ !" cr
    THEN
;

: srp-wait-crq ( -- [tag true] | false )
    crq-wait not IF false EXIT THEN

    crq >crq-format c@ VIOSRP_SRP_FORMAT <> IF
    	." VSCSI: Unsupported SRP response: "
	crq >crq-format c@ . cr
	false EXIT
    THEN

    crq >crq-iu-data-ptr x@ true
;

\ Add scsi functions to dictionary
scsi-open


\ -----------------------------------------------------------
\ SRP definitions
\ -----------------------------------------------------------

0 VALUE >srp_opcode

00 CONSTANT SRP_LOGIN_REQ
01 CONSTANT SRP_TSK_MGMT
02 CONSTANT SRP_CMD
03 CONSTANT SRP_I_LOGOUT
c0 CONSTANT SRP_LOGIN_RSP
c1 CONSTANT SRP_RSP
c2 CONSTANT SRP_LOGIN_REJ
80 CONSTANT SRP_T_LOGOUT
81 CONSTANT SRP_CRED_REQ
82 CONSTANT SRP_AER_REQ
41 CONSTANT SRP_CRED_RSP
42 CONSTANT SRP_AER_RSP

02 CONSTANT SRP_BUF_FORMAT_DIRECT
04 CONSTANT SRP_BUF_FORMAT_INDIRECT

struct
   1 field >srp-login-opcode
   3 +
   8 field >srp-login-tag
   4 field >srp-login-req-it-iu-len
   4 +
   2 field >srp-login-req-buf-fmt
   1 field >srp-login-req-flags
   5 +
  10 field >srp-login-init-port-ids
  10 field >srp-login-trgt-port-ids
constant /srp-login

struct
   1 field >srp-lresp-opcode
   3 +
   4 field >srp-lresp-req-lim-delta
   8 field >srp-lresp-tag
   4 field >srp-lresp-max-it-iu-len
   4 field >srp-lresp-max-ti-iu-len
   2 field >srp-lresp-buf-fmt
   1 field >srp-lresp-flags
constant /srp-login-resp

struct
   1 field >srp-lrej-opcode
   3 +
   4 field >srp-lrej-reason
   8 field >srp-lrej-tag
   8 +
   2 field >srp-lrej-buf-fmt
constant /srp-login-rej

00 CONSTANT SRP_NO_DATA_DESC
01 CONSTANT SRP_DATA_DESC_DIRECT
02 CONSTANT SRP_DATA_DESC_INDIRECT

struct
    1 field >srp-cmd-opcode
    1 field >srp-cmd-sol-not
    3 +
    1 field >srp-cmd-buf-fmt
    1 field >srp-cmd-dout-desc-cnt
    1 field >srp-cmd-din-desc-cnt
    8 field >srp-cmd-tag
    4 +
    8 field >srp-cmd-lun
    1 +
    1 field >srp-cmd-task-attr
    1 +
    1 field >srp-cmd-add-cdb-len
   10 field >srp-cmd-cdb
    0 field >srp-cmd-cdb-add
constant /srp-cmd

struct
    1 field >srp-rsp-opcode
    1 field >srp-rsp-sol-not
    2 +
    4 field >srp-rsp-req-lim-delta
    8 field >srp-rsp-tag
    2 +
    1 field >srp-rsp-flags
    1 field >srp-rsp-status
    4 field >srp-rsp-dout-res-cnt
    4 field >srp-rsp-din-res-cnt
    4 field >srp-rsp-sense-len
    4 field >srp-rsp-resp-len
    0 field >srp-rsp-data
constant /srp-rsp

\ Storage for up to 256 bytes SRP request */
CREATE srp 100 allot
0 VALUE srp-len

: srp-prep-cmd-nodata ( srplun -- )
    srp /srp-cmd erase
    SRP_CMD srp >srp-cmd-opcode c!
    1 srp >srp-cmd-tag x!
    srp >srp-cmd-lun x!         \ 8 bytes lun
    /srp-cmd to srp-len   
;

: srp-prep-cmd-io ( addr len srplun -- )
    srp-prep-cmd-nodata		( addr len )
    swap l2dma			( len dmaaddr )
    srp srp-len +    		( len dmaaddr descaddr )
    dup >r x! r> 8 +		( len descaddr+8 )
    dup 0 swap l! 4 +		( len descaddr+c )
    l!    
    srp-len 10 + to srp-len
;

: srp-prep-cmd-read ( addr len srplun -- )
    srp-prep-cmd-io
    01 srp >srp-cmd-buf-fmt c!	\ in direct buffer
    1 srp >srp-cmd-din-desc-cnt c!
;

: srp-prep-cmd-write ( addr len srplun -- )
    srp-prep-cmd-io
    10 srp >srp-cmd-buf-fmt c!	\ out direct buffer
    1 srp >srp-cmd-dout-desc-cnt c!
;

: srp-send-cmd ( -- )
    vscsi-debug IF
        ." VSCSI: Sending SCSI cmd " srp >srp-cmd-cdb c@ . cr
    THEN
    srp srp-len srp-send-crq
;

: srp-rsp-find-sense ( -- addr )
    \ XXX FIXME: Always in same position
    srp >srp-rsp-data
;

: srp-wait-rsp ( -- true | [ ascq asc sense-key false ] )
    srp-wait-crq not IF false EXIT THEN
    dup 1 <> IF
        ." VSCSI: Invalid CRQ response tag, want 1 got " . cr
	false EXIT
    THEN drop
    
    srp >srp-rsp-tag x@ dup 1 <> IF
        ." VSCSI: Invalid SRP response tag, want 1 got " . cr
	false EXIT
    THEN drop
    
    srp >srp-rsp-status c@
    vscsi-debug IF
        ." VSCSI: Got response status: "
	dup .status-text cr
    THEN

    0 <> IF
       srp-rsp-find-sense
       scsi-get-sense-data
       vscsi-debug IF
           ." VSCSI: Sense key: " dup .sense-text cr	   
       THEN
       false EXIT
    THEN
    true
;


\ -----------------------------------------------------------
\ Core VSCSI
\ -----------------------------------------------------------

CREATE sector d# 512 allot

8000000000000000 INSTANCE VALUE current-target

\ SCSI test-unit-read
: test-unit-ready ( -- true | [ ascq asc sense-key false ] )
    current-target srp-prep-cmd-nodata
    srp >srp-cmd-cdb scsi-build-test-unit-ready
    srp-send-cmd
    srp-wait-rsp
;

: inquiry ( -- true | false )
    \ WARNING: ATAPI devices with libata seem to ignore the MSB of
    \ the allocation length... let's only ask for ff bytes
    sector ff current-target srp-prep-cmd-read
    ff srp >srp-cmd-cdb scsi-build-inquiry
    srp-send-cmd
    srp-wait-rsp
    dup not IF nip nip nip EXIT THEN \ swallow sense
;

: report-luns ( -- true | false )
    sector 200 current-target srp-prep-cmd-read
    200 srp >srp-cmd-cdb scsi-build-report-luns
    srp-send-cmd
    srp-wait-rsp
    dup not IF nip nip nip EXIT THEN \ swallow sense
;

: read-capacity ( -- true | false )
    sector scsi-length-read-cap-10 current-target srp-prep-cmd-read
    srp >srp-cmd-cdb scsi-build-read-cap-10
    srp-send-cmd
    srp-wait-rsp
    dup not IF nip nip nip EXIT THEN \ swallow sense    
;

: start-stop-unit ( state# -- true | false )
    current-target srp-prep-cmd-nodata
    srp >srp-cmd-cdb scsi-build-start-stop-unit
    srp-send-cmd
    srp-wait-rsp
    dup not IF nip nip nip EXIT THEN \ swallow sense    
;

: get-media-event ( -- true | false )
    sector scsi-length-media-event current-target srp-prep-cmd-read
    srp >srp-cmd-cdb scsi-build-get-media-event
    srp-send-cmd
    srp-wait-rsp
    dup not IF nip nip nip EXIT THEN \ swallow sense    
;

: read-blocks ( -- addr block# #blocks blksz -- [ #read-blocks true ] | false )
    over * 					( addr block# #blocks len )    
    >r rot r> 			                ( block# #blocks addr len )
    5 0 DO
      	2dup current-target
	srp-prep-cmd-read                       ( block# #blocks addr len )
        2swap					( addr len block# #blocks )
        2dup srp >srp-cmd-cdb scsi-build-read-10 ( addr len block# #blocks )
	2swap                                   ( block# #blocks addr len )
        srp-send-cmd
 	srp-wait-rsp
	IF 2drop nip true UNLOOP EXIT THEN
	srp >srp-rsp-status c@ 8 <> IF
	    nip nip nip 2drop 2drop false EXIT
	THEN
	3drop
	100 ms
    LOOP
    2drop 2drop false
;

\ Cleanup behind us
: vscsi-cleanup
    \ ." VSCSI: Cleaning up" cr
    crq-cleanup
    \ Disable TCE bypass:
    vscsi-unit 0 rtas-set-tce-bypass
;

\ Initialize our vscsi instance
: vscsi-init ( -- true | false )
    ." VSCSI: Initializing" cr

    \ Can't use my-unit bcs we aren't instanciating (fix this ?)
    " reg" get-node get-package-property IF
        ." VSCSI: Not reg property !!!" 0
    THEN
    decode-int to vscsi-unit 2drop

    \ Enable TCE bypass special qemu feature
    vscsi-unit 1 rtas-set-tce-bypass

    \ Initialize CRQ
    crq-init 0 <> IF false EXIT THEN

    \ Send init command
    " "(C0 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00)" drop
    crq-send not IF
        ." VSCSI: Error sending init command"
        crq-cleanup false EXIT
    THEN

    \ Wait reply
    crq-wait not IF
        crq-cleanup false EXIT
    THEN

    \ Check init reply
    crq c@ c0 <> crq 1 + c@ 02 <> or IF
        ." VSCSI: Initial handshake failed"
	crq-cleanup false EXIT
    THEN

    \ We should now login etc.. but we really don't need to
    \ with our qemu model

    \ Ensure we cleanup after booting
    ['] vscsi-cleanup add-quiesce-xt

    true
;

\ -----------------------------------------------------------
\ SCSI scan at boot and child device support
\ -----------------------------------------------------------

\ We use SRP luns of the form 8000 | (bus << 8) | (id << 5) | lun
\ in the top 16 bits of the 64-bit LUN
: set-target ( srplun -- )
    to current-target 
;

: dev-max-transfer ( -- n )
    10000 \ Larger value seem to have problems with some CDROMs
;

: dev-get-capacity ( -- blocksize #blocks )
    \ Make sure that there are zeros in the buffer in case something goes wrong:
    sector 10 erase
    \ Now issue the read-capacity command
    read-capacity not IF
        0 0 EXIT
    THEN
    sector scsi-get-capacity-10
;

: dev-read-blocks ( -- addr block# #blocks blksize -- #read-blocks )
    read-blocks    
;

: initial-test-unit-ready ( -- true | [ ascq asc sense-key false ] )
    0 0 0 false
    3 0 DO
        2drop 2drop
        test-unit-ready dup IF UNLOOP EXIT THEN
    LOOP    
;

: compare-sense ( ascq asc key ascq2 asc2 key2 -- true | false )
    3 pick =	    ( ascq asc key ascq2 asc2 keycmp )
    swap 4 pick =   ( ascq asc key ascq2 keycmp asccmp )
    rot 5 pick =    ( ascq asc key keycmp asccmp ascqcmp )
    and and nip nip nip
;

0 CONSTANT CDROM-READY
1 CONSTANT CDROM-NOT-READY
2 CONSTANT CDROM-NO-DISK
3 CONSTANT CDROM-TRAY-OPEN
4 CONSTANT CDROM-INIT-REQUIRED
5 CONSTANT CDROM-TRAY-MAYBE-OPEN

: cdrom-status ( -- status )
    initial-test-unit-ready
    IF CDROM-READY EXIT THEN

    vscsi-debug IF
        ." TestUnitReady sense: " 3dup . . . cr
    THEN

    3dup 1 4 2 compare-sense IF
        3drop CDROM-NOT-READY EXIT
    THEN

    get-media-event IF
        sector w@ 4 >= IF
	    sector 2 + c@ 04 = IF
	        sector 5 + c@
		dup 02 and 0<> IF drop 3drop CDROM-READY EXIT THEN
		dup 01 and 0<> IF drop 3drop CDROM-TRAY-OPEN EXIT THEN
		drop 3drop CDROM-NO-DISK EXIT
	    THEN
	THEN
    THEN

    3dup 2 4 2 compare-sense IF
        3drop CDROM-INIT-REQUIRED EXIT
    THEN
    over 4 = over 2 = and IF
        \ Format in progress... what do we do ? Just ignore
	3drop CDROM-READY EXIT
    THEN
    over 3a = IF
        3drop CDROM-NO-DISK EXIT
    THEN

    \ Other error...
    3drop CDROM-TRAY-MAYBE-OPEN    
;

: cdrom-try-close-tray ( -- )
    scsi-const-load start-stop-unit drop
;

: cdrom-must-close-tray ( -- )
    scsi-const-load start-stop-unit not IF
        ." Tray open !" cr -65 throw
    THEN
;

: dev-prep-cdrom ( -- )
    5 0 DO
        cdrom-status CASE
	    CDROM-READY           OF UNLOOP EXIT ENDOF
	    CDROM-NO-DISK         OF ." No medium !" cr -65 THROW ENDOF
	    CDROM-TRAY-OPEN       OF cdrom-must-close-tray ENDOF
	    CDROM-INIT-REQUIRED   OF cdrom-try-close-tray ENDOF
	    CDROM-TRAY-MAYBE-OPEN OF cdrom-try-close-tray ENDOF
	ENDCASE
	d# 1000 ms
    LOOP
    ." Drive not ready !" cr -65 THROW
;

: dev-prep-disk ( -- )
    initial-test-unit-ready 0= IF
        ." Disk not ready!" cr
        3drop
    THEN
;

: vscsi-create-disk	( srplun -- )
    " disk" 0 " vio-vscsi-device.fs" included
;

: vscsi-create-cdrom	( srplun -- )
    " cdrom" 1 " vio-vscsi-device.fs" included
;

: wrapped-inquiry ( -- true | false )
    inquiry not IF false EXIT THEN
    \ Skip devices with PQ != 0
    sector inquiry-data>peripheral c@ e0 and 0 =
;

8 CONSTANT #dev

: vscsi-report-luns ( -- array ndev )
  \ array of pointers, up to 8 devices
  #dev 3 << alloc-mem dup
  0                                    ( devarray devcur ndev )   
  #dev 0 DO
     i 8 << 8000 or 30 << set-target
     report-luns IF
        sector l@                     ( devarray devcur ndev size )
        sector 8 + swap               ( devarray devcur ndev lunarray size )
        dup 8 + dup alloc-mem         ( devarray devcur ndev lunarray size size+ mem )
        dup rot 0 fill                ( devarray devcur ndev lunarray size mem )
        dup >r swap move r>           ( devarray devcur ndev mem )
        dup sector l@ 3 >> 0 DO       ( devarray devcur ndev mem memcur )
           dup dup x@ j 8 << 8000 or or 30 << swap x! 8 +
        LOOP drop
	rot                           ( devarray ndev mem devcur )
        dup >r x! r> 8 +              ( devarray ndev devcur )
        swap 1 +
     THEN
  LOOP
  nip
;

: vscsi-find-disks      ( -- )   
    ." VSCSI: Looking for devices" cr
    vscsi-report-luns
    0 DO
       dup x@
       BEGIN
          dup x@
          dup 0= IF drop FALSE ELSE
             set-target wrapped-inquiry IF	
	        ."   " current-target (u.) type ."  "
	        \ XXX FIXME: Check top bits to ignore unsupported units
	        \            and maybe provide better printout & more cases
                \ XXX FIXME: Actually check for LUNs
	        sector inquiry-data>peripheral c@ CASE
                   0   OF ." DISK     : " current-target vscsi-create-disk  ENDOF
                   5   OF ." CD-ROM   : " current-target vscsi-create-cdrom ENDOF
                   7   OF ." OPTICAL  : " current-target vscsi-create-cdrom ENDOF
                   e   OF ." RED-BLOCK: " current-target vscsi-create-disk  ENDOF
                   dup dup OF ." ? (" . 8 emit 29 emit 5 spaces ENDOF
                ENDCASE
	        sector .inquiry-text cr
	     THEN
             8 + TRUE
          THEN
       UNTIL drop
       8 +
    LOOP drop
;

\ Remove scsi functions from word list
scsi-close

: setup-alias
    " scsi" find-alias 0= IF
        " scsi" get-node node>path set-alias
    ELSE
        drop
    THEN 
;

: vscsi-init-and-scan  ( -- )
    \ Create instance for scanning:
    0 0 get-node open-node ?dup 0= IF EXIT THEN
    my-self >r
    dup to my-self
    \ Scan the VSCSI bus:
    vscsi-init IF
        vscsi-find-disks
        setup-alias
    THEN
    \ Close the temporary instance:
    close-node
    r> to my-self
;

\ Create a dummy "disk" node with no unit for the sake of
\ the SLES11 installer. It is -not- a fully functional node
\ you can open to access a disk at this stage
new-device
s" disk" device-name
s" block" device-type      
finish-device

vscsi-init-and-scan
