Commit 1f6b12f7 authored by Peter Maydell's avatar Peter Maydell

Merge remote-tracking branch 'remotes/mwalle/tags/lm32-fixes/20140204' into staging

target-lm32: fixes

# gpg: Signature made Tue 04 Feb 2014 18:47:56 GMT using DSA key ID 3F98A378
# gpg: Can't check signature: public key not found

* remotes/mwalle/tags/lm32-fixes/20140204:
  hw/lm32: print error if cpu model is not found
  target-lm32: stop VM on illegal or unknown instruction
  lm32_sys: dump cpu state if test case fails
  lm32_sys: print test result on stderr
  target-lm32: add breakpoint/watchpoint support
  target-lm32: move model features to LM32CPU
  target-lm32: kill cpu_abort() calls
  milkymist-vgafb: swap pixel data in source buffer
  lm32_uart/lm32_juart: use qemu_chr_fe_write_all()
  milkymist-uart: use qemu_chr_fe_write_all() instead of qemu_chr_fe_write()
  tests: lm32: new rule for single test cases
  lm32_sys: increase test case name length limit
Signed-off-by: default avatarPeter Maydell <peter.maydell@linaro.org>
parents 3ea3bd62 f41152bd
......@@ -75,7 +75,7 @@ void lm32_juart_set_jtx(DeviceState *d, uint32_t jtx)
s->jtx = jtx;
if (s->chr) {
qemu_chr_fe_write(s->chr, &ch, 1);
qemu_chr_fe_write_all(s->chr, &ch, 1);
}
}
......
......@@ -177,7 +177,7 @@ static void uart_write(void *opaque, hwaddr addr,
switch (addr) {
case R_RXTX:
if (s->chr) {
qemu_chr_fe_write(s->chr, &ch, 1);
qemu_chr_fe_write_all(s->chr, &ch, 1);
}
break;
case R_IER:
......
......@@ -124,7 +124,7 @@ static void uart_write(void *opaque, hwaddr addr, uint64_t value,
switch (addr) {
case R_RXTX:
if (s->chr) {
qemu_chr_fe_write(s->chr, &ch, 1);
qemu_chr_fe_write_all(s->chr, &ch, 1);
}
s->regs[R_STAT] |= STAT_TX_EVT;
break;
......
......@@ -61,7 +61,7 @@ static void glue(draw_line_, BITS)(void *opaque, uint8_t *d, const uint8_t *s,
uint8_t r, g, b;
while (width--) {
memcpy(&rgb565, s, sizeof(rgb565));
rgb565 = lduw_be_p(s);
r = ((rgb565 >> 11) & 0x1f) << 3;
g = ((rgb565 >> 5) & 0x3f) << 2;
b = ((rgb565 >> 0) & 0x1f) << 3;
......
......@@ -101,6 +101,11 @@ static void lm32_evr_init(QEMUMachineInitArgs *args)
cpu_model = "lm32-full";
}
cpu = cpu_lm32_init(cpu_model);
if (cpu == NULL) {
fprintf(stderr, "qemu: unable to find CPU '%s'\n", cpu_model);
exit(1);
}
env = &cpu->env;
reset_info->cpu = cpu;
......@@ -198,6 +203,11 @@ static void lm32_uclinux_init(QEMUMachineInitArgs *args)
cpu_model = "lm32-full";
}
cpu = cpu_lm32_init(cpu_model);
if (cpu == NULL) {
fprintf(stderr, "qemu: unable to find CPU '%s'\n", cpu_model);
exit(1);
}
env = &cpu->env;
reset_info->cpu = cpu;
......
......@@ -108,6 +108,11 @@ milkymist_init(QEMUMachineInitArgs *args)
cpu_model = "lm32-full";
}
cpu = cpu_lm32_init(cpu_model);
if (cpu == NULL) {
fprintf(stderr, "qemu: unable to find CPU '%s'\n", cpu_model);
exit(1);
}
env = &cpu->env;
reset_info->cpu = cpu;
......
......@@ -42,7 +42,7 @@ enum {
R_MAX
};
#define MAX_TESTNAME_LEN 16
#define MAX_TESTNAME_LEN 32
#define TYPE_LM32_SYS "lm32-sys"
#define LM32_SYS(obj) OBJECT_CHECK(LM32SysState, (obj), TYPE_LM32_SYS)
......@@ -80,7 +80,11 @@ static void sys_write(void *opaque, hwaddr addr,
case R_PASSFAIL:
s->regs[addr] = value;
testname = (char *)s->testname;
qemu_log("TC %-16s %s\n", testname, (value) ? "FAILED" : "OK");
fprintf(stderr, "TC %-*s %s\n", MAX_TESTNAME_LEN,
testname, (value) ? "FAILED" : "OK");
if (value) {
cpu_dump_state(qemu_get_cpu(0), stderr, fprintf, 0);
}
break;
case R_TESTNAME:
s->regs[addr] = value;
......
* disassembler (lm32-dis.c)
* linux-user emulation
* native bp/wp emulation (?)
......@@ -60,6 +60,12 @@ typedef struct LM32CPU {
/*< public >*/
CPULM32State env;
uint32_t revision;
uint8_t num_interrupts;
uint8_t num_breakpoints;
uint8_t num_watchpoints;
uint32_t features;
} LM32CPU;
static inline LM32CPU *lm32_env_get_cpu(CPULM32State *env)
......
......@@ -29,6 +29,87 @@ static void lm32_cpu_set_pc(CPUState *cs, vaddr value)
cpu->env.pc = value;
}
/* Sort alphabetically by type name. */
static gint lm32_cpu_list_compare(gconstpointer a, gconstpointer b)
{
ObjectClass *class_a = (ObjectClass *)a;
ObjectClass *class_b = (ObjectClass *)b;
const char *name_a, *name_b;
name_a = object_class_get_name(class_a);
name_b = object_class_get_name(class_b);
return strcmp(name_a, name_b);
}
static void lm32_cpu_list_entry(gpointer data, gpointer user_data)
{
ObjectClass *oc = data;
CPUListState *s = user_data;
const char *typename = object_class_get_name(oc);
char *name;
name = g_strndup(typename, strlen(typename) - strlen("-" TYPE_LM32_CPU));
(*s->cpu_fprintf)(s->file, " %s\n", name);
g_free(name);
}
void lm32_cpu_list(FILE *f, fprintf_function cpu_fprintf)
{
CPUListState s = {
.file = f,
.cpu_fprintf = cpu_fprintf,
};
GSList *list;
list = object_class_get_list(TYPE_LM32_CPU, false);
list = g_slist_sort(list, lm32_cpu_list_compare);
(*cpu_fprintf)(f, "Available CPUs:\n");
g_slist_foreach(list, lm32_cpu_list_entry, &s);
g_slist_free(list);
}
static void lm32_cpu_init_cfg_reg(LM32CPU *cpu)
{
CPULM32State *env = &cpu->env;
uint32_t cfg = 0;
if (cpu->features & LM32_FEATURE_MULTIPLY) {
cfg |= CFG_M;
}
if (cpu->features & LM32_FEATURE_DIVIDE) {
cfg |= CFG_D;
}
if (cpu->features & LM32_FEATURE_SHIFT) {
cfg |= CFG_S;
}
if (cpu->features & LM32_FEATURE_SIGN_EXTEND) {
cfg |= CFG_X;
}
if (cpu->features & LM32_FEATURE_I_CACHE) {
cfg |= CFG_IC;
}
if (cpu->features & LM32_FEATURE_D_CACHE) {
cfg |= CFG_DC;
}
if (cpu->features & LM32_FEATURE_CYCLE_COUNT) {
cfg |= CFG_CC;
}
cfg |= (cpu->num_interrupts << CFG_INT_SHIFT);
cfg |= (cpu->num_breakpoints << CFG_BP_SHIFT);
cfg |= (cpu->num_watchpoints << CFG_WP_SHIFT);
cfg |= (cpu->revision << CFG_REV_SHIFT);
env->cfg = cfg;
}
/* CPUClass::reset() */
static void lm32_cpu_reset(CPUState *s)
{
......@@ -41,6 +122,7 @@ static void lm32_cpu_reset(CPUState *s)
/* reset cpu state */
memset(env, 0, offsetof(CPULM32State, breakpoints));
lm32_cpu_init_cfg_reg(cpu);
tlb_flush(env, 1);
}
......@@ -71,7 +153,93 @@ static void lm32_cpu_initfn(Object *obj)
if (tcg_enabled() && !tcg_initialized) {
tcg_initialized = true;
lm32_translate_init();
cpu_set_debug_excp_handler(lm32_debug_excp_handler);
}
}
static void lm32_basic_cpu_initfn(Object *obj)
{
LM32CPU *cpu = LM32_CPU(obj);
cpu->revision = 3;
cpu->num_interrupts = 32;
cpu->num_breakpoints = 4;
cpu->num_watchpoints = 4;
cpu->features = LM32_FEATURE_SHIFT
| LM32_FEATURE_SIGN_EXTEND
| LM32_FEATURE_CYCLE_COUNT;
}
static void lm32_standard_cpu_initfn(Object *obj)
{
LM32CPU *cpu = LM32_CPU(obj);
cpu->revision = 3;
cpu->num_interrupts = 32;
cpu->num_breakpoints = 4;
cpu->num_watchpoints = 4;
cpu->features = LM32_FEATURE_MULTIPLY
| LM32_FEATURE_DIVIDE
| LM32_FEATURE_SHIFT
| LM32_FEATURE_SIGN_EXTEND
| LM32_FEATURE_I_CACHE
| LM32_FEATURE_CYCLE_COUNT;
}
static void lm32_full_cpu_initfn(Object *obj)
{
LM32CPU *cpu = LM32_CPU(obj);
cpu->revision = 3;
cpu->num_interrupts = 32;
cpu->num_breakpoints = 4;
cpu->num_watchpoints = 4;
cpu->features = LM32_FEATURE_MULTIPLY
| LM32_FEATURE_DIVIDE
| LM32_FEATURE_SHIFT
| LM32_FEATURE_SIGN_EXTEND
| LM32_FEATURE_I_CACHE
| LM32_FEATURE_D_CACHE
| LM32_FEATURE_CYCLE_COUNT;
}
typedef struct LM32CPUInfo {
const char *name;
void (*initfn)(Object *obj);
} LM32CPUInfo;
static const LM32CPUInfo lm32_cpus[] = {
{
.name = "lm32-basic",
.initfn = lm32_basic_cpu_initfn,
},
{
.name = "lm32-standard",
.initfn = lm32_standard_cpu_initfn,
},
{
.name = "lm32-full",
.initfn = lm32_full_cpu_initfn,
},
};
static ObjectClass *lm32_cpu_class_by_name(const char *cpu_model)
{
ObjectClass *oc;
char *typename;
if (cpu_model == NULL) {
return NULL;
}
typename = g_strdup_printf("%s-" TYPE_LM32_CPU, cpu_model);
oc = object_class_by_name(typename);
g_free(typename);
if (oc != NULL && (!object_class_dynamic_cast(oc, TYPE_LM32_CPU) ||
object_class_is_abstract(oc))) {
oc = NULL;
}
return oc;
}
static void lm32_cpu_class_init(ObjectClass *oc, void *data)
......@@ -86,6 +254,7 @@ static void lm32_cpu_class_init(ObjectClass *oc, void *data)
lcc->parent_reset = cc->reset;
cc->reset = lm32_cpu_reset;
cc->class_by_name = lm32_cpu_class_by_name;
cc->do_interrupt = lm32_cpu_do_interrupt;
cc->dump_state = lm32_cpu_dump_state;
cc->set_pc = lm32_cpu_set_pc;
......@@ -98,19 +267,36 @@ static void lm32_cpu_class_init(ObjectClass *oc, void *data)
cc->gdb_num_core_regs = 32 + 7;
}
static void lm32_register_cpu_type(const LM32CPUInfo *info)
{
TypeInfo type_info = {
.parent = TYPE_LM32_CPU,
.instance_init = info->initfn,
};
type_info.name = g_strdup_printf("%s-" TYPE_LM32_CPU, info->name);
type_register(&type_info);
g_free((void *)type_info.name);
}
static const TypeInfo lm32_cpu_type_info = {
.name = TYPE_LM32_CPU,
.parent = TYPE_CPU,
.instance_size = sizeof(LM32CPU),
.instance_init = lm32_cpu_initfn,
.abstract = false,
.abstract = true,
.class_size = sizeof(LM32CPUClass),
.class_init = lm32_cpu_class_init,
};
static void lm32_cpu_register_types(void)
{
int i;
type_register_static(&lm32_cpu_type_info);
for (i = 0; i < ARRAY_SIZE(lm32_cpus); i++) {
lm32_register_cpu_type(&lm32_cpus[i]);
}
}
type_init(lm32_cpu_register_types)
......@@ -163,8 +163,11 @@ struct CPULM32State {
/* debug registers */
uint32_t dc; /* debug control */
uint32_t bp[4]; /* breakpoint addresses */
uint32_t wp[4]; /* watchpoint addresses */
uint32_t bp[4]; /* breakpoints */
uint32_t wp[4]; /* watchpoints */
CPUBreakpoint * cpu_breakpoint[4];
CPUWatchpoint * cpu_watchpoint[4];
CPU_COMMON
......@@ -177,25 +180,42 @@ struct CPULM32State {
DeviceState *juart_state;
/* processor core features */
uint32_t features;
uint32_t flags;
uint8_t num_bps;
uint8_t num_wps;
};
typedef enum {
LM32_WP_DISABLED = 0,
LM32_WP_READ,
LM32_WP_WRITE,
LM32_WP_READ_WRITE,
} lm32_wp_t;
static inline lm32_wp_t lm32_wp_type(uint32_t dc, int idx)
{
assert(idx < 4);
return (dc >> (idx+1)*2) & 0x3;
}
#include "cpu-qom.h"
LM32CPU *cpu_lm32_init(const char *cpu_model);
void cpu_lm32_list(FILE *f, fprintf_function cpu_fprintf);
int cpu_lm32_exec(CPULM32State *s);
/* you can call this signal handler from your SIGBUS and SIGSEGV
signal handlers to inform the virtual CPU of exceptions. non zero
is returned if the signal was handled by the virtual CPU. */
int cpu_lm32_signal_handler(int host_signum, void *pinfo,
void *puc);
void lm32_cpu_list(FILE *f, fprintf_function cpu_fprintf);
void lm32_translate_init(void);
void cpu_lm32_set_phys_msb_ignore(CPULM32State *env, int value);
void QEMU_NORETURN raise_exception(CPULM32State *env, int index);
void lm32_debug_excp_handler(CPULM32State *env);
void lm32_breakpoint_insert(CPULM32State *env, int index, target_ulong address);
void lm32_breakpoint_remove(CPULM32State *env, int index);
void lm32_watchpoint_insert(CPULM32State *env, int index, target_ulong address,
lm32_wp_t wp_type);
void lm32_watchpoint_remove(CPULM32State *env, int index);
static inline CPULM32State *cpu_init(const char *cpu_model)
{
......@@ -206,7 +226,7 @@ static inline CPULM32State *cpu_init(const char *cpu_model)
return &cpu->env;
}
#define cpu_list cpu_lm32_list
#define cpu_list lm32_cpu_list
#define cpu_exec cpu_lm32_exec
#define cpu_gen_code cpu_lm32_gen_code
#define cpu_signal_handler cpu_lm32_signal_handler
......
......@@ -49,6 +49,96 @@ hwaddr lm32_cpu_get_phys_page_debug(CPUState *cs, vaddr addr)
}
}
void lm32_breakpoint_insert(CPULM32State *env, int idx, target_ulong address)
{
cpu_breakpoint_insert(env, address, BP_CPU, &env->cpu_breakpoint[idx]);
}
void lm32_breakpoint_remove(CPULM32State *env, int idx)
{
if (!env->cpu_breakpoint[idx]) {
return;
}
cpu_breakpoint_remove_by_ref(env, env->cpu_breakpoint[idx]);
env->cpu_breakpoint[idx] = NULL;
}
void lm32_watchpoint_insert(CPULM32State *env, int idx, target_ulong address,
lm32_wp_t wp_type)
{
int flags = 0;
switch (wp_type) {
case LM32_WP_DISABLED:
/* nothing to to */
break;
case LM32_WP_READ:
flags = BP_CPU | BP_STOP_BEFORE_ACCESS | BP_MEM_READ;
break;
case LM32_WP_WRITE:
flags = BP_CPU | BP_STOP_BEFORE_ACCESS | BP_MEM_WRITE;
break;
case LM32_WP_READ_WRITE:
flags = BP_CPU | BP_STOP_BEFORE_ACCESS | BP_MEM_ACCESS;
break;
}
if (flags != 0) {
cpu_watchpoint_insert(env, address, 1, flags,
&env->cpu_watchpoint[idx]);
}
}
void lm32_watchpoint_remove(CPULM32State *env, int idx)
{
if (!env->cpu_watchpoint[idx]) {
return;
}
cpu_watchpoint_remove_by_ref(env, env->cpu_watchpoint[idx]);
env->cpu_watchpoint[idx] = NULL;
}
static bool check_watchpoints(CPULM32State *env)
{
LM32CPU *cpu = lm32_env_get_cpu(env);
int i;
for (i = 0; i < cpu->num_watchpoints; i++) {
if (env->cpu_watchpoint[i] &&
env->cpu_watchpoint[i]->flags & BP_WATCHPOINT_HIT) {
return true;
}
}
return false;
}
void lm32_debug_excp_handler(CPULM32State *env)
{
CPUBreakpoint *bp;
if (env->watchpoint_hit) {
if (env->watchpoint_hit->flags & BP_CPU) {
env->watchpoint_hit = NULL;
if (check_watchpoints(env)) {
raise_exception(env, EXCP_WATCHPOINT);
} else {
cpu_resume_from_signal(env, NULL);
}
}
} else {
QTAILQ_FOREACH(bp, &env->breakpoints, entry) {
if (bp->pc == env->pc) {
if (bp->flags & BP_CPU) {
raise_exception(env, EXCP_BREAKPOINT);
}
break;
}
}
}
}
void lm32_cpu_do_interrupt(CPUState *cs)
{
LM32CPU *cpu = LM32_CPU(cs);
......@@ -90,136 +180,16 @@ void lm32_cpu_do_interrupt(CPUState *cs)
}
}
typedef struct {
const char *name;
uint32_t revision;
uint8_t num_interrupts;
uint8_t num_breakpoints;
uint8_t num_watchpoints;
uint32_t features;
} LM32Def;
static const LM32Def lm32_defs[] = {
{
.name = "lm32-basic",
.revision = 3,
.num_interrupts = 32,
.num_breakpoints = 4,
.num_watchpoints = 4,
.features = (LM32_FEATURE_SHIFT
| LM32_FEATURE_SIGN_EXTEND
| LM32_FEATURE_CYCLE_COUNT),
},
{
.name = "lm32-standard",
.revision = 3,
.num_interrupts = 32,
.num_breakpoints = 4,
.num_watchpoints = 4,
.features = (LM32_FEATURE_MULTIPLY
| LM32_FEATURE_DIVIDE
| LM32_FEATURE_SHIFT
| LM32_FEATURE_SIGN_EXTEND
| LM32_FEATURE_I_CACHE
| LM32_FEATURE_CYCLE_COUNT),
},
{
.name = "lm32-full",
.revision = 3,
.num_interrupts = 32,
.num_breakpoints = 4,
.num_watchpoints = 4,
.features = (LM32_FEATURE_MULTIPLY
| LM32_FEATURE_DIVIDE
| LM32_FEATURE_SHIFT
| LM32_FEATURE_SIGN_EXTEND
| LM32_FEATURE_I_CACHE
| LM32_FEATURE_D_CACHE
| LM32_FEATURE_CYCLE_COUNT),
}
};
void cpu_lm32_list(FILE *f, fprintf_function cpu_fprintf)
{
int i;
cpu_fprintf(f, "Available CPUs:\n");
for (i = 0; i < ARRAY_SIZE(lm32_defs); i++) {
cpu_fprintf(f, " %s\n", lm32_defs[i].name);
}
}
static const LM32Def *cpu_lm32_find_by_name(const char *name)
{
int i;
for (i = 0; i < ARRAY_SIZE(lm32_defs); i++) {
if (strcasecmp(name, lm32_defs[i].name) == 0) {
return &lm32_defs[i];
}
}
return NULL;
}
static uint32_t cfg_by_def(const LM32Def *def)
{
uint32_t cfg = 0;
if (def->features & LM32_FEATURE_MULTIPLY) {
cfg |= CFG_M;
}
if (def->features & LM32_FEATURE_DIVIDE) {
cfg |= CFG_D;
}
if (def->features & LM32_FEATURE_SHIFT) {
cfg |= CFG_S;
}
if (def->features & LM32_FEATURE_SIGN_EXTEND) {
cfg |= CFG_X;
}
if (def->features & LM32_FEATURE_I_CACHE) {
cfg |= CFG_IC;
}
if (def->features & LM32_FEATURE_D_CACHE) {
cfg |= CFG_DC;
}
if (def->features & LM32_FEATURE_CYCLE_COUNT) {
cfg |= CFG_CC;
}
cfg |= (def->num_interrupts << CFG_INT_SHIFT);
cfg |= (def->num_breakpoints << CFG_BP_SHIFT);
cfg |= (def->num_watchpoints << CFG_WP_SHIFT);
cfg |= (def->revision << CFG_REV_SHIFT);
return cfg;
}
LM32CPU *cpu_lm32_init(const char *cpu_model)
{
LM32CPU *cpu;
CPULM32State *env;
const LM32Def *def;
ObjectClass *oc;
def = cpu_lm32_find_by_name(cpu_model);
if (!def) {
oc = cpu_class_by_name(TYPE_LM32_CPU, cpu_model);
if (oc == NULL) {
return NULL;
}
cpu = LM32_CPU(object_new(TYPE_LM32_CPU));
env = &cpu->env;
env->features = def->features;
env->num_bps = def->num_breakpoints;
env->num_wps = def->num_watchpoints;
env->cfg = cfg_by_def(def);
cpu = LM32_CPU(object_new(object_class_get_name(oc)));
object_property_set_bool(OBJECT(cpu), true, "realized", NULL);
......
......@@ -2,6 +2,9 @@
DEF_HELPER_2(raise_exception, void, env, i32)
DEF_HELPER_1(hlt, void, env)
DEF_HELPER_3(wcsr_bp, void, env, i32, i32)
DEF_HELPER_3(wcsr_wp, void, env, i32, i32)
DEF_HELPER_2(wcsr_dc, void, env, i32)
DEF_HELPER_2(wcsr_im, void, env, i32)
DEF_HELPER_2(wcsr_ip, void, env, i32)
DEF_HELPER_2(wcsr_jtx, void, env, i32)
......@@ -10,5 +13,6 @@ DEF_HELPER_1(rcsr_im, i32, env)
DEF_HELPER_1(rcsr_ip, i32, env)
DEF_HELPER_1(rcsr_jtx, i32, env)
DEF_HELPER_1(rcsr_jrx, i32, env)
DEF_HELPER_1(ill, void, env)
#include "exec/def-helper.h"
......@@ -8,6 +8,10 @@
#include "exec/softmmu_exec.h"
#ifndef CONFIG_USER_ONLY
#include "sysemu/sysemu.h"
#endif
#if !defined(CONFIG_USER_ONLY)
#define MMUSUFFIX _mmu
#define SHIFT 0
......@@ -19,12 +23,17 @@
#define SHIFT 3
#include "exec/softmmu_template.h"
void HELPER(raise_exception)(CPULM32State *env, uint32_t index)
void raise_exception(CPULM32State *env, int index)
{
env->exception_index = index;
cpu_loop_exit(env);
}
void HELPER(raise_exception)(CPULM32State *env, uint32_t index)
{
raise_exception(env, index);
}
void HELPER(hlt)(CPULM32State *env)
{
CPUState *cs = CPU(lm32_env_get_cpu(env));
......@@ -34,6 +43,70 @@ void HELPER(hlt)(CPULM32State *env)
cpu_loop_exit(env);
}
void HELPER(ill)(CPULM32State *env)
{
#ifndef CONFIG_USER_ONLY
CPUState *cs = CPU(lm32_env_get_cpu(env));
fprintf(stderr, "VM paused due to illegal instruction. "
"Connect a debugger or switch to the monitor console "
"to find out more.\n");
qemu_system_vmstop_request(RUN_STATE_PAUSED);
cs->halted = 1;
raise_exception(env, EXCP_HALTED);
#endif
}
void HELPER(wcsr_bp)(CPULM32State *env, uint32_t bp, uint32_t idx)
{
uint32_t addr = bp & ~1;
assert(idx < 4);
env->bp[idx] = bp;
lm32_breakpoint_remove(env, idx);
if (bp & 1) {
lm32_breakpoint_insert(env, idx, addr);
}
}
void HELPER(wcsr_wp)(CPULM32State *env, uint32_t wp, uint32_t idx)
{
lm32_wp_t wp_type;
assert(idx < 4);
env->wp[idx] = wp;