; Copyright 2016 Castle Technology Ltd
;
; Licensed under the Apache License, Version 2.0 (the "License");
; you may not use this file except in compliance with the License.
; You may obtain a copy of the License at
;
;     http://www.apache.org/licenses/LICENSE-2.0
;
; Unless required by applicable law or agreed to in writing, software
; distributed under the License is distributed on an "AS IS" BASIS,
; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
; See the License for the specific language governing permissions and
; limitations under the License.
;

; This file is duplicated in the kernel sources and CallASWI. Please try and
; keep them in sync!

 [ :LNOT: :DEF: InKernel
        GBLL    InKernel
InKernel SETL   {TRUE}
 ]

; Non-feature register flags:
; * ROM & CPU alignment modes?
; * NOP after banked LDM?
; * StrongARM MSR bug?
; * ARM2 TEQP bug?


FeatureOp_BIC * 0 << 3
FeatureOp_AND * 1 << 3
FeatureOp_OR  * 2 << 3

        MACRO
        CPUFeature $register, $field, $minval, $feature, $op
        ASSERT  ($register < 8)
        ASSERT  ($field :AND: 28) == $field
        ASSERT  ($minval < 16)
      [ "$op" <> ""
        DCB     $register + $op
      |
        DCB     $register + FeatureOp_OR
      ]
        DCB     $field
        DCB     $minval
        DCB     CPUFeature_$feature
        MEND

FeatureRegsTable
        CPUFeature      0, 24, 2, UDIV_SDIV
        CPUFeature      0, 20, 1, BKPT
        CPUFeature      0, 08, 1, BFC_BFI_SBFX_UBFX
        CPUFeature      0, 04, 1, CLZ
        CPUFeature      0, 00, 1, SWP_SWPB

        CPUFeature      1, 24, 1, BX
        CPUFeature      1, 24, 2, BLX
        CPUFeature      1, 24, 3, Interworking_MOV_pc
        CPUFeature      1, 20, 1, MOVW_MOVT
        CPUFeature      1, 12, 1, SXTB_SXTH_UXTB_UXTH
        CPUFeature      1, 12, 2, SXTAB_SXTAH_UXTAB_UXTAH
        CPUFeature      1, 12, 2, SXTB16_SXTAB16_UXTB16_UXTAB16
        CPUFeature      1, 08, 1, SRS_RFE_CPS

        CPUFeature      2, 28, 1, REV_REV16_REVSH
        CPUFeature      2, 28, 2, RBIT
        CPUFeature      2, 24, 1, MRS_MSR
        CPUFeature      2, 20, 1, UMULL_UMLAL
        CPUFeature      2, 20, 2, UMAAL
        CPUFeature      2, 16, 1, SMULL_SMLAL
        CPUFeature      2, 16, 2, SMLAxy_SMLALxy_SMLAWy_SMULxy_SMULWy
        CPUFeature      2, 16, 2, PSR_Q_bit
        CPUFeature      2, 16, 3, SMLAlDx_SMLSlDx_SMMLAr_SMMLSr_SMMULr_SMUADx_SMUSDx
        CPUFeature      2, 12, 2, MLS
        CPUFeature      2, 08, 0, LDM_STM_noninterruptible
        CPUFeature      2, 08, 1, LDM_STM_noninterruptible, FeatureOp_BIC
        CPUFeature      2, 08, 1, LDM_STM_restartable
        CPUFeature      2, 08, 2, LDM_STM_restartable, FeatureOp_BIC
        CPUFeature      2, 08, 2, LDM_STM_continuable
        CPUFeature      2, 04, 1, PLD
        CPUFeature      2, 04, 3, PLI
        CPUFeature      2, 04, 4, PLDW
        CPUFeature      2, 00, 1, LDRD_STRD
        CPUFeature      2, 00, 2, LDAx_STLx

        CPUFeature      3, 24, 1, NOP_hints
        CPUFeature      3, 12, 1, LDREX_STREX
        CPUFeature      3, 12, 1, CLREX_LDREXB_LDREXH_STREXB_STREXH
        CPUFeature      4, 20, 3, CLREX_LDREXB_LDREXH_STREXB_STREXH, FeatureOp_AND
        CPUFeature      3, 12, 2, CLREX_LDREXB_LDREXH_STREXB_STREXH
        CPUFeature      3, 12, 2, LDREXD_STREXD
        CPUFeature      3, 04, 1, SSAT_USAT
        CPUFeature      3, 04, 1, PSR_Q_bit
        CPUFeature      3, 04, 3, PKHxy_xADD16_xADD8_xASX_xSUB16_xSUB8_xSAX_SEL
        CPUFeature      3, 04, 3, PSR_GE_bits
        CPUFeature      3, 04, 3, SXTB16_SXTAB16_UXTB16_UXTAB16, FeatureOp_AND
        CPUFeature      3, 00, 1, QADD_QDADD_QDSUB_QSUB
        CPUFeature      3, 00, 1, PSR_Q_bit

        CPUFeature      4, 28, 1, SWP_SWPB_uniproc
        CPUFeature      0, 00, 1, SWP_SWPB_uniproc, FeatureOp_BIC
        CPUFeature      4, 16, 1, DMB_DSB_ISB
        CPUFeature      4, 12, 1, SMC
        CPUFeature      4, 00, 2, LDRHT_LDRSBT_LDRSHT_STRHT

        CPUFeature      5, 16, 1, CRC32B_CRC32H_CRC32W_CRC32CB_CRC32CH_CRC32CW
        CPUFeature      5, 12, 1, SHA256H_SHA256H2_SHA256SU0_SHA256SU1
        CPUFeature      5, 08, 1, SHA1C_SHA1P_SHA1M_SHA1H_SHA1SU0_SHA1SU1
        CPUFeature      5, 04, 1, AESE_AESD_AESMC_AESIMC
        CPUFeature      5, 00, 1, SEVL
FeatureRegsTableEnd

 [ :LNOT: InKernel
ARMv3    *       0
ARMv4    *       1
ARMv4T   *       2
ARMv5    *       3
ARMv5T   *       4
ARMv5TE  *       5
ARMv5TEJ *       6
ARMv6    *       7
ARMvF    *      &F

; int Init_ARMarch(void)
; Returns architecture, as above in r2. Also EQ if ARMv3, NE if ARMv4 or later.
Init_ARMarch ROUT
        Entry   "ip"
        MRC     p15, 0, ip, c0, c0, 0
        ANDS    r2, ip, #&0000F000
        EXIT    EQ                              ; ARM 3 or ARM 6
        TEQ     r2, #&00007000
        BNE     %FT20
        TST     ip, #&00800000                  ; ARM 7 - check for Thumb
        MOVNE   r2, #ARMv4T
        MOVEQ   r2, #ARMv3
        EXIT
20      ANDS    r2, ip, #&000F0000              ; post-ARM 7
        MOV     r2, r2, LSR #16
        EXIT

CPUFeatures
        DCD     0
        DCD     0

CPUFeatures_Size * 8
 |
CPUFeatures_Size * ?CPUFeatures
 ]


        MACRO
        SetFeature$cc $feature
        LDR$cc.B lr, [r2, #CPUFeature_$feature :SHR: 3]
        ORR$cc   lr, lr, #1 :SHL: (CPUFeature_$feature :AND: 7)
        STR$cc.B lr, [r2, #CPUFeature_$feature :SHR: 3]
        MEND

        ASSERT  CPUFeatures_Size * 8 >= CPUFeature_Max

CalcCPUFeatures ROUT
        ; In:
        ; r0 -> instruction set feature registers
        ; r1 = number of registers
        ; r2 = CPU architecture field from MIDR, or -1 if ARM3 or older
        ; Out:
        ; CPUFeatures populated
        Entry   "r2-r8"
      [ InKernel
        LDR     r2, =ZeroPage+CPUFeatures
      |
        ADR     r2, CPUFeatures
      ]
        ASSERT  CPUFeatures_Size == 8
        MOV     r3, #0
        STR     r3, [r2]
        STR     r3, [r2, #4]
        ; Fill in the features defined by the instruction set feature registers
        ADR     r3, FeatureRegsTable
        ADR     r4, FeatureRegsTableEnd
        MOV     r8, #1
10
        LDRB    r5, [r3], #4
        AND     r6, r5, #7
        CMP     r6, r1
        LDRLT   r6, [r0, r6, LSL #2]
        MOVGE   r6, #0                  ; n.b. can't skip registers which are zero, we must still process them
        LDRB    r7, [r3, #-3]
        MOV     r6, r6, LSR r7
        LDRB    r7, [r3, #-2]
        AND     r6, r6, #15
        CMP     r6, r7
        LDRB    r6, [r3, #-1]
        BIC     r5, r5, #7
        LDRB    r7, [r2, r6, LSR #3]
        AND     lr, r6, #7
        ASSERT  FeatureOp_BIC < FeatureOp_AND
        ASSERT  FeatureOp_AND < FeatureOp_OR
        ; If we failed the test, massage the flags so that AND becomes BIC, and BIC and OR become a no-op (AND)
        EORLT   r5, r5, #FeatureOp_BIC :EOR: FeatureOp_AND
        BICLT   r5, r5, #FeatureOp_OR
        CMP     r5, #FeatureOp_AND
        BICLT   r7, r7, r8, LSL lr
        ORRGT   r7, r7, r8, LSL lr
        STRNEB  r7, [r2, r6, LSR #3]
        CMP     r3, r4
        BNE     %BT10
        ; Fill in extra features which we care about
      [ InKernel
        LDR     r3, =ZeroPage
        LDR     r3, [r3, #ProcessorFlags]
      |
        Push    "r0-r1"
        MOV     r0, #OSPlatformFeatures_ReadCodeFeatures
        SWI     XOS_PlatformFeatures
        MOVVC   r3, r0
        MOVVS   r3, #0
        Pull    "r0-r1"
      ]
        TST     r3, #CPUFlag_No26bitMode ; Trust the current kernel about whether 26bit modes are supported, rather than trying to work it out ourselves
        SetFeatureEQ TEQP
        FRAMLDR r4,,r2
        CMP     r4, #ARMv4
        SetFeatureGE LDRSB
        SetFeatureGE SYS_mode
        SetFeatureLE MULS_flag_corruption
    [ InKernel ; Kernel init is too early for SWIs, just check target machine type instead
      [ "$Machine" <> "IOMD"
        SetFeatureGE LDRH_LDRSH_STRH
      ]
    |
        BNE     %FT35
        ; ARMv4. For simplicity assume that if IOMD is present LDRH/STRH won't work.
        Push    "r0-r4"
        MOV     r0, #2
        SWI     XOS_ReadSysInfo
        AND     r0, r0, #&ff00
        CMP     r0, #&ff00
        Pull    "r0-r4"
35
        SetFeatureGE LDRH_LDRSH_STRH    ; ARMv5+, or ARMv4 without IOMD
    ]
        ; Be careful with ARMv6 comparisons, GT condition could still be ARMv6
        CMP     r4, #ARMv6
        SetFeatureLT MUL_Rd_Rn_restriction
        SetFeatureLT LDR_STR_Rd_Rn_restriction
        SetFeatureLE Rotated_loads
        BLT     %FT40
        SetFeature Unaligned_loads
        ; Look at the cache type register to work out whether this is ARMv6 or ARMv7+
        MRC     p15, 0, r5, c0, c0, 1   ; Cache type register
        TST     r5, #1<<31              ; EQ = ARMv6, NE = ARMv7+
        SetFeatureEQ Rotated_loads
        ; Guess whether WFE does something useful by whether we're a multicore chip (i.e. MPIDR is implemented)
        MRC     p15, 0, r5, c0, c0, 5   ; MPIDR
        MRC     p15, 0, r6, c0, c0, 0   ; MIDR
        TEQ     r5, r6
        SetFeatureNE WFE
        ; Detect instructions introduced by virtualisation extensions
        MRC     p15, 0, r5, c0, c1, 1   ; ID_PFR1
        TST     r5, #15<<12             ; ARMv7
        SetFeatureNE HVC
        TSTEQ   r5, #15<<24             ; ARMv8
        SetFeatureNE ERET_MSR_MRS_banked
40
        EXIT

ReadCPUFeatures ROUT
        ; Out:
        ; CPUFeatures populated
        Entry   "r0-r2"
      [ InKernel
        LDR     r2, =ZeroPage
        LDRB    r2, [r2, #ProcessorArch]
      |
        ; Check if the MIDR should be present (do a dummy MRS)
        MOV     r0, #0
        MRS     r0, CPSR
        TEQ     r0, #0
        ADRNE   lr, %FT20
        BNE     Init_ARMarch
        ; ARM2, ARM250 or ARM3
        ; Use the information that was collected by the main CallASWI code to work out which it is
        MOV     r2, #-1
        ADR     r0, Features_ARM3
        LDR     r1, nomrc
        CMP     r1, #0
        ADRNE   r0, Features_ARM250
        LDRNE   r1, noswp
        CMPNE   r1, #0
        ADRNE   r0, Features_ARM2
        B       %FT30
      ]
20
        CMP     r2, #ARMvF
        BGE     %FT50
        ; Old architecture, use fake ISAR registers
        ADR     r0, Features_ARMv3
        ASSERT  Features_ARMv4 = Features_ARMv3 + 20
        ASSERT  Features_ARMv4T = Features_ARMv4 + 20
        ASSERT  Features_ARMv5T = Features_ARMv4T + 20
        ASSERT  Features_ARMv5TE = Features_ARMv5T + 20
        ASSERT  Features_ARMv5TEJ = Features_ARMv5TE + 20
        ASSERT  Features_ARMv6 = Features_ARMv5TEJ + 20
        ADD     r0, r0, r2, LSL #4
        ADD     r0, r0, r2, LSL #2
        ; n.b. not dealing with plain ARMv5 properly
        CMP     r2, #ARMv5
        SUBGE   r0, r0, #20
30
        MOV     r1, #5
        BL      CalcCPUFeatures
        EXIT
50
        ; Read the ISAR registers directly
        MRC     p15, 0, lr, c0, c2, 5
        MRC     p15, 0, r1, c0, c2, 4
        MRC     p15, 0, r0, c0, c2, 3
        STMDB   sp!, {r0-r1,lr}
        MRC     p15, 0, lr, c0, c2, 2
        MRC     p15, 0, r1, c0, c2, 1
        MRC     p15, 0, r0, c0, c2, 0
        STMDB   sp!, {r0-r1,lr}
        MOV     r0, sp
        MOV     r1, #6
        BL      CalcCPUFeatures
        ADD     sp, sp, #24
        EXIT

PlatFeatSWI_ReadCPUFeatures ROUT
        ; In: r1 = >=0: flag bit to read
        ;           <0: return bit masks for page NOT r1
        ;     LR stacked
        ; Out: Reading single flag:
        ;        r0 = 0/1 flag bit value, or -1 if not known
        ;      Reading bit masks:
        ;        r0-r3 = flag values
        ;        r4-r7 = validity masks
        ASSERT  CPUFeature_Max <= 128
        CMP     r1, #-1
        BEQ     %FT20
        BLT     %FT40
        ; Read single flag
        CMP     r1, #CPUFeature_Max
        MOVGE   r0, #-1
        BGE     %FT30
      [ InKernel
        LDR     r0, =ZeroPage+CPUFeatures
      |
        ADR     r0, CPUFeatures
      ]
        AND     lr, r1, #7
        LDRB    r0, [r0, r1, LSR #3]
        MOV     r0, r0, LSR lr
        AND     r0, r0, #1
        B       %FT30
20
        ; Read page 0 of flags (the only page)
      [ InKernel
        LDR     r0, =ZeroPage+CPUFeatures
      |
        ADR     r0, CPUFeatures
      ]
        ASSERT  CPUFeatures_Size == 8
        LDMIA   r0, {r0-r1}
        ASSERT  CPUFeature_Max >= 32
        MOV     r4, #-1
        ASSERT  CPUFeature_Max <= 64
        LDR     r5, =&ffffffff :SHR: (64 - CPUFeature_Max)
25
        MOV     r2, #0
        MOV     r3, #0
        MOV     r6, #0
        MOV     r7, #0
30
        Pull    "lr"
        B       SLVK

40
        ; Read unimplemented page of flags
        MOV     r0, #0
        MOV     r1, #0
        MOV     r4, #0
        MOV     r5, #0
        B       %BT25

; Fake ISAR registers for old architectures
; Thumb instruction details might not be entirely correct, but we don't really care about that

 [ :LNOT: InKernel
Features_ARM2
        DCD     &00000000
        DCD     &00000010
        DCD     &00001000
        DCD     &00000100
        DCD     &00000141

Features_ARM250
        DCD     &00000001
        DCD     &00000010
        DCD     &00001000
        DCD     &00000100
        DCD     &00000141

Features_ARM3
        DCD     &00010001
        DCD     &00000010
        DCD     &00001000
        DCD     &00000100
        DCD     &00000141
 ]

Features_ARMv3
        DCD     &00010001
        DCD     &00000010
        DCD     &01001000
        DCD     &00000100
        DCD     &00000141

Features_ARMv4
        DCD     &00010001
        DCD     &00000010
        DCD     &01111000
        DCD     &00000100
        DCD     &00000141

Features_ARMv4T
        DCD     &00010001
        DCD     &01000010
        DCD     &01111000
        DCD     &00000100
        DCD     &00000141

Features_ARMv5T
        DCD     &00120011
        DCD     &02000010
        DCD     &01111000
        DCD     &00000100
        DCD     &00000141

Features_ARMv5TE
        DCD     &00130011
        DCD     &02000010
        DCD     &01121011
        DCD     &00000101
        DCD     &00000141

Features_ARMv5TEJ
        DCD     &00130011
        DCD     &12000010
        DCD     &01121011
        DCD     &00000101
        DCD     &00000141

Features_ARMv6
        DCD     &00140011
        DCD     &12002111
        DCD     &11231011
        DCD     &00001131
        DCD     &00000141

; Assume any ARMv6 chip with extensions will use the CPUID scheme

        LTORG

        END