avatartwo / avatar2

Python core of avatar²
Apache License 2.0
518 stars 98 forks source link

Unable to modify memory from Peripheral handlers #103

Open max-r-b opened 2 years ago

max-r-b commented 2 years ago

Hi,

I am trying to make a dummy flash controller for an ARM cortex-m3 based MCU that I emulate with QEMU. The goal is to retrieve the different parameters (address, value, size) and then modify the corresponding memory region that I allocated using avatar.add_memory_range(). I took inspiration from the example in the repo avatar2/avatar2_example so I made read and write handlers in which I perform the different actions depending on the offset and the values.

My problem is that when I use qemu.write_memory() in my controller read/write handlers, nothing happens. I can read the memory afterwards and I notice no changes. However, when I modify the memory using the same API after I reach a breakpoint, the memory is modified as expected.

Any idea how I could achieve these memory modifications in my controller?

Here is an extract of my code:

    def do_flash_op(self):
        if self.opcode == FlashOpcode.NONE:
            print("Warning: FlashOp: wrong op code, doing nothing")

        elif self.opcode == FlashOpcode.WRITE or self.opcode == FlashOpcode.ERASE:
            print("Write flash at " + hex(self.flashAddr) + " (" + hex(self.nwords) + " words) in bank " + hex(self.bank))
            for x in range(0, self.nwords):
                print(qemu.write_memory(self.flashAddr + x, 4, self.wordsToWrite[x]))
        else:
            print("Read bank " + str(self.bank) + " (info=" + str(self.isInfo) +") op " + str(self.opcode) + " words " + str(self.nwords) + " at " + hex(self.flashAddr))

    def handle_read(self, offset, size):
        if (offset == 0xc):
            return self.offsetValue
        return 0x00 # TODO: read info bank

    def handle_write(self, offset, size, value):
        if offset == 0x4 or offset == 0x8: # Control register
            if offset == 0x4:
                self.bank = 0
            else:
                self.bank = 1

            if value == 0x31415927: # Erase
                self.opcode = FlashOpcode.ERASE
            elif value == 0x27182818: # Write
                self.opcode = FlashOpcode.WRITE
            elif value == 0x16021765: # Read (only for info bank)
                self.opcode = FlashOpcode.READ
            self.parse_offset()
            self.do_flash_op()
            self.reset_regs()

Thanks

mariusmue commented 2 years ago

Heya,

Thanks for opening the issue! Unfortunately, without a complete example to run as a test case, it's somewhat hard to reproduce, especially as we don't know your target setup.

Either way, from my gut feeling, I think the following is happening:

To move forward, I see 3 ways: a) use the pypanda target and modify memory with the raw=True argument. This uses the pypanda interface and bypasses qemu, hence, allowing to modify the memory either way. b) Before doing the write access, explicitly stop the QemuTarget (Note: I'm not sure if this is working with the current version of avatar2.) c) Adding a new qemu-monitor command to modify memory while the target is running and use this as API instead.

In any case, pretty cool project, so far we haven't worked on flash controller emulation!

max-r-b commented 2 years ago

Thanks for the quick reply :)

I already tested b), but no luck (my Peripheral code stop being executed after I launch qemu.stop()).

I am now looking at solution a), but I get a segfault this time (apparently after reaching SVC 0).

Also using this target (and Panda target as well), a dummy UART peripheral is not working properly: an interrupt should be triggered from the firmware code by writing in a Cortex-M3 specific address (0xe000ef00). I can see instructions corresponding to the write at this address, but I notice no interruption (but this was working as expected with QemuTarget)... The firmware is suppose to set its own interrupt table using one of these Cortex-M specific address, maybe this could explain the crash. Any gut feeling on this issue? :)

For information, I am using the Dockerfile and the version 1.4.7.

mariusmue commented 2 years ago

Regarding b: did you also try with qemu.stop(blacking=False)?

The interrupt issue is very likely because on the stock configurable machine, we do not initialize the interrupt controller - which would be in-line with segfaulting for SVC 0, as this should trigger in interrupt?

For the time being our solution was to always "hack-in" the irq controller by checking against the cpu model, as we did for instance here for pretender or here for FirmWire. While we added this to upstream avatar-qemu for the Cortex-m3, it's not present in our configurable machine port for panda, and definitely something we should contribute back.

For the time being, can you add an according hack to Panda and try building it? It may fix the interrupt/svc issue.

max-r-b commented 2 years ago

Thank you again for the assistance.

Using qemu.stop(blacking=False), I have an exception when calling qemu.cont()

Exception: cont() requested but Target is RUNNING

As for the irq hack, thanks for pointing it out, I didn't know about it. I will see if it is not too difficult to do it for Panda.

mariusmue commented 2 years ago

Sorry, I had a typo, it should've been blocking, not blacking. Either way, for Panda, if you don't succeed please get back here and I can check what I can do.

max-r-b commented 2 years ago

Sorry, I copied your typo here, but I used blocking my script. I end up with the exception from my previous message when I try to call qemu.cont