; 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}
 ]

; Check that when new flags are added, the code is updated appropriately. This
; is necessary (particularly for CallASWI) because the code will claim that it
; supports all the flags up to CPUFeature_Max, which could lead to misreporting
; if the code for calculating that flag doesn't exist yet.
 [ CPUFeature_Max <> 65
   [ InKernel
        ! 1, "Please update this check value after adding support for the new feature flags, and remember to update CallASWI"
   |
        ; (we're in CallASWI)
        ! 1, "Please re-sync with Kernel.s.CPUFeatures to ensure CallASWI reports the correct flag values"
   ]
 ]

; Potential non-feature register flags:
; * 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
        DCD     0

CPUFeatures_Size * 12
 |
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 == 12
        MOV     r3, #0
        STR     r3, [r2]
        STR     r3, [r2, #4]
        STR     r3, [r2, #8]
        ; 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
        SetFeatureGE CP15_DFAR_DFSR_writable
        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
        SetFeature CP15_IFSR
        ; 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
        SetFeatureNE CP15_AIFSR
        SetFeatureNE CP15_ADFSR
        ; IFAR is only guaranteed to be present on ARMv6T2+
      [ InKernel :LAND: ("$Machine" = "RPi" :LOR: "$Machine" = "ARM11ZF")
        SetFeature CP15_IFAR            ; Cheeky hard-coding for Pi 1/ARM11ZF, which is pre-v6T2 but does have the register
      |
        SetFeatureNE CP15_IFAR          ; v7+ has it
        CMPEQ   r4, #ARMvF              ; v6 with feature registers?
        MRCEQ   p15, 0, r5, c0, c1, 0   ; ID_PFR0
        ANDEQ   r5, r5, #15<<4          ; State1
        CMPEQ   r5, #3<<4
        SetFeatureEQ CP15_IFAR          ; Thumb-2 implemented, i.e. v6T2
      ]
        ; Guess whether WFE does something useful by whether we're a multicore chip
        MRC     p15, 0, r5, c0, c0, 5   ; MPIDR
        MRCEQ   p15, 0, r6, c0, c0, 0   ; ARMv6: Register is optional, so compare value against MIDR to see if it's implemented. There's no multiprocessing extensions flag so assume the check against MIDR will be good enough.
        BICNE   r6, r5, #1<<31          ; ARMv7+: Register is mandatory, but useful WFE still not guaranteed. So check if multiprocessing extensions implemented (bit 31 set).
        TEQ     r5, r6
        SetFeatureNE WFE
        ; Detect instructions introduced by virtualisation extensions
        CMP     r4, #ARMvF
        MOVNE   r5, #0
        MRCEQ   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 == 12
        LDMIA   r0, {r0-r2}
        ASSERT  CPUFeature_Max >= 64
        MOV     r4, #-1
        MOV     r5, #-1
        ASSERT  CPUFeature_Max <= 96
        LDR     r6, =&ffffffff :SHR: (96 - CPUFeature_Max)
25
        MOV     r3, #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