* ========== Copyright Header Begin ==========================================
* Hypervisor Software File: cwq.s
* Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved.
* - Do no alter or remove copyright notices
* - Redistribution and use of this software in source and binary forms, with
* or without modification, are permitted provided that the following
* - Redistribution of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* - Redistribution in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* Neither the name of Sun Microsystems, Inc. or the names of contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
* This software is provided "AS IS," without a warranty of any kind.
* ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
* INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
* MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
* ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
* DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN
* OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR
* FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
* DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
* ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
* SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
* You acknowledge that this software is not designed, licensed or
* intended for use in the design, construction, operation or maintenance of
* ========== Copyright Header End ============================================
* Copyright 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
.ident "@(#)cwq.s 1.2 07/05/21 SMI"
#include <sys/asm_linkage.h>
#include <sparcv9/misc.h>
#include <devices/pc16550.h>
*-----------------------------------------------------------
* Called via setup_cpu() if the given cpu has access
* to a mau. If the handle is non-NULL then the mau
* struct has already been initialized.
* %g1 - &config.cwqs[cwq-id] or NULL (0) if error.
*-----------------------------------------------------------
VCPU2STRAND_STRUCT(%g1, %g5)
ldub [%g5 + STRAND_ID], %g5
and %g5, NSTRANDS_PER_CWQ_MASK, %g5 ! %g5 = hw thread-id
ldx [%g3 + CWQ_CPUSET], %g6
stx %g6, [%g3 + CWQ_CPUSET]
add %g5, CWQ_CPU_ACTIVE, %g5
ldx [%g3 + CWQ_CPUSET], %g5
cmp %g4, %g5 ! 1st (only) cpu?
ID2HANDLE(%g6, CWQ_HANDLE_SIG, %g6)
stx %g6, [%g3 + CWQ_HANDLE]
* Now set up interrupt stuff.
ldx [%g1 + CPU_GUEST], %g1
LABEL_ADDRESS(cwq_intr_getstate, %g4)
!! %g3 = &config.cwqs[cwq-id]
!! %g4 = cwq_intr_getstate
!! %g5 = NULL (no setstate callback)
!! %g7 = return pc (set up in setup_cpu())
* Note that vdev_intr_register() clobbers %g1,%g3,%g5-%g7.
mov %g7, %l3 ! save return pc
HVCALL(vdev_intr_register)
stx %g1, [%g3 + CWQ_IHDLR + CI_COOKIE]
mov CWQ_STATE_RUNNING, %g2
stx %g2, [%g3 + CWQ_STATE]
mov %l3, %g7 ! restore return pc
mov %g3, %g1 ! return &cwqs[cwq-id]
* Wrapper around setup_cwq, so it can be called from C
* SPARC ABI requries only that g2,g3,g4 are preserved across
* %g1 - &config.cwqs[cwq-id] or NULL (0) if error.
* cwqp = c_setup_cwq(vcpup, ino, &config);
STRAND_PUSH(%g2, %g6, %g7)
STRAND_PUSH(%g3, %g6, %g7)
STRAND_PUSH(%g4, %g6, %g7)
*-----------------------------------------------------------
* Called from within trap context.
*-----------------------------------------------------------
brz,pn %g2, .ci_exit_nolock
CWQ_LOCK_ENTER(%g2, %g5, %g4, %g6)
ldx [%g2 + CWQ_STATE], %g3
cmp %g3, CWQ_STATE_RUNNING
* Read CSR (contains error info) and check for errors.
* If there were no errors, set bit 50 of all finished
* Control Words to 1 indicating that the hardware has finished
* processing of these Control Words.
* If there were errors we'll need to do some work.
* We set bit 50 to 1, and copy the CSR error indicator
* bits hwe and protocolerror to bits 52 and 51, resp.
* of the Control Word causing the error and each following
* Control Word of the Control Word Block it belongs to.
ldxa [%g4]ASI_STREAM, %g7
mov ASI_SPU_CWQ_HEAD, %g4
ldxa [%g4]ASI_STREAM, %g3
ldx [%g2 + CWQ_QUEUE + CQ_DR_HEAD], %g4
ldx [%g2 + CWQ_QUEUE + CQ_DR_HV_OFFSET], %g6
be,pn %xcc, .ci_exit ! phantom interrupt, we already
andcc %g7, CWQ_CSR_ERROR, %g7
* %g4 driver's first non-processed CW entry
ldx [%g2 + CWQ_QUEUE + CQ_DR_HV_OFFSET], %g6
sllx %g6, CW_RES_SHIFT, %g6
add %g4, CWQ_CW_SIZE, %g5
ldx [%g2 + CWQ_QUEUE + CQ_DR_LAST], %g6
cmp %g5, %g6 ! next == Last?
ldx [%g2 + CWQ_QUEUE + CQ_DR_BASE], %g6
movgu %xcc, %g6, %g5 ! next = First
* at this point, we have processed everything up to
* cwq[head - 1]. and have an error at cwq[head]
ldx [%g2 + CWQ_QUEUE + CQ_DR_HV_OFFSET], %g5
mov ASI_SPU_CWQ_TAIL, %g4
ldxa [%g4]ASI_STREAM, %g4
stx %g4, [%g2 + CWQ_QUEUE + CQ_DR_TAIL] ! tail
* - Head = CW in error (within CW Block)
* We'll mark all remaining CWs in this CW Block
* with the error bit then move Head to next
* CW Block and reenable CWQ. Note that we will
* not move the Head beyond the Tail. In theory this
* should never happen, but since we rely on software
* to properly set the EOB bit we have to guard against
* it not being set and this code being stuck in an
srl %g7, CWQ_CSR_ERROR_SHIFT - 1, %g6
sllx %g6, CW_RES_SHIFT, %g7
srlx %g5, CW_EOB_SHIFT, %g5
add %g3, CWQ_CW_SIZE, %g3
ldx [%g2 + CWQ_QUEUE + CQ_DR_LAST], %g7
cmp %g3, %g7 ! current == Last?
ldx [%g2 + CWQ_QUEUE + CQ_DR_BASE], %g7
movgu %xcc, %g7, %g3 ! current = First
ldx [%g2 + CWQ_QUEUE + CQ_DR_TAIL], %g7
cmp %g3, %g7 ! current == Tail?
* Move the Head to the next CW.
add %g3, CWQ_CW_SIZE, %g3
ldx [%g2 + CWQ_QUEUE + CQ_DR_LAST], %g7
ldx [%g2 + CWQ_QUEUE + CQ_DR_BASE], %g7
stx %g3, [%g2 + CWQ_QUEUE + CQ_DR_HEAD]
ldx [%g2 + CWQ_QUEUE + CQ_DR_HV_OFFSET], %g5
mov ASI_SPU_CWQ_HEAD, %g4
stxa %g3, [%g4]ASI_STREAM
stx %g3, [%g2 + CWQ_QUEUE + CQ_HEAD]
* Clear the CWQ state and reenable the CWQ.
MAU_LOCK_ENTER(%g6, %g1, %g4, %o0)
ldx [%g6 + MAU_STORE_IN_PROGR], %g4
brnz,pt %g4, .ci_do_enable
stx %g4, [%g6 + MAU_ENABLE_CWQ]
stxa %g6, [%g4]ASI_STREAM
andcc %g6, CWQ_CSR_BUSY, %g6
bz,pt %xcc, .ci_no_error_loop
mov %g0, %g7 ! collect interrupt bits in %g7
* if the busy bit was set in the CSR, we leave the last entry for
* the next invocation of the interrupt handler, as the result of
* the last CW might not be globally visible yet
sub %g3, CWQ_CW_SIZE, %g3
ldx [%g2 + CWQ_QUEUE + CQ_BASE], %g6
cmp %g6, %g3 ! previous < First?
ldx [%g2 + CWQ_QUEUE + CQ_LAST], %g6
movgu %xcc, %g6, %g3 ! pervious = Last
* %g4 driver's first non-processed CW entry
ldx [%g2 + CWQ_QUEUE + CQ_DR_HV_OFFSET], %g6
be,pn %xcc, .ci_no_error_done
or %g5, %g7, %g7 !collecting the intr bits
sllx %g6, CW_RES_SHIFT, %g6
add %g4, CWQ_CW_SIZE, %g5
ldx [%g2 + CWQ_QUEUE + CQ_DR_LAST], %g6
cmp %g5, %g6 ! next == Last?
ldx [%g2 + CWQ_QUEUE + CQ_DR_BASE], %g6
movgu %xcc, %g6, %g5 ! next = First
stx %g4, [%g2 + CWQ_QUEUE + CQ_DR_HEAD]
ldx [%g2 + CWQ_QUEUE + CQ_DR_HV_OFFSET], %g3
stx %g3, [%g2 + CWQ_QUEUE + CQ_HEAD]
srlx %g7, CW_INTR_SHIFT, %g7
and %g7, CW_INTR_MASK, %g7
ldx [%g2 + CWQ_IHDLR + CI_COOKIE], %g1
brz,pt %g7, .ci_exit_nolock ! don't generate interrupt
HVCALL(vdev_intr_generate)
*-----------------------------------------------------------
* Function: cwq_intr_getstate()
*-----------------------------------------------------------
ENTRY_NP(cwq_intr_getstate)
* Note that ideally we would get the actual Head
* from the hardware, however there is no guarantee
* that this routine will be called on the target
* core/cwq, so we have to rely on the Head being
* captured during cwq_intr time.
ldx [%g1 + CWQ_QUEUE + CQ_HEAD], %g3
ldx [%g1 + CWQ_QUEUE + CQ_HEAD_MARKER], %g4
SET_SIZE(cwq_intr_getstate)
*-----------------------------------------------------------
* Function: ncs_qconf_cwq
* %o1 - base real address of queue or queue handle if
* %o2 - number of entries in queue.
* %o0 - EOK (on success),
* EINVAL, ENOACCESS, EBADALIGN,
* ENORADDR, EWOULDBLOCK (on failure)
* %o1 - queue handle for respective queue.
*-----------------------------------------------------------
VCPU_GUEST_STRUCT(%g2, %g1)
brz,pn %o2, .c_qconf_unconfig
cmp %o2, NCS_MIN_CWQ_NENTRIES
cmp %o2, NCS_MAX_CWQ_NENTRIES
* Check that #entries is a power of two.
brz,pn %g3, herr_noaccess
* The cpu that does the queue configure will also
* be the one targeted for all the interrupts for
* this cwq. We need to effectively single thread
* the interrupts per-cwq because the interrupt handler
* updates global per-cwq data structures.
VCPU2STRAND_STRUCT(%g2, %o0)
ldub [%o0 + STRAND_ID], %o0
* Make sure base address is size aligned.
sllx %o2, CWQ_CW_SHIFT, %g4
bnz,pn %xcc, herr_badalign
CWQ_LOCK_ENTER(%g3, %g5, %g2, %g6)
* Translate base address from real to physical.
RA2PA_RANGE_CONV_UNK_SIZE(%g1, %o1, %g4, .c_qconf_noraddr, %g2, %g6)
stx %o0, [%g3 + CWQ_QUEUE + CQ_CPU_PID]
stx %o1, [%g3 + CWQ_QUEUE + CQ_DR_BASE_RA]
stx %g6, [%g3 + CWQ_QUEUE + CQ_DR_BASE]
stx %g6, [%g3 + CWQ_QUEUE + CQ_DR_HEAD]
add %g3, CWQ_QUEUE + CQ_HV_CWS + CWQ_CW_SIZE - 1, %g2
and %g2, -CWQ_CW_SIZE, %g2
stx %g2, [%g3 + CWQ_QUEUE + CQ_BASE]
stx %g2, [%g3 + CWQ_QUEUE + CQ_HEAD]
stx %g6, [%g3 + CWQ_QUEUE + CQ_DR_HV_OFFSET]
sub %g6, CWQ_CW_SIZE, %g6
stx %g6, [%g3 + CWQ_QUEUE + CQ_LAST]
ldx [%g3 + CWQ_QUEUE + CQ_DR_HV_OFFSET], %g4
stx %g4, [%g3 + CWQ_QUEUE + CQ_DR_LAST]
stx %o2, [%g3 + CWQ_QUEUE + CQ_NENTRIES]
st %g0, [%g3 + CWQ_QUEUE + CQ_BUSY]
stx %g0, [%g3 + CWQ_QUEUE + CQ_HEAD_MARKER]
!! %g6 = (end - [1 cwq entry]) (last valid entry)
* Clear any errors and disable the CWQ.
MAU_LOCK_ENTER(%g7, %g1, %g4, %o0)
stxa %g0, [%g4]ASI_STREAM
* first = head = tail = %g2 (base)
mov ASI_SPU_CWQ_FIRST, %g4
stxa %g2, [%g4]ASI_STREAM
mov ASI_SPU_CWQ_LAST, %g4
stxa %g6, [%g4]ASI_STREAM
mov ASI_SPU_CWQ_HEAD, %g4
stxa %g2, [%g4]ASI_STREAM
mov ASI_SPU_CWQ_TAIL, %g4
stxa %g2, [%g4]ASI_STREAM
* First and Last have been set. Now ready to
ldx [%g7 + MAU_STORE_IN_PROGR], %g4
brnz,pt %g4, .c_qconf_do_enable
stx %g4, [%g7 + MAU_ENABLE_CWQ]
mov ASI_SPU_CWQ_CSR_ENABLE, %g4
stxa %o0, [%g4]ASI_STREAM
mov NCS_QSTATE_CONFIGURED, %g1
st %g1, [%g3 + CWQ_QUEUE + CQ_STATE]
ldx [%g3 + CWQ_HANDLE], %o1
CWQ_HANDLE2ID_VERIFY(%o1, herr_inval, %g2)
GUEST_CID_GETCWQ(%g1, %g2, %g3)
brz,pn %g3, herr_noaccess
CWQ_LOCK_ENTER(%g3, %g5, %g1, %g6)
ld [%g3 + CWQ_QUEUE + CQ_BUSY], %g4
brnz,pn %g4, .c_qconf_wouldblock
* Clear any errors and disable CWQ,
* then do a synchronous load to wait
* for any outstanding ops.
MAU_LOCK_ENTER(%g7, %g1, %g4, %o0)
stx %g0, [%g7 + MAU_ENABLE_CWQ]
stxa %g0, [%g4]ASI_STREAM
mov ASI_SPU_CWQ_SYNC, %g4
ldxa [%g4]ASI_STREAM, %g0
mov NCS_QSTATE_UNCONFIGURED, %g1
st %g1, [%g3 + CWQ_QUEUE + CQ_STATE]
*-----------------------------------------------------------
* Function: ncs_settail_cwq(uint64_t qhandle, uint64_t new_tailoffset)
* %o0 - EOK (on success),
* EINVAL, ENORADDR (on failure)
*-----------------------------------------------------------
ENTRY_NP(ncs_settail_cwq)
CWQ_HANDLE2ID_VERIFY(%o0, herr_inval, %g2)
GUEST_CID_GETCWQ(%g1, %g2, %g3)
* Verify that we're on the CWQ that the
btst CWQ_CW_SIZE - 1, %o1
bnz,a,pn %xcc, herr_inval
mov %g1, %o0 ! %o0 = guest struct
ldx [%g3 + CWQ_QUEUE + CQ_BASE], %g1
ldx [%g3 + CWQ_QUEUE + CQ_LAST], %g2
CWQ_LOCK_ENTER(%g3, %o5, %g4, %g6)
stx %o1, [%g3 + CWQ_QUEUE + CQ_SCR1]
stx %o2, [%g3 + CWQ_QUEUE + CQ_SCR2]
stx %o3, [%g3 + CWQ_QUEUE + CQ_SCR3]
* Use the per-cwq assigned cpu as target
* for interrupts for this job.
ldx [%g3 + CWQ_QUEUE + CQ_DR_HV_OFFSET], %o2
ldx [%g3 + CWQ_QUEUE + CQ_CPU_PID], %o1
and %o1, NSTRANDS_PER_CWQ_MASK, %o1 ! hw-thread-id
sllx %o1, CW_STRAND_ID_SHIFT, %g4 ! prep for CW conrol reg
mov ASI_SPU_CWQ_TAIL, %g5
ldxa [%g5]ASI_STREAM, %g2
brz,a,pn %g2, .st_cwq_return
mov ASI_SPU_CWQ_FIRST, %g5
ldxa [%g5]ASI_STREAM, %g3
brz,a,pn %g3, .st_cwq_return
mov ASI_SPU_CWQ_LAST, %g5
ldxa [%g5]ASI_STREAM, %g5
brz,a,pn %g5, .st_cwq_return
!! %g4 = hw-thread-id shifted over for CTLBITS.
* %g5 = current CW that we're working on.
* %o2 = driver queue - HV queue offset.
be,a,pn %xcc, .st_cwq_trans_done
ldx [%g6 + CW_CTLBITS], %g6
sllx %g7, CW_SOB_SHIFT, %g7
bz,pn %xcc, .st_cwq_storectl
* Fill in the CW_STRAND_ID field with the
* physical hwthread-id that we're on.
mov CW_STRAND_ID_MASK, %g7
sllx %g7, CW_STRAND_ID_SHIFT, %g7
* Force the interrupt bit to be on
sllx %g7, CW_INTR_SHIFT, %g7
stx %g6, [%g5 + CW_CTLBITS]
setx CW_LENGTH_MASK, %g2, %g7
and %g6, %g7, %g7 ! %g7 = cw_length
srlx %g6, CW_HMAC_KEYLEN_SHIFT, %g6
and %g6, CW_HMAC_KEYLEN_MASK, %g6 ! %g6 = cw_hmac_keylen
* Source address should never be NULL.
ldx [%g2 + CW_SRC_ADDR], %g2
brz,a,pn %g2, .st_cwq_return
RA2PA_RANGE_CONV_UNK_SIZE(%o0, %g2, %g7, .st_cwq_noraddr, %o5, %o1)
stx %o1, [%g5 + CW_SRC_ADDR]
ldx [%g2 + CW_AUTH_KEY_ADDR], %g2
brz,pn %g2, .st_cwq_chk_authkey
RA2PA_RANGE_CONV_UNK_SIZE(%o0, %g2, %g6, .st_cwq_noraddr, %o5, %o1)
stx %o1, [%g5 + CW_AUTH_KEY_ADDR]
ldx [%g2 + CW_AUTH_IV_ADDR], %g2
brz,pn %g2, .st_cwq_chk_authiv
RA2PA_RANGE_CONV_UNK_SIZE(%o0, %g2, %o3, .st_cwq_noraddr, %o5, %o1)
stx %o1, [%g5 + CW_AUTH_IV_ADDR]
ldx [%g2 + CW_FINAL_AUTH_STATE_ADDR], %g2
brz,pn %g2, .st_cwq_chk_authst
mov MAX_AUTHSTATE_LENGTH, %o3
RA2PA_RANGE_CONV_UNK_SIZE(%o0, %g2, %o3, .st_cwq_noraddr, %o5, %o1)
stx %o1, [%g5 + CW_FINAL_AUTH_STATE_ADDR]
ldx [%g2 + CW_ENC_KEY_ADDR], %g2
brz,pn %g2, .st_cwq_chk_key
RA2PA_RANGE_CONV_UNK_SIZE(%o0, %g2, %g6, .st_cwq_noraddr, %o5, %o1)
stx %o1, [%g5 + CW_ENC_KEY_ADDR]
ldx [%g2 + CW_ENC_IV_ADDR], %g2
brz,pn %g2, .st_cwq_chk_iv
RA2PA_RANGE_CONV_UNK_SIZE(%o0, %g2, %o3, .st_cwq_noraddr, %o5, %o1)
stx %o1, [%g5 + CW_ENC_IV_ADDR]
ldx [%g2 + CW_DST_ADDR], %g2
brz,pn %g2, .st_cwq_chk_dst
RA2PA_RANGE_CONV_UNK_SIZE(%o0, %g2, %g7, .st_cwq_noraddr, %o5, %o1)
stx %o1, [%g5 + CW_DST_ADDR]
mov ASI_SPU_CWQ_LAST, %o1
ldxa [%o1]ASI_STREAM, %o1
mov %g5, %g6 ! save the last
add %g5, CWQ_CW_SIZE, %g5
cmp %g5, %o1 ! current == Last?
ba,pt %xcc, .st_cwq_trans
movgu %xcc, %g3, %g5 ! current = First
mov CW_EOB_MASK, %g5 ! force set the EOB bit on the last
sllx %g5, CW_EOB_SHIFT, %g5 ! CWQ submitted
* Update our local copy of the Head pointer.
* This will ensure that CQ_HEAD is non-zero
* for cwq_intr_getstate().
* If the cq_head is non-zero then that indicates
* it is effectively being managed via sethead,
* so we don't want/need to update it here.
ldx [%g3 + CWQ_QUEUE + CQ_HEAD], %g2
brnz,pt %g2, .st_cwq_tailonly
* Our first time installing a job on this queue,
* so go ahead and initialize cq_head.
mov ASI_SPU_CWQ_HEAD, %g4
ldxa [%g4]ASI_STREAM, %g2
stx %g2, [%g3 + CWQ_QUEUE + CQ_HEAD]
* Update HW's copy of Tail with new Tail.
mov ASI_SPU_CWQ_TAIL, %g5
stxa %g1, [%g5]ASI_STREAM
stx %g1, [%g3 + CWQ_QUEUE + CQ_TAIL]
ba,pt %xcc, .st_cwq_return
ldx [%g3 + CWQ_QUEUE + CQ_SCR1], %o1
ldx [%g3 + CWQ_QUEUE + CQ_SCR2], %o2
ldx [%g3 + CWQ_QUEUE + CQ_SCR3], %o3
SET_SIZE(ncs_settail_cwq)