SpenceKonde / megaTinyCore

Arduino core for the tinyAVR 0/1/2-series - Ones's digit 2,4,5,7 (pincount, 8,14,20,24), tens digit 0, 1, or 2 (featureset), preceded by flash in kb. Library maintainers: porting help available!
Other
551 stars 143 forks source link

Can't chip erase with prog.py #817

Closed Azmisov closed 1 year ago

Azmisov commented 1 year ago

Not sure exactly how it happened, but I've got an attiny424 that got into a locked state and can't be programmed anymore. I'm trying to do a chip erase with prog.py. Calling: python3 prog.py -d attiny424 -a erase -u /dev/ttyUSB0 And the logs:

Version 1.2.3 - Jan 2022
Using serial port /dev/ttyUSB0 at 115200 baud.
Target: attiny424
Action: erase
pymcuprog.nvm - ERROR - Device is locked.
Error:
Failed to enter NVM programming mode
Pinging device...
Device ID mismatch! Stopping.

I think pymcu._start_session shouldn't have returned STATUS_SUCCESS, since the device was locked (if it didn't return success, it would have dropped into the chip erase logic). Digging through pymcuprog included in megatinycore, looks like the culprit is in Backend.start_session -> self.programmer.setup_device -> get_nvm_access_provider -> NvmAccessProviderSerial.__init__ -> self.avr.enter_progmode(). The final call fails (device doesn't unlock before timeout); but the error is caught. Everything else runs fine unwinding the stack to Backend.start_session. Looks like a PymcuprogDeviceLockedError should have been raised somewhere, but is not. I think the solution is to raise raise PymcuprogDeviceLockedError in the try-except block for that enter_progmode() call (see nvmserialupdi.py).

(Note I think the Device ID mismatch error is misleading, and I suspect just shows up because the NVM programmer couldn't unlock the device and couldn't get an ID)

So after adding that change, I get:

Version 1.2.3 - Jan 2022
Using serial port /dev/ttyUSB0 at 115200 baud.
Target: attiny424
Action: erase
pymcuprog.serialupdi.application - ERROR - Timeout waiting for device to unlock
pymcuprog.nvm - ERROR - Device is locked.
Error:
Failed to enter NVM programming mode: device is locked
The device is in a locked state and is not accessible; a chip erase is required.
Locked AVR UPDI devices can:
 - be unlocked using command: erase --chip-erase-locked-device
 - write user row values using command: write --user-row-locked-device
Locked state detected, performing chip erase
pymcuprog.nvm - ERROR - Device is locked.
Error:
Failed to enter NVM programming mode
The device is in a locked state and is not accessible; a chip erase is required.
Locked AVR UPDI devices can:
 - be unlocked using command: erase --chip-erase-locked-device
 - write user row values using command: write --user-row-locked-device
Error:

Looks like the args_start.chip_erase_locked_device is not being used for NvmAccessProviderSerial class. It only seems to be used by Searching the files, looks like it is only used in NvmAccessProviderCmsisDapUpdi. Does the chip erase logic need to be ported to NvmAccessProviderSerial? Or perhaps I should be calling this with different args so that NvmAccessProviderCmsisDapUpdi gets used instead?

In any case, in the process of digging further into pymcuprog, it happened to resolve itself:

Version 1.2.3 - Jan 2022
Using serial port /dev/ttyUSB0 at 115200 baud.
Target: attiny424
Action: erase
connecting to serial port: /dev/ttyUSB0
pymcuprog.nvm - ERROR - Device is locked.
Error:
Failed to enter NVM programming mode
The device is in a locked state and is not accessible; a chip erase is required.
Locked AVR UPDI devices can:
 - be unlocked using command: erase --chip-erase-locked-device
 - write user row values using command: write --user-row-locked-device
Locked state detected, performing chip erase
Pinging device...
Ping response: 1E922C
Chip/Bulk erase,
Memory type eeprom is conditionally erased (depending upon EESAVE fuse setting)
Memory type flash is always erased
Memory type lockbits is always erased
...
Erased.
Action took 0.01s

No other changes necessary, it just happened to get lucky and fix itself. I think maybe there was a problem with an ISR or something, and so the device appeared to be locked when it was just super slow to respond to the serial programmer (?). E.g. note that in the success case, you don't get the ERROR - Device is locked. message anymore.

So perhaps the timeout for enter_progmode() (currently 100ms) should be made higher for my particular case. (Edit: Nevermind, after running into the same problem I put this up to 10s and it still says device locked; so that doesn't appear to help). Though I also suspect that if my tiny were actually locked, it would have continued to fail and you'd need to implement some code to actually handle chip_erase_locked_device in NvmAccessProviderSerial. As it stands, turning that flag on in the chip-erase branch of prog.py doesn't actually do anything.

Azmisov commented 1 year ago

See pull request which fixes it for me. Also just FYI, the problem was because I had interrupts enabled for the same port as UPDI pin was on, but I did not have the ISR defined. The interrupt got triggered when UPDI started and that must have locked the device with the nonexistent ISR.

SpenceKonde commented 1 year ago

Well, if this works, that's pretty damned awesome. That was one of the Three Missing Features.

SpenceKonde commented 1 year ago

And that is very interesting. Here is the seqence of events

2-3 clocks from the the interrupt to reach the vector. That's a JMP to BADISR, and almost certainly relaxed to an rjmp. So to clock cycles later, it hits another rjmp, this one to 0x0000. where the CPU would arrive 6-7 clocks from the initial insult. A few very early init tasks would be done, then init3 would be reached, which would then issue a software reset..... to clear the detected dirty reset. I suspect that without the reset, you'd have been able to reprogram.

SpenceKonde commented 1 year ago

That implies that it should also be possible to get the same soft brick by uploading this sketch.

#include <util/delay.h>
int main() {
  PORTA.PIN0CTRL=0x03; //tinyAVR
  //PORTF.PIN7CTRL=0x03; //DD. 
}
ISR(PORTA_PORT_vect) { 
//ISR(PORTF_PORT_vect) { //DD
   __PROTECTED_WRITE(RSTCTRL.SWRR, 1);
}

If that's true, it's rather gross.

Waaait..... waaaaaaiitt is this true?

Can I do fucking ersatz reset for tiny1-series parts ON THE UPDI PIN??? And A) it will reset the part (I did not relize you could do interrupts on the UPDI pin when it's acting as a updi pin) and B) that doing this will foil a programmer that cant do chip erase, but C) can be overcome with updi programmer as long as it does chip erase correctly?

That is would be huge.

That would let you get virtual autoreset for all 0/1-series and 2-series 14-pin parts with unmodified hardware using the same PCBs I sell for using with UPDI fused as reset and optiboot loaded, except you'd be able to disconnect the autoreset bridge and UPDII program it? And all it would cost is the PORTA_PORT_vect?!? If that is true, that has been in my hopeless dream category since day one, because magically we would suddenly have a way to turn UPDI or RESET pin into UPDI and RESET...

Azmisov commented 1 year ago

I can't get the soft-brick with your code, but it will brick with this:

void setup(){
  PORTA.DIRCLR = 0b1; // pa0 input
  PORTA.INTFLAGS = 0b1; // clear pa0 interrupts
  PORTA.PIN0CTRL = PORT_PULLUPEN_bm | PORT_ISC_BOTHEDGES_gc;
}
ISR(PORTA_PORT_vect){
  if (PORTA.INTFLAGS & 0b1)
    _PROTECTED_WRITE(RSTCTRL.SWRR, 1);
}
void loop(){}

If I do without setup/loop, it doesn't brick. I don't know enough about the differences between main vs setup/loop to comment. Admittedly, this is my first time programming a microcontroller in several years.

I actually didn't realize you couldn't do GPIO and UPDI at the same time. And it was working for both, before I had commented out the ISR. I guess I got lucky since I was using it as input-pullup for a button, same as UPDI. I suppose whenever the button was pressed (longer than 200ns), it triggered the UPDI enable flow, temporarily driving low, then reverting to input-pullup to wait for SYNCH. Since no SYNCH came from the button press, UPDI didn't get enabled.

SpenceKonde commented 1 year ago

It's increasingly looking like the UPDI pin, when fused as that, is still fully usable as an input, provided your input don't bear a dangerous resemblance to UPDI commands function as an input pin.

OH, duh, of course my code couldn't manifest it, interrupts start off disabled,

int main() {
  sei();
   PORTA.PIN0CTRL=0x03; //tinyAVR
  //PORTF.PIN7CTRL=0x03; //DD. 
}
ISR(PORTA_PORT_vect) { 
//ISR(PORTF_PORT_vect) { //DD
   __PROTECTED_WRITE(RSTCTRL.SWRR, 1);
}