diff mbox series

[v1,25/43] target/loongarch: Add LoongArch CSR instruction

Message ID 20220415094058.3584233-26-yangxiaojuan@loongson.cn (mailing list archive)
State New, archived
Headers show
Series Add LoongArch softmmu support | expand

Commit Message

Xiaojuan Yang April 15, 2022, 9:40 a.m. UTC
This includes:
- CSRRD
- CSRWR
- CSRXCHG

Signed-off-by: Xiaojuan Yang <yangxiaojuan@loongson.cn>
Signed-off-by: Song Gao <gaosong@loongson.cn>
---
 target/loongarch/cpu-csr.h                    |   2 +
 target/loongarch/csr_helper.c                 | 186 ++++++++++++++++++
 target/loongarch/disas.c                      |  15 ++
 target/loongarch/helper.h                     |   9 +
 .../insn_trans/trans_privileged.c.inc         | 177 +++++++++++++++++
 target/loongarch/insns.decode                 |  13 ++
 target/loongarch/meson.build                  |   1 +
 target/loongarch/translate.c                  |   5 +
 8 files changed, 408 insertions(+)
 create mode 100644 target/loongarch/csr_helper.c
 create mode 100644 target/loongarch/insn_trans/trans_privileged.c.inc

Comments

Richard Henderson April 16, 2022, 1:04 a.m. UTC | #1
On 4/15/22 02:40, Xiaojuan Yang wrote:
> +int cpu_csr_offset(unsigned csr_num);
...
> +static const uint64_t csr_offsets[] = {

There's no reason for this array to be uint64_t.
It really should match the function.

> +target_ulong helper_csrwr_estat(CPULoongArchState *env, target_ulong val)
> +{
> +    int64_t old_v = env->CSR_ESTAT;
> +
> +    /* Only IS[1:0] can be written */
> +    env->CSR_ESTAT = FIELD_DP64(env->CSR_ESTAT, CSR_ESTAT, IS, val & 0x3);
> +
> +    return old_v;
> +}

This is incorrect.  You're writing to all 13 bits of ESTAT.IS with the low 2 bits of val 
-- i.e. clearing bits [12:2].

You want to use: env->CSR_ESTAT = deposit64(env->CSR_ESTAT, 0, 2, val);

> +target_ulong helper_csrwr_asid(CPULoongArchState *env, target_ulong val)
> +{
> +    int64_t old_v = env->CSR_ASID;
> +
> +    /* Only ASID filed of CSR_ASID can be written */
> +    env->CSR_ASID = FIELD_DP64(env->CSR_ASID, CSR_ASID, ASID,
> +                               val & R_CSR_ASID_ASID_MASK);

You do not need to mask the 4th argument of FIELD_DP64 -- that happens as part of the 
deposit operation.

> +    if (old_v != val) {
> +        tlb_flush(env_cpu(env));
> +    }

You shouldn't be comparing val to old_v, but old_v to the updated CSR_ASID.

> +void  helper_csr_update(CPULoongArchState *env, target_ulong new_val,
> +                        target_ulong csr_offset)
> +{
> +    uint64_t *csr = (void *)env + csr_offset;
> +
> +    *csr = new_val;
> +}

This function should not exist.


> +static void output_r_csr(DisasContext *ctx, arg_r_csr *a,
> +                         const char *mnemonic)
> +{
> +    output(ctx, mnemonic, "r%d, %d", a->rd, a->csr);
> +}
> +
> +static void output_rr_csr(DisasContext *ctx, arg_rr_csr *a,
> +                          const char *mnemonic)
> +{
> +    output(ctx, mnemonic, "r%d, r%d, %d", a->rd, a->rj, a->csr);
> +}

While this is fine, it would be nicer to print the name of CSR, when valid.

> +static void gen_disas_exit(DisasContext *ctx)
> +{
> +    tcg_gen_movi_tl(cpu_pc, ctx->base.pc_next + 4);
> +    ctx->base.is_jmp = DISAS_EXIT;
> +}

Why this function, and not simply place the movi in loongarch_tr_tb_stop too?
Or even just put the tcg_gen_exit_tb() here, and set DISAS_NORETURN.
I would say: one way or the other but not this mix...

> +static bool trans_csrrd(DisasContext *ctx, arg_csrrd *a)
> +{
> +    TCGv dest = gpr_dst(ctx, a->rd, EXT_NONE);
> +
> +    if (check_plv(ctx)) {
> +        return false;
> +    }
> +
> +    unsigned csr_offset = cpu_csr_offset(a->csr);

This is incorrect -- csr_offset must be signed, 'int', to match cpu_csr_offset and the 
single negative value that exists within (CPUID).

> +static bool trans_csrwr(DisasContext *ctx, arg_csrwr *a)
> +{
> +    TCGv dest = gpr_dst(ctx, a->rd, EXT_NONE);
> +    TCGv src1 = gpr_src(ctx, a->rd, EXT_NONE);
> +
> +    if (check_plv(ctx) || ro_csr(a->csr)) {
> +        return false;
> +    }
> +
> +    unsigned csr_offset = cpu_csr_offset(a->csr);

Again, 'int'.

> +    if (csr_offset == 0) {
> +        /* CSR is undefined: write ignored. */
> +        return true;
> +    }
> +
> +    switch (a->csr) {
> +    case LOONGARCH_CSR_ESTAT:
> +        gen_helper_csrwr_estat(dest, cpu_env, src1);
> +        break;
> +    case LOONGARCH_CSR_ASID:
> +        gen_helper_csrwr_asid(dest, cpu_env, src1);
> +        gen_disas_exit(ctx);
> +        break;
> +    case LOONGARCH_CSR_TCFG:
> +        gen_helper_csrwr_tcfg(dest, cpu_env, src1);
> +        break;
> +    case LOONGARCH_CSR_TICLR:
> +        gen_helper_csrwr_ticlr(dest, cpu_env, src1);
> +        break;
> +    default:
> +        {
> +            TCGv temp = tcg_temp_new();
> +            tcg_gen_ld_tl(temp, cpu_env, csr_offset);
> +            tcg_gen_st_tl(src1, cpu_env, csr_offset);
> +            tcg_gen_mov_tl(dest, temp);
> +            tcg_temp_free(temp);
> +
> +            /* Cpu state may be changed, need exit */
> +            if ((a->csr == LOONGARCH_CSR_CRMD) ||
> +                (a->csr == LOONGARCH_CSR_EUEN)) {
> +                gen_disas_exit(ctx);
> +            }
> +        }
> +    }

I said before that you needed to split out the body of this function for re-use by csrxchg.

> +    tcg_gen_not_tl(t1, mask);
> +    tcg_gen_and_tl(t1, old_val, t1);

This is tcg_gen_andc_t1 (t1, old_val, mask).

> +    switch (a->csr) {
> +    case LOONGARCH_CSR_ESTAT:
> +        gen_helper_csrwr_estat(dest, cpu_env, new_val);
> +        break;
> +    case LOONGARCH_CSR_ASID:
> +        gen_helper_csrwr_asid(dest, cpu_env, new_val);
> +        break;
> +    case LOONGARCH_CSR_TCFG:
> +        gen_helper_csrwr_tcfg(dest, cpu_env, new_val);
> +        break;
> +    case LOONGARCH_CSR_TICLR:
> +        gen_helper_csrwr_ticlr(dest, cpu_env, new_val);
> +        break;
> +    default:
> +        tcg_gen_mov_tl(dest, old_val);
> +    }
> +
> +    gen_helper_csr_update(cpu_env, new_val, tcg_constant_tl(csr_offset));

Note that helper_csr_update is nothing more than the store to csr_offset.

> +
> +    if ((a->csr == LOONGARCH_CSR_ASID) || (a->csr == LOONGARCH_CSR_CRMD) ||
> +        (a->csr == LOONGARCH_CSR_EUEN)) {
> +        gen_disas_exit(ctx);
> +    }

Note that this list does not match the list in trans_csrwr.  This is *exactly* why I said 
that you should split out a function for use between csrwr and csrxchg.



r~
Xiaojuan Yang April 18, 2022, 12:38 p.m. UTC | #2
On 2022/4/16 上午9:04, Richard Henderson wrote:
>> +int cpu_csr_offset(unsigned csr_num);
> ...
>> +static const uint64_t csr_offsets[] = {
>
> There's no reason for this array to be uint64_t.
> It really should match the function. 
Yes,  we shoud do this.

If we use 'int', we may get a warning:
../target/loongarch/csr_helper.c:49:30: warning: overflow in implicit 
constant conversion [-Woverflow]
       [LOONGARCH_CSR_CPUID] = offsetof(CPUState, cpu_index)
                                                 ^~~~~~~~
How about use 'long'?  I had tested it no warning.

Thanks.
Xiaojuan
Richard Henderson April 18, 2022, 2:34 p.m. UTC | #3
On 4/18/22 05:38, yangxiaojuan wrote:
> 
> On 2022/4/16 上午9:04, Richard Henderson wrote:
>>> +int cpu_csr_offset(unsigned csr_num);
>> ...
>>> +static const uint64_t csr_offsets[] = {
>>
>> There's no reason for this array to be uint64_t.
>> It really should match the function. 
> Yes,  we shoud do this.
> 
> If we use 'int', we may get a warning:
> ../target/loongarch/csr_helper.c:49:30: warning: overflow in implicit constant conversion 
> [-Woverflow]
>        [LOONGARCH_CSR_CPUID] = offsetof(CPUState, cpu_index)
>                                                  ^~~~~~~~
> How about use 'long'?  I had tested it no warning.

That is because offsetof() result is size_t, which is unsigned, and a "negative" unsigned 
number is a large positive.  Cast each offsetof to int.


r~
Xiaojuan Yang April 19, 2022, 7:33 a.m. UTC | #4
On 2022/4/16 上午9:04, Richard Henderson wrote:
> On 4/15/22 02:40, Xiaojuan Yang wrote:
...
>> +void  helper_csr_update(CPULoongArchState *env, target_ulong new_val,
>> +                        target_ulong csr_offset)
>> +{
>> +    uint64_t *csr = (void *)env + csr_offset;
>> +
>> +    *csr = new_val;
>> +}
> This function should not exist
...
>> +    switch (a->csr) {
>> +    case LOONGARCH_CSR_ESTAT:
>> +        gen_helper_csrwr_estat(dest, cpu_env, new_val);
>> +        break;
>> +    case LOONGARCH_CSR_ASID:
>> +        gen_helper_csrwr_asid(dest, cpu_env, new_val);
>> +        break;
>> +    case LOONGARCH_CSR_TCFG:
>> +        gen_helper_csrwr_tcfg(dest, cpu_env, new_val);
>> +        break;
>> +    case LOONGARCH_CSR_TICLR:
>> +        gen_helper_csrwr_ticlr(dest, cpu_env, new_val);
>> +        break;
>> +    default:
>> +        tcg_gen_mov_tl(dest, old_val);
>> +    }
>> +
>> +    gen_helper_csr_update(cpu_env, new_val, 
>> tcg_constant_tl(csr_offset));
>
> Note that helper_csr_update is nothing more than the store to csr_offset.
On trans_csrxchg() , I am don't know how to use a TCGv value 'new_val 
'to update an uint64_t value "CSR_XXX",  So I use helper_csr_update(),

Thanks.
Xiaojuan
> r~
Richard Henderson April 19, 2022, 5:05 p.m. UTC | #5
On 4/19/22 00:33, yangxiaojuan wrote:
> 
> On 2022/4/16 上午9:04, Richard Henderson wrote:
>> On 4/15/22 02:40, Xiaojuan Yang wrote:
> ...
>>> +void  helper_csr_update(CPULoongArchState *env, target_ulong new_val,
>>> +                        target_ulong csr_offset)
>>> +{
>>> +    uint64_t *csr = (void *)env + csr_offset;
>>> +
>>> +    *csr = new_val;
>>> +}
>> This function should not exist
> ...
>>> +    switch (a->csr) {
>>> +    case LOONGARCH_CSR_ESTAT:
>>> +        gen_helper_csrwr_estat(dest, cpu_env, new_val);
>>> +        break;
>>> +    case LOONGARCH_CSR_ASID:
>>> +        gen_helper_csrwr_asid(dest, cpu_env, new_val);
>>> +        break;
>>> +    case LOONGARCH_CSR_TCFG:
>>> +        gen_helper_csrwr_tcfg(dest, cpu_env, new_val);
>>> +        break;
>>> +    case LOONGARCH_CSR_TICLR:
>>> +        gen_helper_csrwr_ticlr(dest, cpu_env, new_val);
>>> +        break;
>>> +    default:
>>> +        tcg_gen_mov_tl(dest, old_val);
>>> +    }
>>> +
>>> +    gen_helper_csr_update(cpu_env, new_val, tcg_constant_tl(csr_offset));
>>
>> Note that helper_csr_update is nothing more than the store to csr_offset.
> On trans_csrxchg() , I am don't know how to use a TCGv value 'new_val 'to update an 
> uint64_t value "CSR_XXX",  So I use helper_csr_update(),

You'd use a store, just like you were already doing in trans_csrwr.

But here's how I'd improve this.  For avoidance of doubt, all of this would go in 
trans_priviledged.c.inc -- there's no use of csr_offsets[] outside of that file.


r~

----


typedef void (*GenCSRRead)(TCGv dest, TCGv_ptr env);
typedef void (*GenCSRWrite)(TCGv dest, TCGv_ptr env, TCGv src);

typedef struct {
     int offset;
     int flags;
     GenCSRRead readfn;
     GenCSRWrite writefn;
} CSRInfo;

enum {
     CSRFL_READONLY  = (1 << 0),
     CSRFL_EXITTB    = (1 << 1),
     CSRFL_IO        = (1 << 2),
};

#define CSR_OFF_FUNCS(NAME, FL, RD, WR) \
     [LOONGARCH_CSR_##NAME] = {                             \
         .offset = offsetof(CPULoongArchState, CSR_##NAME), \
         .flags = FL, .readfn = RD, .writefn = WR           \
     }
#define CSR_OFF_FLAGS(NAME, FL) \
     CSR_OFF_FUNCS(NAME, FL, NULL, NULL)
#define CSR_OFF(NAME) \
     CSR_OFF_FLAGS(NAME, 0)

static const CSRInfo csr_info[] = {
     CSR_OFF(CRMD),
     CSR_OFF_FLAGS(CPUID, CSRFL_READONLY),
     CSR_OFF_FUNCS(TCFG, CSRFL_IO, NULL, gen_helper_csrwr_tcfg),
     CSR_OFF_FUNCS(TVAL, CSRFL_READONLY | CSRFL_IO, gen_helper_csrrd_tval, NULL),
     CSR_OFF_FUNCS(TICLR, CSRFL_IO, NULL, gen_helper_csrwr_ticlr),
     CSR_OFF_FUNCS(ESTAT, CSRFL_EXITTB, NULL, gen_helper_csrwr_estat),
     ...
};

static const CSRInfo *get_csr(unsigned csr_num)
{
     const CSRInfo *csr;
     if (csr_num < ARRAY_SIZE(csr_info)) {
         return NULL;
     }
     csr = &csr_info[csr_num];
     if (csr->offset == 0) {
         return NULL; /* undefined */
     }
     return csr;
}

static bool check_csr_flags(DisasContext *ctx, const CSRInfo *csr, bool write)
{
     if ((info->flags & CSRFL_READONLY) && write) {
         return false;
     }
     if ((info->flags & CSRFL_IO) &&
         (tb_cflags(ctx->base.tb) & CF_USE_ICOUNT)) {
         gen_io_start();
         ctx->base.is_jmp = DISAS_EXIT_UPDATE;
     } else if ((info->flags & CSRFL_EXITTB) && write) {
         ctx->base.is_jmp = DISAS_EXIT_UPDATE;
     }
     return true;
}

static bool trans_csrrd(DisasContext *ctx, arg_csrrd *a)
{
     TCGv dest;
     const CSRInfo *csr;

     if (check_plv(ctx)) {
         return false;
     }
     csr = get_csr(a->csr);
     if (csr == NULL) {
         /* CSR is undefined: read as 0 */
         dest = tcg_constant_tl(0);
     } else {
         check_csr_flags(ctx, csr, false);
         dest = gpr_dst(ctx, a->rd, EXT_NONE);
         if (csr->readfn) {
             csr_readfn(dest, cpu_env);
         } else {
             tcg_gen_ld_tl(dest, cpu_env, csr->offset);
         }
     }
     gen_set_gpr(a->rd, dest, EXT_NONE);
     return true;
}

static bool trans_csrwr(DisasContext *ctx, arg_csrwr *a)
{
     TCGv dest, src1;
     const CSRInfo *csr;

     if (check_plv(ctx)) {
         return false;
     }
     csr = get_csr_info(a->csr);
     if (csr == NULL) {
         /* CSR is undefined: write ignored, read old value as 0. */
         gen_set_gpr(a->rd, tcg_constant_tl(0), EXT_NONE);
         return true;
     }
     if (!check_csr_flags(ctx, csr, true)) {
         /* CSR is readonly: trap. */
         return false;
     }
     src1 = gpr_src(ctx, a->rd, EXT_NONE);
     if (csr->writefn) {
         dest = gpr_dst(ctx, a->rd, EXT_NONE);
         csr->writefn(dest, cpu_env, src1);
     } else {
          dest = temp_new(ctx);
          tcg_gen_ld_tl(dest, cpu_env, csr->offset);
          tcg_gen_st_tl(src1, cpu_env, csr->offset);
     }
     gen_set_gpr(a->rd, dest, EXT_NONE);
     return true;
}

static bool trans_csrxchg(DisasContext *ctx, arg_csrxchg *a)
{
     TCGv src1, mask, oldv, newv, temp;
     const CSRInfo *csr;

     if (check_plv(ctx)) {
         return false;
     }
     csr = get_csr_info(a->csr);
     if (csr == NULL) {
         /* CSR is undefined: write ignored, read old value as 0. */
         gen_set_gpr(a->rd, tcg_constant_tl(0), EXT_NONE);
         return true;
     }
     if (!check_csr_flags(ctx, csr, true)) {
         /* CSR is readonly: trap. */
         return false;
     }

     /* So far only readonly csrs have readfn. */
     assert(csr->readfn == NULL);

     src1 = gpr_src(ctx, a->rd, EXT_NONE);
     mask = gpr_src(ctx, a->rj, EXT_NONE);
     oldv = tcg_temp_new();
     newv = tcg_temp_new();
     temp = tcg_temp_new();

     tcg_gen_ld_tl(oldv, cpu_env, csr->offset);
     tcg_gen_and_tl(newv, src1, mask);
     tcg_gen_andc_tl(temp, oldv, mask);
     tcg_gen_or_tl(newv, newv, temp);

     if (csr->writefn) {
         csr->writefn(oldv, cpu_env, newv);
     } else {
         tcg_gen_st_tl(newv, cpu_env, csr->offset);
     }
     gen_set_gpr(a->rd, oldv, EXT_NONE);

     tcg_temp_free(temp);
     tcg_temp_free(newv);
     tcg_temp_free(oldv);
     return true;
}

and then in loongarch_tr_tb_stop:

     case DISAS_EXIT_UPDATE:
         tcg_gen_movi_tl(cpu_pc, ctx->base.pc_next);

         /* fall through */
     case DISAS_EXIT:

         tcg_gen_exit_tb(NULL, 0);

         break;
Xiaojuan Yang April 21, 2022, 9:15 a.m. UTC | #6
On 2022/4/20 上午1:05, Richard Henderson wrote:
> You'd use a store, just like you were already doing in trans_csrwr.
>
> But here's how I'd improve this.  For avoidance of doubt, all of this 
> would go in trans_priviledged.c.inc -- there's no use of csr_offsets[] 
> outside of that file.

Thanks you very much,  I had tested this with bios,  it worked well, and 
I have two questions.

1. CSRFL_IO,   how to use it.   I don't understand  CPUState options  
'can_do_Io',
2./* fall through */,   this may have warning,  should we care about this?

Thanks.
Xiaojuan
> r~
>
> ----
>
>
> typedef void (*GenCSRRead)(TCGv dest, TCGv_ptr env);
> typedef void (*GenCSRWrite)(TCGv dest, TCGv_ptr env, TCGv src);
>
> typedef struct {
>     int offset;
>     int flags;
>     GenCSRRead readfn;
>     GenCSRWrite writefn;
> } CSRInfo;
>
> enum {
>     CSRFL_READONLY  = (1 << 0),
>     CSRFL_EXITTB    = (1 << 1),
>     CSRFL_IO        = (1 << 2),
> };
>
> #define CSR_OFF_FUNCS(NAME, FL, RD, WR) \
>     [LOONGARCH_CSR_##NAME] = {                             \
>         .offset = offsetof(CPULoongArchState, CSR_##NAME), \
>         .flags = FL, .readfn = RD, .writefn = WR           \
>     }
> #define CSR_OFF_FLAGS(NAME, FL) \
>     CSR_OFF_FUNCS(NAME, FL, NULL, NULL)
> #define CSR_OFF(NAME) \
>     CSR_OFF_FLAGS(NAME, 0)
>
> static const CSRInfo csr_info[] = {
>     CSR_OFF(CRMD),
>     CSR_OFF_FLAGS(CPUID, CSRFL_READONLY),
>     CSR_OFF_FUNCS(TCFG, CSRFL_IO, NULL, gen_helper_csrwr_tcfg),
>     CSR_OFF_FUNCS(TVAL, CSRFL_READONLY | CSRFL_IO, 
> gen_helper_csrrd_tval, NULL),
>     CSR_OFF_FUNCS(TICLR, CSRFL_IO, NULL, gen_helper_csrwr_ticlr),
>     CSR_OFF_FUNCS(ESTAT, CSRFL_EXITTB, NULL, gen_helper_csrwr_estat),
>     ...
> };
>
> static const CSRInfo *get_csr(unsigned csr_num)
> {
>     const CSRInfo *csr;
>     if (csr_num < ARRAY_SIZE(csr_info)) {
>         return NULL;
>     }
>     csr = &csr_info[csr_num];
>     if (csr->offset == 0) {
>         return NULL; /* undefined */
>     }
>     return csr;
> }
>
> static bool check_csr_flags(DisasContext *ctx, const CSRInfo *csr, 
> bool write)
> {
>     if ((info->flags & CSRFL_READONLY) && write) {
>         return false;
>     }
>     if ((info->flags & CSRFL_IO) &&
>         (tb_cflags(ctx->base.tb) & CF_USE_ICOUNT)) {
>         gen_io_start();
>         ctx->base.is_jmp = DISAS_EXIT_UPDATE;
>     } else if ((info->flags & CSRFL_EXITTB) && write) {
>         ctx->base.is_jmp = DISAS_EXIT_UPDATE;
>     }
>     return true;
> }
>
> static bool trans_csrrd(DisasContext *ctx, arg_csrrd *a)
> {
>     TCGv dest;
>     const CSRInfo *csr;
>
>     if (check_plv(ctx)) {
>         return false;
>     }
>     csr = get_csr(a->csr);
>     if (csr == NULL) {
>         /* CSR is undefined: read as 0 */
>         dest = tcg_constant_tl(0);
>     } else {
>         check_csr_flags(ctx, csr, false);
>         dest = gpr_dst(ctx, a->rd, EXT_NONE);
>         if (csr->readfn) {
>             csr_readfn(dest, cpu_env);
>         } else {
>             tcg_gen_ld_tl(dest, cpu_env, csr->offset);
>         }
>     }
>     gen_set_gpr(a->rd, dest, EXT_NONE);
>     return true;
> }
>
> static bool trans_csrwr(DisasContext *ctx, arg_csrwr *a)
> {
>     TCGv dest, src1;
>     const CSRInfo *csr;
>
>     if (check_plv(ctx)) {
>         return false;
>     }
>     csr = get_csr_info(a->csr);
>     if (csr == NULL) {
>         /* CSR is undefined: write ignored, read old value as 0. */
>         gen_set_gpr(a->rd, tcg_constant_tl(0), EXT_NONE);
>         return true;
>     }
>     if (!check_csr_flags(ctx, csr, true)) {
>         /* CSR is readonly: trap. */
>         return false;
>     }
>     src1 = gpr_src(ctx, a->rd, EXT_NONE);
>     if (csr->writefn) {
>         dest = gpr_dst(ctx, a->rd, EXT_NONE);
>         csr->writefn(dest, cpu_env, src1);
>     } else {
>          dest = temp_new(ctx);
>          tcg_gen_ld_tl(dest, cpu_env, csr->offset);
>          tcg_gen_st_tl(src1, cpu_env, csr->offset);
>     }
>     gen_set_gpr(a->rd, dest, EXT_NONE);
>     return true;
> }
>
> static bool trans_csrxchg(DisasContext *ctx, arg_csrxchg *a)
> {
>     TCGv src1, mask, oldv, newv, temp;
>     const CSRInfo *csr;
>
>     if (check_plv(ctx)) {
>         return false;
>     }
>     csr = get_csr_info(a->csr);
>     if (csr == NULL) {
>         /* CSR is undefined: write ignored, read old value as 0. */
>         gen_set_gpr(a->rd, tcg_constant_tl(0), EXT_NONE);
>         return true;
>     }
>     if (!check_csr_flags(ctx, csr, true)) {
>         /* CSR is readonly: trap. */
>         return false;
>     }
>
>     /* So far only readonly csrs have readfn. */
>     assert(csr->readfn == NULL);
>
>     src1 = gpr_src(ctx, a->rd, EXT_NONE);
>     mask = gpr_src(ctx, a->rj, EXT_NONE);
>     oldv = tcg_temp_new();
>     newv = tcg_temp_new();
>     temp = tcg_temp_new();
>
>     tcg_gen_ld_tl(oldv, cpu_env, csr->offset);
>     tcg_gen_and_tl(newv, src1, mask);
>     tcg_gen_andc_tl(temp, oldv, mask);
>     tcg_gen_or_tl(newv, newv, temp);
>
>     if (csr->writefn) {
>         csr->writefn(oldv, cpu_env, newv);
>     } else {
>         tcg_gen_st_tl(newv, cpu_env, csr->offset);
>     }
>     gen_set_gpr(a->rd, oldv, EXT_NONE);
>
>     tcg_temp_free(temp);
>     tcg_temp_free(newv);
>     tcg_temp_free(oldv);
>     return true;
> }
>
> and then in loongarch_tr_tb_stop:
>
>     case DISAS_EXIT_UPDATE:
>         tcg_gen_movi_tl(cpu_pc, ctx->base.pc_next);
>
>         /* fall through */
>     case DISAS_EXIT:
>
>         tcg_gen_exit_tb(NULL, 0);
>
>         break;
Richard Henderson April 21, 2022, 3:59 p.m. UTC | #7
On 4/21/22 02:15, yangxiaojuan wrote:
> 
> On 2022/4/20 上午1:05, Richard Henderson wrote:
>> You'd use a store, just like you were already doing in trans_csrwr.
>>
>> But here's how I'd improve this.  For avoidance of doubt, all of this would go in 
>> trans_priviledged.c.inc -- there's no use of csr_offsets[] outside of that file.
> 
> Thanks you very much,  I had tested this with bios,  it worked well, and I have two 
> questions.
> 
> 1. CSRFL_IO,   how to use it.   I don't understand  CPUState options 'can_do_Io',

Whenever a cpu touches a device, like a timer or clock, must have io flag set.  Missing 
this flag should result in assertion failures when running with -icount.

> 2./* fall through */,   this may have warning,  should we care about this?

It should not have a warning, as the comment itself should suppress that.  For more 
complex cases we also have QEMU_FALLTHROUGH.


r~
diff mbox series

Patch

diff --git a/target/loongarch/cpu-csr.h b/target/loongarch/cpu-csr.h
index 5c89605d1a..7a96ec7b6a 100644
--- a/target/loongarch/cpu-csr.h
+++ b/target/loongarch/cpu-csr.h
@@ -198,4 +198,6 @@  FIELD(CSR_DBG, ECODE, 16, 6)
 #define LOONGARCH_CSR_DERA           0x501 /* Debug era */
 #define LOONGARCH_CSR_DSAVE          0x502 /* Debug save */
 
+int cpu_csr_offset(unsigned csr_num);
+
 #endif /* LOONGARCH_CPU_CSR_H */
diff --git a/target/loongarch/csr_helper.c b/target/loongarch/csr_helper.c
new file mode 100644
index 0000000000..0884e85cb0
--- /dev/null
+++ b/target/loongarch/csr_helper.c
@@ -0,0 +1,186 @@ 
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * LoongArch emulation helpers for CSRs
+ *
+ * Copyright (c) 2021 Loongson Technology Corporation Limited
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/main-loop.h"
+#include "cpu.h"
+#include "internals.h"
+#include "qemu/host-utils.h"
+#include "exec/helper-proto.h"
+#include "exec/exec-all.h"
+#include "exec/cpu_ldst.h"
+#include "hw/irq.h"
+#include "cpu-csr.h"
+#include "hw/loongarch/loongarch.h"
+#include "tcg/tcg-ldst.h"
+
+#define CSR_OFF(X)  \
+           [LOONGARCH_CSR_##X] = offsetof(CPULoongArchState, CSR_##X)
+#define CSR_OFF_ARRAY(X, N)  \
+           [LOONGARCH_CSR_##X(N)] = offsetof(CPULoongArchState, CSR_##X[N])
+
+static const uint64_t csr_offsets[] = {
+     CSR_OFF(CRMD),
+     CSR_OFF(PRMD),
+     CSR_OFF(EUEN),
+     CSR_OFF(MISC),
+     CSR_OFF(ECFG),
+     CSR_OFF(ESTAT),
+     CSR_OFF(ERA),
+     CSR_OFF(BADV),
+     CSR_OFF(BADI),
+     CSR_OFF(EENTRY),
+     CSR_OFF(TLBIDX),
+     CSR_OFF(TLBEHI),
+     CSR_OFF(TLBELO0),
+     CSR_OFF(TLBELO1),
+     CSR_OFF(ASID),
+     CSR_OFF(PGDL),
+     CSR_OFF(PGDH),
+     CSR_OFF(PGD),
+     CSR_OFF(PWCL),
+     CSR_OFF(PWCH),
+     CSR_OFF(STLBPS),
+     CSR_OFF(RVACFG),
+     [LOONGARCH_CSR_CPUID] = offsetof(CPUState, cpu_index)
+                             - offsetof(ArchCPU, env),
+     CSR_OFF(PRCFG1),
+     CSR_OFF(PRCFG2),
+     CSR_OFF(PRCFG3),
+     CSR_OFF_ARRAY(SAVE, 0),
+     CSR_OFF_ARRAY(SAVE, 1),
+     CSR_OFF_ARRAY(SAVE, 2),
+     CSR_OFF_ARRAY(SAVE, 3),
+     CSR_OFF_ARRAY(SAVE, 4),
+     CSR_OFF_ARRAY(SAVE, 5),
+     CSR_OFF_ARRAY(SAVE, 6),
+     CSR_OFF_ARRAY(SAVE, 7),
+     CSR_OFF_ARRAY(SAVE, 8),
+     CSR_OFF_ARRAY(SAVE, 9),
+     CSR_OFF_ARRAY(SAVE, 10),
+     CSR_OFF_ARRAY(SAVE, 11),
+     CSR_OFF_ARRAY(SAVE, 12),
+     CSR_OFF_ARRAY(SAVE, 13),
+     CSR_OFF_ARRAY(SAVE, 14),
+     CSR_OFF_ARRAY(SAVE, 15),
+     CSR_OFF(TID),
+     CSR_OFF(TCFG),
+     CSR_OFF(TVAL),
+     CSR_OFF(CNTC),
+     CSR_OFF(TICLR),
+     CSR_OFF(LLBCTL),
+     CSR_OFF(IMPCTL1),
+     CSR_OFF(IMPCTL2),
+     CSR_OFF(TLBRENTRY),
+     CSR_OFF(TLBRBADV),
+     CSR_OFF(TLBRERA),
+     CSR_OFF(TLBRSAVE),
+     CSR_OFF(TLBRELO0),
+     CSR_OFF(TLBRELO1),
+     CSR_OFF(TLBREHI),
+     CSR_OFF(TLBRPRMD),
+     CSR_OFF(MERRCTL),
+     CSR_OFF(MERRINFO1),
+     CSR_OFF(MERRINFO2),
+     CSR_OFF(MERRENTRY),
+     CSR_OFF(MERRERA),
+     CSR_OFF(MERRSAVE),
+     CSR_OFF(CTAG),
+     CSR_OFF_ARRAY(DMW, 0),
+     CSR_OFF_ARRAY(DMW, 1),
+     CSR_OFF_ARRAY(DMW, 2),
+     CSR_OFF_ARRAY(DMW, 3),
+     CSR_OFF(DBG),
+     CSR_OFF(DERA),
+     CSR_OFF(DSAVE),
+};
+
+int cpu_csr_offset(unsigned csr_num)
+{
+    if (csr_num < ARRAY_SIZE(csr_offsets)) {
+        return csr_offsets[csr_num];
+    }
+    return 0;
+}
+
+target_ulong helper_csrrd_pgd(CPULoongArchState *env)
+{
+    int64_t v;
+
+    if (env->CSR_TLBRERA & 0x1) {
+        v = env->CSR_TLBRBADV;
+    } else {
+        v = env->CSR_BADV;
+    }
+
+    if ((v >> 63) & 0x1) {
+        v = env->CSR_PGDH;
+    } else {
+        v = env->CSR_PGDL;
+    }
+
+    return v;
+}
+
+target_ulong helper_csrrd_tval(CPULoongArchState *env)
+{
+    LoongArchCPU *cpu = LOONGARCH_CPU(env_cpu(env));
+
+    return cpu_loongarch_get_constant_timer_ticks(cpu);
+}
+
+target_ulong helper_csrwr_estat(CPULoongArchState *env, target_ulong val)
+{
+    int64_t old_v = env->CSR_ESTAT;
+
+    /* Only IS[1:0] can be written */
+    env->CSR_ESTAT = FIELD_DP64(env->CSR_ESTAT, CSR_ESTAT, IS, val & 0x3);
+
+    return old_v;
+}
+
+target_ulong helper_csrwr_asid(CPULoongArchState *env, target_ulong val)
+{
+    int64_t old_v = env->CSR_ASID;
+
+    /* Only ASID filed of CSR_ASID can be written */
+    env->CSR_ASID = FIELD_DP64(env->CSR_ASID, CSR_ASID, ASID,
+                               val & R_CSR_ASID_ASID_MASK);
+    if (old_v != val) {
+        tlb_flush(env_cpu(env));
+    }
+    return old_v;
+}
+
+target_ulong helper_csrwr_tcfg(CPULoongArchState *env, target_ulong val)
+{
+    LoongArchCPU *cpu = LOONGARCH_CPU(env_cpu(env));
+    int64_t old_v = env->CSR_TCFG;
+
+    cpu_loongarch_store_constant_timer_config(cpu, val);
+
+    return old_v;
+}
+
+target_ulong helper_csrwr_ticlr(CPULoongArchState *env, target_ulong val)
+{
+    LoongArchCPU *cpu = LOONGARCH_CPU(env_cpu(env));
+    int64_t old_v = 0;
+
+    if (val & 0x1) {
+        loongarch_cpu_set_irq(cpu, IRQ_TIMER, 0);
+    }
+    return old_v;
+}
+
+void  helper_csr_update(CPULoongArchState *env, target_ulong new_val,
+                        target_ulong csr_offset)
+{
+    uint64_t *csr = (void *)env + csr_offset;
+
+    *csr = new_val;
+}
diff --git a/target/loongarch/disas.c b/target/loongarch/disas.c
index 9454ebb8e9..db0e0c73fe 100644
--- a/target/loongarch/disas.c
+++ b/target/loongarch/disas.c
@@ -205,6 +205,18 @@  static void output_rr_offs(DisasContext *ctx, arg_rr_offs *a,
            a->rd, a->offs, ctx->pc + a->offs);
 }
 
+static void output_r_csr(DisasContext *ctx, arg_r_csr *a,
+                         const char *mnemonic)
+{
+    output(ctx, mnemonic, "r%d, %d", a->rd, a->csr);
+}
+
+static void output_rr_csr(DisasContext *ctx, arg_rr_csr *a,
+                          const char *mnemonic)
+{
+    output(ctx, mnemonic, "r%d, r%d, %d", a->rd, a->rj, a->csr);
+}
+
 #define INSN(insn, type)                                    \
 static bool trans_##insn(DisasContext *ctx, arg_##type * a) \
 {                                                           \
@@ -514,6 +526,9 @@  INSN(blt,          rr_offs)
 INSN(bge,          rr_offs)
 INSN(bltu,         rr_offs)
 INSN(bgeu,         rr_offs)
+INSN(csrrd,        r_csr)
+INSN(csrwr,        r_csr)
+INSN(csrxchg,      rr_csr)
 
 #define output_fcmp(C, PREFIX, SUFFIX)                                         \
 {                                                                              \
diff --git a/target/loongarch/helper.h b/target/loongarch/helper.h
index da1a2bced7..bd2cb3a9c5 100644
--- a/target/loongarch/helper.h
+++ b/target/loongarch/helper.h
@@ -92,3 +92,12 @@  DEF_HELPER_2(frint_s, i64, env, i64)
 DEF_HELPER_2(frint_d, i64, env, i64)
 
 DEF_HELPER_FLAGS_2(set_rounding_mode, TCG_CALL_NO_RWG, void, env, i32)
+
+/* CSRs helper */
+DEF_HELPER_1(csrrd_pgd, i64, env)
+DEF_HELPER_1(csrrd_tval, i64, env)
+DEF_HELPER_2(csrwr_estat, i64, env, tl)
+DEF_HELPER_2(csrwr_asid, i64, env, tl)
+DEF_HELPER_2(csrwr_tcfg, i64, env, tl)
+DEF_HELPER_2(csrwr_ticlr, i64, env, tl)
+DEF_HELPER_3(csr_update, void, env, tl, i64)
diff --git a/target/loongarch/insn_trans/trans_privileged.c.inc b/target/loongarch/insn_trans/trans_privileged.c.inc
new file mode 100644
index 0000000000..ba111779c2
--- /dev/null
+++ b/target/loongarch/insn_trans/trans_privileged.c.inc
@@ -0,0 +1,177 @@ 
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2021 Loongson Technology Corporation Limited
+ *
+ * LoongArch translation routines for the privileged instructions.
+ */
+
+#include "cpu-csr.h"
+
+static void gen_disas_exit(DisasContext *ctx)
+{
+    tcg_gen_movi_tl(cpu_pc, ctx->base.pc_next + 4);
+    ctx->base.is_jmp = DISAS_EXIT;
+}
+
+static bool check_plv(DisasContext *ctx)
+{
+    if (ctx->base.tb->flags == MMU_USER_IDX) {
+        generate_exception(ctx, EXCCODE_IPE);
+        return true;
+    }
+    return false;
+}
+
+static bool ro_csr(int csr_num)
+{
+    /*
+     * For now qemu does not support any features of the MISC
+     * bits yet treat as a RO CSR.
+     */
+    if ((csr_num == LOONGARCH_CSR_BADI) ||
+        (csr_num == LOONGARCH_CSR_CPUID) ||
+        (csr_num == LOONGARCH_CSR_PRCFG1) ||
+        (csr_num == LOONGARCH_CSR_PRCFG2) ||
+        (csr_num == LOONGARCH_CSR_PRCFG3) ||
+        (csr_num == LOONGARCH_CSR_PGD) ||
+        (csr_num == LOONGARCH_CSR_TVAL) ||
+        (csr_num == LOONGARCH_CSR_MISC)) {
+        return true;
+    }
+
+    return false;
+}
+
+static bool trans_csrrd(DisasContext *ctx, arg_csrrd *a)
+{
+    TCGv dest = gpr_dst(ctx, a->rd, EXT_NONE);
+
+    if (check_plv(ctx)) {
+        return false;
+    }
+
+    unsigned csr_offset = cpu_csr_offset(a->csr);
+    if (csr_offset == 0) {
+        /* CSR is undefined: read as 0 */
+        dest = tcg_constant_tl(0);
+        return true;
+    }
+
+    switch (a->csr) {
+    case LOONGARCH_CSR_PGD:
+        gen_helper_csrrd_pgd(dest, cpu_env);
+        break;
+    case LOONGARCH_CSR_TVAL:
+        gen_helper_csrrd_tval(dest, cpu_env);
+        break;
+    default:
+        tcg_gen_ld_tl(dest, cpu_env, csr_offset);
+    }
+    return true;
+}
+
+static bool trans_csrwr(DisasContext *ctx, arg_csrwr *a)
+{
+    TCGv dest = gpr_dst(ctx, a->rd, EXT_NONE);
+    TCGv src1 = gpr_src(ctx, a->rd, EXT_NONE);
+
+    if (check_plv(ctx) || ro_csr(a->csr)) {
+        return false;
+    }
+
+    unsigned csr_offset = cpu_csr_offset(a->csr);
+    if (csr_offset == 0) {
+        /* CSR is undefined: write ignored. */
+        return true;
+    }
+
+    switch (a->csr) {
+    case LOONGARCH_CSR_ESTAT:
+        gen_helper_csrwr_estat(dest, cpu_env, src1);
+        break;
+    case LOONGARCH_CSR_ASID:
+        gen_helper_csrwr_asid(dest, cpu_env, src1);
+        gen_disas_exit(ctx);
+        break;
+    case LOONGARCH_CSR_TCFG:
+        gen_helper_csrwr_tcfg(dest, cpu_env, src1);
+        break;
+    case LOONGARCH_CSR_TICLR:
+        gen_helper_csrwr_ticlr(dest, cpu_env, src1);
+        break;
+    default:
+        {
+            TCGv temp = tcg_temp_new();
+            tcg_gen_ld_tl(temp, cpu_env, csr_offset);
+            tcg_gen_st_tl(src1, cpu_env, csr_offset);
+            tcg_gen_mov_tl(dest, temp);
+            tcg_temp_free(temp);
+
+            /* Cpu state may be changed, need exit */
+            if ((a->csr == LOONGARCH_CSR_CRMD) ||
+                (a->csr == LOONGARCH_CSR_EUEN)) {
+                gen_disas_exit(ctx);
+            }
+        }
+    }
+    return true;
+}
+
+static bool trans_csrxchg(DisasContext *ctx, arg_csrxchg *a)
+{
+    TCGv dest = gpr_dst(ctx, a->rd, EXT_NONE);
+    TCGv src1 = gpr_src(ctx, a->rd, EXT_NONE);
+    TCGv mask = gpr_src(ctx, a->rj, EXT_NONE);
+
+    if (check_plv(ctx) || ro_csr(a->csr)) {
+        return false;
+    }
+
+    unsigned csr_offset = cpu_csr_offset(a->csr);
+    if (csr_offset == 0) {
+        /* CSR is undefined: write ignored */
+        return true;
+    }
+
+    TCGv old_val = tcg_temp_new();
+    TCGv new_val = tcg_temp_new();
+    TCGv t0 = tcg_temp_new();
+    TCGv t1 = tcg_temp_new();
+
+    tcg_gen_ld_tl(old_val, cpu_env, csr_offset);
+    tcg_gen_and_tl(t0, src1, mask);
+    tcg_gen_not_tl(t1, mask);
+    tcg_gen_and_tl(t1, old_val, t1);
+    tcg_gen_or_tl(new_val, t1, t0);
+
+    switch (a->csr) {
+    case LOONGARCH_CSR_ESTAT:
+        gen_helper_csrwr_estat(dest, cpu_env, new_val);
+        break;
+    case LOONGARCH_CSR_ASID:
+        gen_helper_csrwr_asid(dest, cpu_env, new_val);
+        break;
+    case LOONGARCH_CSR_TCFG:
+        gen_helper_csrwr_tcfg(dest, cpu_env, new_val);
+        break;
+    case LOONGARCH_CSR_TICLR:
+        gen_helper_csrwr_ticlr(dest, cpu_env, new_val);
+        break;
+    default:
+        tcg_gen_mov_tl(dest, old_val);
+    }
+
+    gen_helper_csr_update(cpu_env, new_val, tcg_constant_tl(csr_offset));
+
+    if ((a->csr == LOONGARCH_CSR_ASID) || (a->csr == LOONGARCH_CSR_CRMD) ||
+        (a->csr == LOONGARCH_CSR_EUEN)) {
+        gen_disas_exit(ctx);
+    }
+
+    tcg_temp_free(t1);
+    tcg_temp_free(t0);
+    tcg_temp_free(new_val);
+    tcg_temp_free(old_val);
+
+    return true;
+}
diff --git a/target/loongarch/insns.decode b/target/loongarch/insns.decode
index 9b293dfdf9..43005ca283 100644
--- a/target/loongarch/insns.decode
+++ b/target/loongarch/insns.decode
@@ -45,6 +45,8 @@ 
 &c_offs       cj offs
 &offs         offs
 &rr_offs      rj rd offs
+&r_csr        rd csr
+&rr_csr       rd rj csr
 
 #
 # Formats
@@ -85,6 +87,8 @@ 
 @c_offs21      .... .. ................ .. cj:3 .....    &c_offs      offs=%offs21
 @offs26            .... .. ..........................    &offs        offs=%offs26
 @rr_offs16         .... .. ................ rj:5 rd:5    &rr_offs     offs=%offs16
+@r_csr                    .... .... csr:14 ..... rd:5    &r_csr
+@rr_csr                    .... .... csr:14 rj:5 rd:5    &rr_csr
 
 #
 # Fixed point arithmetic operation instruction
@@ -437,3 +441,12 @@  blt             0110 00 ................ ..... .....     @rr_offs16
 bge             0110 01 ................ ..... .....     @rr_offs16
 bltu            0110 10 ................ ..... .....     @rr_offs16
 bgeu            0110 11 ................ ..... .....     @rr_offs16
+
+#
+# Core instructions
+#
+{
+  csrrd             0000 0100 .............. 00000 .....     @r_csr
+  csrwr             0000 0100 .............. 00001 .....     @r_csr
+  csrxchg           0000 0100 .............. ..... .....     @rr_csr
+}
diff --git a/target/loongarch/meson.build b/target/loongarch/meson.build
index 04e15ba1e3..d11829a6cc 100644
--- a/target/loongarch/meson.build
+++ b/target/loongarch/meson.build
@@ -19,6 +19,7 @@  loongarch_softmmu_ss.add(files(
   'machine.c',
   'tlb_helper.c',
   'constant_timer.c',
+  'csr_helper.c',
 ))
 
 loongarch_ss.add_all(when: 'CONFIG_TCG', if_true: [loongarch_tcg_ss])
diff --git a/target/loongarch/translate.c b/target/loongarch/translate.c
index f39ebe7967..c1cac2f006 100644
--- a/target/loongarch/translate.c
+++ b/target/loongarch/translate.c
@@ -26,6 +26,7 @@  TCGv_i32 cpu_fcsr0;
 TCGv_i64 cpu_fpr[32];
 
 #define DISAS_STOP       DISAS_TARGET_0
+#define DISAS_EXIT       DISAS_TARGET_1
 
 static inline int plus_1(DisasContext *ctx, int x)
 {
@@ -172,6 +173,7 @@  static void gen_set_gpr(int reg_num, TCGv t, DisasExtend dst_ext)
 #include "insn_trans/trans_fmov.c.inc"
 #include "insn_trans/trans_fmemory.c.inc"
 #include "insn_trans/trans_branch.c.inc"
+#include "insn_trans/trans_privileged.c.inc"
 
 static void loongarch_tr_translate_insn(DisasContextBase *dcbase, CPUState *cs)
 {
@@ -209,6 +211,9 @@  static void loongarch_tr_tb_stop(DisasContextBase *dcbase, CPUState *cs)
         break;
     case DISAS_NORETURN:
         break;
+    case DISAS_EXIT:
+        tcg_gen_exit_tb(NULL, 0);
+        break;
     default:
         g_assert_not_reached();
     }