Message ID | 1453844704-26469-1-git-send-email-jcd@tribudubois.net (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
On 26 January 2016 at 21:45, Jean-Christophe Dubois <jcd@tribudubois.net> wrote: > This controller is also present in i.MX5X devices but they are not > yet emulated by Qemu. QEMU is all-caps. > Signed-off-by: Jean-Christophe Dubois <jcd@tribudubois.net> > --- > hw/misc/Makefile.objs | 1 + > hw/misc/imx6_src.c | 353 +++++++++++++++++++++++++++++++++++++++++++++ > include/hw/misc/imx6_src.h | 72 +++++++++ > 3 files changed, 426 insertions(+) > create mode 100644 hw/misc/imx6_src.c > create mode 100644 include/hw/misc/imx6_src.h > > diff --git a/hw/misc/Makefile.objs b/hw/misc/Makefile.objs > index a2a8e91..6bac654 100644 > --- a/hw/misc/Makefile.objs > +++ b/hw/misc/Makefile.objs > @@ -29,6 +29,7 @@ obj-$(CONFIG_IMX) += imx_ccm.o > obj-$(CONFIG_IMX) += imx31_ccm.o > obj-$(CONFIG_IMX) += imx25_ccm.o > obj-$(CONFIG_IMX) += imx6_ccm.o > +obj-$(CONFIG_IMX) += imx6_src.o > obj-$(CONFIG_MILKYMIST) += milkymist-hpdmc.o > obj-$(CONFIG_MILKYMIST) += milkymist-pfpu.o > obj-$(CONFIG_MAINSTONE) += mst_fpga.o > diff --git a/hw/misc/imx6_src.c b/hw/misc/imx6_src.c > new file mode 100644 > index 0000000..d1be7c1 > --- /dev/null > +++ b/hw/misc/imx6_src.c > @@ -0,0 +1,353 @@ > +/* > + * IMX6 System Reset Controller > + * > + * Copyright (c) 2015 Jean-Christophe Dubois <jcd@tribudubois.net> > + * > + * This work is licensed under the terms of the GNU GPL, version 2 or later. > + * See the COPYING file in the top-level directory. > + * > + */ > + > +#include "hw/misc/imx6_src.h" > +#include "sysemu/sysemu.h" > +#include "qemu/bitops.h" > + > +#ifndef DEBUG_IMX6_SRC > +#define DEBUG_IMX6_SRC 0 > +#endif > + > +#define DPRINTF(fmt, args...) \ > + do { \ > + if (DEBUG_IMX6_SRC) { \ > + fprintf(stderr, "[%s]%s: " fmt , TYPE_IMX6_SRC, \ > + __func__, ##args); \ > + } \ > + } while (0) > + > +static char const *imx6_src_reg_name(uint32_t reg) > +{ > + static char unknown[20]; > + > + switch (reg) { > + case SRC_SCR: > + return "SRC_SCR"; > + case SRC_SBMR1: > + return "SRC_SBMR1"; > + case SRC_SRSR: > + return "SRC_SRSR"; > + case SRC_SISR: > + return "SRC_SISR"; > + case SRC_SIMR: > + return "SRC_SIMR"; > + case SRC_SBMR2: > + return "SRC_SBMR2"; > + case SRC_GPR1: > + return "SRC_GPR1"; > + case SRC_GPR2: > + return "SRC_GPR2"; > + case SRC_GPR3: > + return "SRC_GPR3"; > + case SRC_GPR4: > + return "SRC_GPR4"; > + case SRC_GPR5: > + return "SRC_GPR5"; > + case SRC_GPR6: > + return "SRC_GPR6"; > + case SRC_GPR7: > + return "SRC_GPR7"; > + case SRC_GPR8: > + return "SRC_GPR8"; > + case SRC_GPR9: > + return "SRC_GPR9"; > + case SRC_GPR10: > + return "SRC_GPR10"; > + default: > + sprintf(unknown, "%d ?", reg); > + return unknown; > + } > +} > + > +static const VMStateDescription vmstate_imx6_src = { > + .name = TYPE_IMX6_SRC, > + .version_id = 1, > + .minimum_version_id = 1, > + .fields = (VMStateField[]) { > + VMSTATE_UINT32_ARRAY(regs, IMX6SRCState, SRC_MAX), > + VMSTATE_END_OF_LIST() > + }, > +}; > + > +static void imx6_src_reset(DeviceState *dev) > +{ > + IMX6SRCState *s = IMX6_SRC(dev); > + > + DPRINTF("\n"); > + > + /* > + * We only clear the first registers as all GPR registers are preserved > + * over resets > + */ > + memset(s->regs, 0, SRC_GPR1 * sizeof(uint32_t)); Reset for a QEMU device means "full power cycle reset", so we should return the state to the same as if QEMU had just been started. > + > + /* Set reset values */ > + s->regs[SRC_SCR] = 0x521; > + s->regs[SRC_SRSR] = 0x1; > + s->regs[SRC_SIMR] = 0x1F; > +} > + > +static CPUState *imx6_src_get_cpu_by_id(uint32_t id) > +{ > + CPUState *cpu; > + > + DPRINTF("cpu %d\n", id); > + > + CPU_FOREACH(cpu) { > + ARMCPU *armcpu = ARM_CPU(cpu); > + > + if (armcpu->mp_affinity == id) { > + return cpu; > + } > + } > + > + qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Resquesting unknown CPU %d\n", > + TYPE_IMX6_SRC, __func__, id); > + > + return NULL; > +} > + > +static void imx6_src_cpu_on(uint32_t cpuid, uint32_t entry, uint32_t context_id) > +{ > + CPUState *target_cpu_state; > + ARMCPU *target_cpu; > + CPUClass *target_cpu_class; > + > + DPRINTF("cpu %d @ 0x%08x with R0 = 0x%08x\n", cpuid, entry, context_id); > + > + /* change to the cpu we are powering up */ > + target_cpu_state = imx6_src_get_cpu_by_id(cpuid); > + if (!target_cpu_state) { > + return; > + } > + target_cpu = ARM_CPU(target_cpu_state); > + if (!target_cpu->powered_off) { > + qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: CPU %d is already running\n", > + TYPE_IMX6_SRC, __func__, cpuid); > + return; > + } > + target_cpu_class = CPU_GET_CLASS(target_cpu); > + > + /* Initialize the cpu we are turning on */ > + cpu_reset(target_cpu_state); > + target_cpu->powered_off = false; > + target_cpu_state->halted = 0; > + > + target_cpu->env.regs[0] = context_id; > + target_cpu->env.thumb = entry & 1; > + > + target_cpu_class->set_pc(target_cpu_state, entry); This all looks pretty dangerous to me. Handling power-on of another CPU needs careful thought, especially since we're moving towards multi-threaded TCG, in which case the other CPU might in thory be really running. > +} > + > +static void imx6_src_cpu_off(uint32_t cpuid) > +{ > + CPUState *target_cpu_state; > + ARMCPU *target_cpu; > + > + DPRINTF("cpu %d\n", cpuid); > + > + /* change to the cpu we are powering up */ > + target_cpu_state = imx6_src_get_cpu_by_id(cpuid); > + if (!target_cpu_state) { > + return; > + } > + target_cpu = ARM_CPU(target_cpu_state); > + if (target_cpu->powered_off) { > + qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: CPU %d is already off\n", > + TYPE_IMX6_SRC, __func__, cpuid); > + return; > + } > + > + target_cpu->powered_off = true; > + target_cpu_state->halted = 1; > + target_cpu_state->exception_index = EXCP_HLT; > + cpu_loop_exit(target_cpu_state); > +} > + > +static void imx6_src_cpu_reset(uint32_t cpuid) > +{ > + CPUState *target_cpu_state; > + ARMCPU *target_cpu; > + > + DPRINTF("cpu %d\n", cpuid); > + > + /* change to the cpu we are powering up */ > + target_cpu_state = imx6_src_get_cpu_by_id(cpuid); > + if (!target_cpu_state) { > + return; > + } > + target_cpu = ARM_CPU(target_cpu_state); > + if (target_cpu->powered_off) { > + qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: CPU %d is off\n", > + TYPE_IMX6_SRC, __func__, cpuid); > + return; > + } > + > + /* Reset the cpu we are turning on */ > + cpu_reset(target_cpu_state); > +} > + > +static uint64_t imx6_src_read(void *opaque, hwaddr offset, unsigned size) > +{ > + uint32 value = 0; > + IMX6SRCState *s = (IMX6SRCState *)opaque; > + uint32_t index = offset >> 2; > + > + if (index < SRC_MAX) { > + value = s->regs[index]; > + } else { > + qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%" > + HWADDR_PRIx "\n", TYPE_IMX6_SRC, __func__, offset); > + > + } > + > + DPRINTF("reg[%s] => 0x%" PRIx32 "\n", imx6_src_reg_name(index), value); > + > + return (uint64_t)value; > +} > + > +static void imx6_src_write(void *opaque, hwaddr offset, uint64_t value, > + unsigned size) > +{ > + IMX6SRCState *s = (IMX6SRCState *)opaque; > + uint32_t index = offset >> 2; > + uint64_t change_mask; > + > + if (index >= SRC_MAX) { > + qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%" > + HWADDR_PRIx "\n", TYPE_IMX6_SRC, __func__, offset); > + return; > + } > + > + DPRINTF("reg[%s] <= 0x%" PRIx32 "\n", imx6_src_reg_name(index), > + (uint32_t)value); > + > + change_mask = s->regs[index] ^ (uint32_t)value; > + > + switch (index) { > + case SRC_SCR: > + if (EXTRACT(change_mask, CORE3_ENABLE)) { > + if (EXTRACT(value, CORE3_ENABLE)) { > + /* CORE 3 is brought up */ > + imx6_src_cpu_on(3, s->regs[SRC_GPR7], s->regs[SRC_GPR8]); > + } else { > + /* CORE 3 is shut down */ > + imx6_src_cpu_off(3); > + } > + /* We clear the reset bits as the processor chaged state */ "changed" > +#define EXTRACT(value, name) (((value) >> name##_SHIFT) & name##_MASK) Again, please don't create local bit-manipulation functions or macros; use the ones in bitops.h. thanks -- PMM
Le 02/02/2016 17:46, Peter Maydell a écrit : > On 26 January 2016 at 21:45, Jean-Christophe Dubois <jcd@tribudubois.net> wrote: >> This controller is also present in i.MX5X devices but they are not >> yet emulated by Qemu. > QEMU is all-caps. > >> Signed-off-by: Jean-Christophe Dubois <jcd@tribudubois.net> >> --- >> hw/misc/Makefile.objs | 1 + >> hw/misc/imx6_src.c | 353 +++++++++++++++++++++++++++++++++++++++++++++ >> include/hw/misc/imx6_src.h | 72 +++++++++ >> 3 files changed, 426 insertions(+) >> create mode 100644 hw/misc/imx6_src.c >> create mode 100644 include/hw/misc/imx6_src.h >> >> diff --git a/hw/misc/Makefile.objs b/hw/misc/Makefile.objs >> index a2a8e91..6bac654 100644 >> --- a/hw/misc/Makefile.objs >> +++ b/hw/misc/Makefile.objs >> @@ -29,6 +29,7 @@ obj-$(CONFIG_IMX) += imx_ccm.o >> obj-$(CONFIG_IMX) += imx31_ccm.o >> obj-$(CONFIG_IMX) += imx25_ccm.o >> obj-$(CONFIG_IMX) += imx6_ccm.o >> +obj-$(CONFIG_IMX) += imx6_src.o >> obj-$(CONFIG_MILKYMIST) += milkymist-hpdmc.o >> obj-$(CONFIG_MILKYMIST) += milkymist-pfpu.o >> obj-$(CONFIG_MAINSTONE) += mst_fpga.o >> diff --git a/hw/misc/imx6_src.c b/hw/misc/imx6_src.c >> new file mode 100644 >> index 0000000..d1be7c1 >> --- /dev/null >> +++ b/hw/misc/imx6_src.c >> @@ -0,0 +1,353 @@ >> +/* >> + * IMX6 System Reset Controller >> + * >> + * Copyright (c) 2015 Jean-Christophe Dubois <jcd@tribudubois.net> >> + * >> + * This work is licensed under the terms of the GNU GPL, version 2 or later. >> + * See the COPYING file in the top-level directory. >> + * >> + */ >> + >> +#include "hw/misc/imx6_src.h" >> +#include "sysemu/sysemu.h" >> +#include "qemu/bitops.h" >> + >> +#ifndef DEBUG_IMX6_SRC >> +#define DEBUG_IMX6_SRC 0 >> +#endif >> + >> +#define DPRINTF(fmt, args...) \ >> + do { \ >> + if (DEBUG_IMX6_SRC) { \ >> + fprintf(stderr, "[%s]%s: " fmt , TYPE_IMX6_SRC, \ >> + __func__, ##args); \ >> + } \ >> + } while (0) >> + >> +static char const *imx6_src_reg_name(uint32_t reg) >> +{ >> + static char unknown[20]; >> + >> + switch (reg) { >> + case SRC_SCR: >> + return "SRC_SCR"; >> + case SRC_SBMR1: >> + return "SRC_SBMR1"; >> + case SRC_SRSR: >> + return "SRC_SRSR"; >> + case SRC_SISR: >> + return "SRC_SISR"; >> + case SRC_SIMR: >> + return "SRC_SIMR"; >> + case SRC_SBMR2: >> + return "SRC_SBMR2"; >> + case SRC_GPR1: >> + return "SRC_GPR1"; >> + case SRC_GPR2: >> + return "SRC_GPR2"; >> + case SRC_GPR3: >> + return "SRC_GPR3"; >> + case SRC_GPR4: >> + return "SRC_GPR4"; >> + case SRC_GPR5: >> + return "SRC_GPR5"; >> + case SRC_GPR6: >> + return "SRC_GPR6"; >> + case SRC_GPR7: >> + return "SRC_GPR7"; >> + case SRC_GPR8: >> + return "SRC_GPR8"; >> + case SRC_GPR9: >> + return "SRC_GPR9"; >> + case SRC_GPR10: >> + return "SRC_GPR10"; >> + default: >> + sprintf(unknown, "%d ?", reg); >> + return unknown; >> + } >> +} >> + >> +static const VMStateDescription vmstate_imx6_src = { >> + .name = TYPE_IMX6_SRC, >> + .version_id = 1, >> + .minimum_version_id = 1, >> + .fields = (VMStateField[]) { >> + VMSTATE_UINT32_ARRAY(regs, IMX6SRCState, SRC_MAX), >> + VMSTATE_END_OF_LIST() >> + }, >> +}; >> + >> +static void imx6_src_reset(DeviceState *dev) >> +{ >> + IMX6SRCState *s = IMX6_SRC(dev); >> + >> + DPRINTF("\n"); >> + >> + /* >> + * We only clear the first registers as all GPR registers are preserved >> + * over resets >> + */ >> + memset(s->regs, 0, SRC_GPR1 * sizeof(uint32_t)); > Reset for a QEMU device means "full power cycle reset", so we should > return the state to the same as if QEMU had just been started. Is there any way to support the warm reset then? >> + >> + /* Set reset values */ >> + s->regs[SRC_SCR] = 0x521; >> + s->regs[SRC_SRSR] = 0x1; >> + s->regs[SRC_SIMR] = 0x1F; >> +} >> + >> +static CPUState *imx6_src_get_cpu_by_id(uint32_t id) >> +{ >> + CPUState *cpu; >> + >> + DPRINTF("cpu %d\n", id); >> + >> + CPU_FOREACH(cpu) { >> + ARMCPU *armcpu = ARM_CPU(cpu); >> + >> + if (armcpu->mp_affinity == id) { >> + return cpu; >> + } >> + } >> + >> + qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Resquesting unknown CPU %d\n", >> + TYPE_IMX6_SRC, __func__, id); >> + >> + return NULL; >> +} >> + >> +static void imx6_src_cpu_on(uint32_t cpuid, uint32_t entry, uint32_t context_id) >> +{ >> + CPUState *target_cpu_state; >> + ARMCPU *target_cpu; >> + CPUClass *target_cpu_class; >> + >> + DPRINTF("cpu %d @ 0x%08x with R0 = 0x%08x\n", cpuid, entry, context_id); >> + >> + /* change to the cpu we are powering up */ >> + target_cpu_state = imx6_src_get_cpu_by_id(cpuid); >> + if (!target_cpu_state) { >> + return; >> + } >> + target_cpu = ARM_CPU(target_cpu_state); >> + if (!target_cpu->powered_off) { >> + qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: CPU %d is already running\n", >> + TYPE_IMX6_SRC, __func__, cpuid); >> + return; >> + } >> + target_cpu_class = CPU_GET_CLASS(target_cpu); >> + >> + /* Initialize the cpu we are turning on */ >> + cpu_reset(target_cpu_state); >> + target_cpu->powered_off = false; >> + target_cpu_state->halted = 0; >> + >> + target_cpu->env.regs[0] = context_id; >> + target_cpu->env.thumb = entry & 1; >> + >> + target_cpu_class->set_pc(target_cpu_state, entry); > This all looks pretty dangerous to me. Handling power-on of > another CPU needs careful thought, especially since we're moving > towards multi-threaded TCG, in which case the other CPU might > in thory be really running. In i.MX6 (and SOC using the SRC in general) all cores (except core 0 obviously) are initialized in "powered-off" mode. Therefore they should not be running before this function is called. Basically this code is just an adaptation of a similar code in target-arm/psci.c. Now it might not be the right way to to things (event if it does work) and I would be thankful if you could point me in the right direction. > >> +} >> + >> +static void imx6_src_cpu_off(uint32_t cpuid) >> +{ >> + CPUState *target_cpu_state; >> + ARMCPU *target_cpu; >> + >> + DPRINTF("cpu %d\n", cpuid); >> + >> + /* change to the cpu we are powering up */ >> + target_cpu_state = imx6_src_get_cpu_by_id(cpuid); >> + if (!target_cpu_state) { >> + return; >> + } >> + target_cpu = ARM_CPU(target_cpu_state); >> + if (target_cpu->powered_off) { >> + qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: CPU %d is already off\n", >> + TYPE_IMX6_SRC, __func__, cpuid); >> + return; >> + } >> + >> + target_cpu->powered_off = true; >> + target_cpu_state->halted = 1; >> + target_cpu_state->exception_index = EXCP_HLT; >> + cpu_loop_exit(target_cpu_state); >> +} >> + >> +static void imx6_src_cpu_reset(uint32_t cpuid) >> +{ >> + CPUState *target_cpu_state; >> + ARMCPU *target_cpu; >> + >> + DPRINTF("cpu %d\n", cpuid); >> + >> + /* change to the cpu we are powering up */ >> + target_cpu_state = imx6_src_get_cpu_by_id(cpuid); >> + if (!target_cpu_state) { >> + return; >> + } >> + target_cpu = ARM_CPU(target_cpu_state); >> + if (target_cpu->powered_off) { >> + qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: CPU %d is off\n", >> + TYPE_IMX6_SRC, __func__, cpuid); >> + return; >> + } >> + >> + /* Reset the cpu we are turning on */ >> + cpu_reset(target_cpu_state); >> +} >> + >> +static uint64_t imx6_src_read(void *opaque, hwaddr offset, unsigned size) >> +{ >> + uint32 value = 0; >> + IMX6SRCState *s = (IMX6SRCState *)opaque; >> + uint32_t index = offset >> 2; >> + >> + if (index < SRC_MAX) { >> + value = s->regs[index]; >> + } else { >> + qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%" >> + HWADDR_PRIx "\n", TYPE_IMX6_SRC, __func__, offset); >> + >> + } >> + >> + DPRINTF("reg[%s] => 0x%" PRIx32 "\n", imx6_src_reg_name(index), value); >> + >> + return (uint64_t)value; >> +} >> + >> +static void imx6_src_write(void *opaque, hwaddr offset, uint64_t value, >> + unsigned size) >> +{ >> + IMX6SRCState *s = (IMX6SRCState *)opaque; >> + uint32_t index = offset >> 2; >> + uint64_t change_mask; >> + >> + if (index >= SRC_MAX) { >> + qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%" >> + HWADDR_PRIx "\n", TYPE_IMX6_SRC, __func__, offset); >> + return; >> + } >> + >> + DPRINTF("reg[%s] <= 0x%" PRIx32 "\n", imx6_src_reg_name(index), >> + (uint32_t)value); >> + >> + change_mask = s->regs[index] ^ (uint32_t)value; >> + >> + switch (index) { >> + case SRC_SCR: >> + if (EXTRACT(change_mask, CORE3_ENABLE)) { >> + if (EXTRACT(value, CORE3_ENABLE)) { >> + /* CORE 3 is brought up */ >> + imx6_src_cpu_on(3, s->regs[SRC_GPR7], s->regs[SRC_GPR8]); >> + } else { >> + /* CORE 3 is shut down */ >> + imx6_src_cpu_off(3); >> + } >> + /* We clear the reset bits as the processor chaged state */ > "changed" > >> +#define EXTRACT(value, name) (((value) >> name##_SHIFT) & name##_MASK) > Again, please don't create local bit-manipulation functions or macros; > use the ones in bitops.h. > > thanks > -- PMM >
On 6 February 2016 at 20:07, Jean-Christophe DUBOIS <jcd@tribudubois.net> wrote: > Le 02/02/2016 17:46, Peter Maydell a écrit : >> >> On 26 January 2016 at 21:45, Jean-Christophe Dubois <jcd@tribudubois.net> >> wrote: >>> +static void imx6_src_reset(DeviceState *dev) >>> +{ >>> + IMX6SRCState *s = IMX6_SRC(dev); >>> + >>> + DPRINTF("\n"); >>> + >>> + /* >>> + * We only clear the first registers as all GPR registers are >>> preserved >>> + * over resets >>> + */ >>> + memset(s->regs, 0, SRC_GPR1 * sizeof(uint32_t)); >> >> Reset for a QEMU device means "full power cycle reset", so we should >> return the state to the same as if QEMU had just been started. > > > Is there any way to support the warm reset then? If it's purely local to a device (triggered by a register write), just write a function that does what it needs to do. If you need a more machine-wide idea of "warm reset" then things get harder. >> This all looks pretty dangerous to me. Handling power-on of >> another CPU needs careful thought, especially since we're moving >> towards multi-threaded TCG, in which case the other CPU might >> in thory be really running. > > > In i.MX6 (and SOC using the SRC in general) all cores (except core 0 > obviously) are initialized in "powered-off" mode. Therefore they should not > be running before this function is called. > > Basically this code is just an adaptation of a similar code in > target-arm/psci.c. Yeah, I realised that a bit after posting my earlier email. We should factor out the code that's doing the actual work of powering up a different CPU, because then if it does need modification for multi-threading we only have one place to fix. thanks -- PMM
diff --git a/hw/misc/Makefile.objs b/hw/misc/Makefile.objs index a2a8e91..6bac654 100644 --- a/hw/misc/Makefile.objs +++ b/hw/misc/Makefile.objs @@ -29,6 +29,7 @@ obj-$(CONFIG_IMX) += imx_ccm.o obj-$(CONFIG_IMX) += imx31_ccm.o obj-$(CONFIG_IMX) += imx25_ccm.o obj-$(CONFIG_IMX) += imx6_ccm.o +obj-$(CONFIG_IMX) += imx6_src.o obj-$(CONFIG_MILKYMIST) += milkymist-hpdmc.o obj-$(CONFIG_MILKYMIST) += milkymist-pfpu.o obj-$(CONFIG_MAINSTONE) += mst_fpga.o diff --git a/hw/misc/imx6_src.c b/hw/misc/imx6_src.c new file mode 100644 index 0000000..d1be7c1 --- /dev/null +++ b/hw/misc/imx6_src.c @@ -0,0 +1,353 @@ +/* + * IMX6 System Reset Controller + * + * Copyright (c) 2015 Jean-Christophe Dubois <jcd@tribudubois.net> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + */ + +#include "hw/misc/imx6_src.h" +#include "sysemu/sysemu.h" +#include "qemu/bitops.h" + +#ifndef DEBUG_IMX6_SRC +#define DEBUG_IMX6_SRC 0 +#endif + +#define DPRINTF(fmt, args...) \ + do { \ + if (DEBUG_IMX6_SRC) { \ + fprintf(stderr, "[%s]%s: " fmt , TYPE_IMX6_SRC, \ + __func__, ##args); \ + } \ + } while (0) + +static char const *imx6_src_reg_name(uint32_t reg) +{ + static char unknown[20]; + + switch (reg) { + case SRC_SCR: + return "SRC_SCR"; + case SRC_SBMR1: + return "SRC_SBMR1"; + case SRC_SRSR: + return "SRC_SRSR"; + case SRC_SISR: + return "SRC_SISR"; + case SRC_SIMR: + return "SRC_SIMR"; + case SRC_SBMR2: + return "SRC_SBMR2"; + case SRC_GPR1: + return "SRC_GPR1"; + case SRC_GPR2: + return "SRC_GPR2"; + case SRC_GPR3: + return "SRC_GPR3"; + case SRC_GPR4: + return "SRC_GPR4"; + case SRC_GPR5: + return "SRC_GPR5"; + case SRC_GPR6: + return "SRC_GPR6"; + case SRC_GPR7: + return "SRC_GPR7"; + case SRC_GPR8: + return "SRC_GPR8"; + case SRC_GPR9: + return "SRC_GPR9"; + case SRC_GPR10: + return "SRC_GPR10"; + default: + sprintf(unknown, "%d ?", reg); + return unknown; + } +} + +static const VMStateDescription vmstate_imx6_src = { + .name = TYPE_IMX6_SRC, + .version_id = 1, + .minimum_version_id = 1, + .fields = (VMStateField[]) { + VMSTATE_UINT32_ARRAY(regs, IMX6SRCState, SRC_MAX), + VMSTATE_END_OF_LIST() + }, +}; + +static void imx6_src_reset(DeviceState *dev) +{ + IMX6SRCState *s = IMX6_SRC(dev); + + DPRINTF("\n"); + + /* + * We only clear the first registers as all GPR registers are preserved + * over resets + */ + memset(s->regs, 0, SRC_GPR1 * sizeof(uint32_t)); + + /* Set reset values */ + s->regs[SRC_SCR] = 0x521; + s->regs[SRC_SRSR] = 0x1; + s->regs[SRC_SIMR] = 0x1F; +} + +static CPUState *imx6_src_get_cpu_by_id(uint32_t id) +{ + CPUState *cpu; + + DPRINTF("cpu %d\n", id); + + CPU_FOREACH(cpu) { + ARMCPU *armcpu = ARM_CPU(cpu); + + if (armcpu->mp_affinity == id) { + return cpu; + } + } + + qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Resquesting unknown CPU %d\n", + TYPE_IMX6_SRC, __func__, id); + + return NULL; +} + +static void imx6_src_cpu_on(uint32_t cpuid, uint32_t entry, uint32_t context_id) +{ + CPUState *target_cpu_state; + ARMCPU *target_cpu; + CPUClass *target_cpu_class; + + DPRINTF("cpu %d @ 0x%08x with R0 = 0x%08x\n", cpuid, entry, context_id); + + /* change to the cpu we are powering up */ + target_cpu_state = imx6_src_get_cpu_by_id(cpuid); + if (!target_cpu_state) { + return; + } + target_cpu = ARM_CPU(target_cpu_state); + if (!target_cpu->powered_off) { + qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: CPU %d is already running\n", + TYPE_IMX6_SRC, __func__, cpuid); + return; + } + target_cpu_class = CPU_GET_CLASS(target_cpu); + + /* Initialize the cpu we are turning on */ + cpu_reset(target_cpu_state); + target_cpu->powered_off = false; + target_cpu_state->halted = 0; + + target_cpu->env.regs[0] = context_id; + target_cpu->env.thumb = entry & 1; + + target_cpu_class->set_pc(target_cpu_state, entry); +} + +static void imx6_src_cpu_off(uint32_t cpuid) +{ + CPUState *target_cpu_state; + ARMCPU *target_cpu; + + DPRINTF("cpu %d\n", cpuid); + + /* change to the cpu we are powering up */ + target_cpu_state = imx6_src_get_cpu_by_id(cpuid); + if (!target_cpu_state) { + return; + } + target_cpu = ARM_CPU(target_cpu_state); + if (target_cpu->powered_off) { + qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: CPU %d is already off\n", + TYPE_IMX6_SRC, __func__, cpuid); + return; + } + + target_cpu->powered_off = true; + target_cpu_state->halted = 1; + target_cpu_state->exception_index = EXCP_HLT; + cpu_loop_exit(target_cpu_state); +} + +static void imx6_src_cpu_reset(uint32_t cpuid) +{ + CPUState *target_cpu_state; + ARMCPU *target_cpu; + + DPRINTF("cpu %d\n", cpuid); + + /* change to the cpu we are powering up */ + target_cpu_state = imx6_src_get_cpu_by_id(cpuid); + if (!target_cpu_state) { + return; + } + target_cpu = ARM_CPU(target_cpu_state); + if (target_cpu->powered_off) { + qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: CPU %d is off\n", + TYPE_IMX6_SRC, __func__, cpuid); + return; + } + + /* Reset the cpu we are turning on */ + cpu_reset(target_cpu_state); +} + +static uint64_t imx6_src_read(void *opaque, hwaddr offset, unsigned size) +{ + uint32 value = 0; + IMX6SRCState *s = (IMX6SRCState *)opaque; + uint32_t index = offset >> 2; + + if (index < SRC_MAX) { + value = s->regs[index]; + } else { + qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%" + HWADDR_PRIx "\n", TYPE_IMX6_SRC, __func__, offset); + + } + + DPRINTF("reg[%s] => 0x%" PRIx32 "\n", imx6_src_reg_name(index), value); + + return (uint64_t)value; +} + +static void imx6_src_write(void *opaque, hwaddr offset, uint64_t value, + unsigned size) +{ + IMX6SRCState *s = (IMX6SRCState *)opaque; + uint32_t index = offset >> 2; + uint64_t change_mask; + + if (index >= SRC_MAX) { + qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%" + HWADDR_PRIx "\n", TYPE_IMX6_SRC, __func__, offset); + return; + } + + DPRINTF("reg[%s] <= 0x%" PRIx32 "\n", imx6_src_reg_name(index), + (uint32_t)value); + + change_mask = s->regs[index] ^ (uint32_t)value; + + switch (index) { + case SRC_SCR: + if (EXTRACT(change_mask, CORE3_ENABLE)) { + if (EXTRACT(value, CORE3_ENABLE)) { + /* CORE 3 is brought up */ + imx6_src_cpu_on(3, s->regs[SRC_GPR7], s->regs[SRC_GPR8]); + } else { + /* CORE 3 is shut down */ + imx6_src_cpu_off(3); + } + /* We clear the reset bits as the processor chaged state */ + clear_bit(CORE3_RST_SHIFT, &value); + clear_bit(CORE3_RST_SHIFT, &change_mask); + } + if (EXTRACT(change_mask, CORE2_ENABLE)) { + if (EXTRACT(value, CORE2_ENABLE)) { + /* CORE 2 is brought up */ + imx6_src_cpu_on(2, s->regs[SRC_GPR5], s->regs[SRC_GPR6]); + } else { + /* CORE 3 is shut down */ + imx6_src_cpu_off(2); + } + /* We clear the reset bits as the processor chaged state */ + clear_bit(CORE2_RST_SHIFT, &value); + clear_bit(CORE2_RST_SHIFT, &change_mask); + } + if (EXTRACT(change_mask, CORE1_ENABLE)) { + if (EXTRACT(value, CORE1_ENABLE)) { + /* CORE 1 is brought up */ + imx6_src_cpu_on(1, s->regs[SRC_GPR3], s->regs[SRC_GPR4]); + } else { + /* CORE 3 is shut down */ + imx6_src_cpu_off(1); + } + /* We clear the reset bits as the processor chaged state */ + clear_bit(CORE1_RST_SHIFT, &value); + clear_bit(CORE1_RST_SHIFT, &change_mask); + } + if (EXTRACT(change_mask, CORE0_RST)) { + imx6_src_cpu_reset(0); + clear_bit(CORE0_RST_SHIFT, &value); + } + if (EXTRACT(change_mask, CORE1_RST)) { + imx6_src_cpu_reset(1); + clear_bit(CORE1_RST_SHIFT, &value); + } + if (EXTRACT(change_mask, CORE2_RST)) { + imx6_src_cpu_reset(2); + clear_bit(CORE2_RST_SHIFT, &value); + } + if (EXTRACT(change_mask, CORE3_RST)) { + imx6_src_cpu_reset(3); + clear_bit(CORE3_RST_SHIFT, &value); + } + if (EXTRACT(change_mask, SW_IPU2_RST)) { + /* We pretend the IPU2 is reseted */ + clear_bit(SW_IPU2_RST_SHIFT, &value); + } + if (EXTRACT(change_mask, SW_IPU1_RST)) { + /* We pretend the IPU1 is reseted */ + clear_bit(SW_IPU1_RST_SHIFT, &value); + } + s->regs[index] = value; + break; + default: + s->regs[index] = value; + break; + } +} + +static const struct MemoryRegionOps imx6_src_ops = { + .read = imx6_src_read, + .write = imx6_src_write, + .endianness = DEVICE_NATIVE_ENDIAN, + .valid = { + /* + * Our device would not work correctly if the guest was doing + * unaligned access. This might not be a limitation on the real + * device but in practice there is no reason for a guest to access + * this device unaligned. + */ + .min_access_size = 4, + .max_access_size = 4, + .unaligned = false, + }, +}; + +static void imx6_src_realize(DeviceState *dev, Error **errp) +{ + IMX6SRCState *s = IMX6_SRC(dev); + + memory_region_init_io(&s->iomem, OBJECT(dev), &imx6_src_ops, s, + TYPE_IMX6_SRC, 0x1000); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem); +} + +static void imx6_src_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->realize = imx6_src_realize; + dc->reset = imx6_src_reset; + dc->vmsd = &vmstate_imx6_src; + dc->desc = "i.MX6 System Reset Controller"; +} + +static const TypeInfo imx6_src_info = { + .name = TYPE_IMX6_SRC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(IMX6SRCState), + .class_init = imx6_src_class_init, +}; + +static void imx6_src_register_types(void) +{ + type_register_static(&imx6_src_info); +} + +type_init(imx6_src_register_types) diff --git a/include/hw/misc/imx6_src.h b/include/hw/misc/imx6_src.h new file mode 100644 index 0000000..f3e0175 --- /dev/null +++ b/include/hw/misc/imx6_src.h @@ -0,0 +1,72 @@ +/* + * IMX6 System Reset Controller + * + * Copyright (C) 2012 NICTA + * Updated by Jean-Christophe Dubois <jcd@tribudubois.net> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + */ + +#ifndef IMX6_SRC_H +#define IMX6_SRC_H + +#include "hw/sysbus.h" + +#define SRC_SCR 0 +#define SRC_SBMR1 1 +#define SRC_SRSR 2 +#define SRC_SISR 5 +#define SRC_SIMR 6 +#define SRC_SBMR2 7 +#define SRC_GPR1 8 +#define SRC_GPR2 9 +#define SRC_GPR3 10 +#define SRC_GPR4 11 +#define SRC_GPR5 12 +#define SRC_GPR6 13 +#define SRC_GPR7 14 +#define SRC_GPR8 15 +#define SRC_GPR9 16 +#define SRC_GPR10 17 +#define SRC_MAX 18 + +/* SRC_SCR */ +#define CORE3_ENABLE_SHIFT (24) +#define CORE3_ENABLE_MASK (0x1) +#define CORE2_ENABLE_SHIFT (23) +#define CORE2_ENABLE_MASK (0x1) +#define CORE1_ENABLE_SHIFT (22) +#define CORE1_ENABLE_MASK (0x1) +#define CORE3_RST_SHIFT (16) +#define CORE3_RST_MASK (0x1) +#define CORE2_RST_SHIFT (15) +#define CORE2_RST_MASK (0x1) +#define CORE1_RST_SHIFT (14) +#define CORE1_RST_MASK (0x1) +#define CORE0_RST_SHIFT (13) +#define CORE0_RST_MASK (0x1) +#define SW_IPU1_RST_SHIFT (3) +#define SW_IPU1_RST_MASK (0x1) +#define SW_IPU2_RST_SHIFT (12) +#define SW_IPU2_RST_MASK (0x1) +#define WARM_RST_ENABLE_SHIFT (0) +#define WARM_RST_ENABLE_MASK (0x1) + +#define EXTRACT(value, name) (((value) >> name##_SHIFT) & name##_MASK) + +#define TYPE_IMX6_SRC "imx6.src" +#define IMX6_SRC(obj) OBJECT_CHECK(IMX6SRCState, (obj), TYPE_IMX6_SRC) + +typedef struct IMX6SRCState { + /* <private> */ + SysBusDevice parent_obj; + + /* <public> */ + MemoryRegion iomem; + + uint32_t regs[SRC_MAX]; + +} IMX6SRCState; + +#endif /* IMX6_SRC_H */
This controller is also present in i.MX5X devices but they are not yet emulated by Qemu. Signed-off-by: Jean-Christophe Dubois <jcd@tribudubois.net> --- hw/misc/Makefile.objs | 1 + hw/misc/imx6_src.c | 353 +++++++++++++++++++++++++++++++++++++++++++++ include/hw/misc/imx6_src.h | 72 +++++++++ 3 files changed, 426 insertions(+) create mode 100644 hw/misc/imx6_src.c create mode 100644 include/hw/misc/imx6_src.h