user2818: I'm trying to program an Arduino Uno (atmega328p) using only the serial Tx/Rx lines (no DTR, so no automatic reset). This is so I can upload code via WiFi/TCP using an ESP-01.
To achieve this, I need to Arduino sketch/application to reset itself (and run the bootloader) whenever it sees avrdude trying to upload code (e.g. when 0 0  appears in the serial port).
I've tried a few approaches to jumping to the bootloader from application code but had no success so far.

Using the watchdog timer

void reset_to_bootloader() {
    // see 11.2.2 MCUCR – MCU Control Register
    // Enable change of interrupt vectors
    MCUCR = (1<<IVCE);
    // Move interrupts to Boot Flash section
    MCUCR = (1<<IVSEL);

    // enable watchdog timer
    wdt_enable(WDTO_30MS);

    // block waiting for the watchdog to timeout and jump to the bootloader
    while(1);
}

Unfortunately, the datasheet[1] mentions the IVSEL bit is not available in atmega328p, so even though this compiles, it simple resets the MCU and the bootloader does not run.

Jumping to the bootloader address directly

There is a table in the datasheet (Table 26-7. Boot Size Configuration, ATmega328P) that specifies the bootloader start address for the 4 supported bootloader section sizes, which are configured by the BOOTSZ fuse.
I have read the fuses and confirmed the bootloader section is 512-bytes (256 words) long.
Therefore, according to the datasheet, the Boot Loader Flash Section is 0x3F00 - 0x3FFF. So, the start of the bootloader should be at word-address 0x3F00.
I have even dumped the whole 32768-byte flash off the MCU and confirmed the bootloader is at byte-address 0x7e00, which matches the expected word-address 0x3F00 (0x7e00 / 2 = 0x3F00).
I have tried a couple methods of jumping to address 0x3F00. Here's a few. None of them seem to work. They just reset the MCU and the bootloader doesn't seem to run.
void jump_to_bootloader() {
    size_t bootloader_addr = 0x3F00;

    void (*bootloader_ptr)() = (void (*)())(bootloader_addr);

    SREG = 0;
    wdt_disable();
    bootloader_ptr();
}

void jump_to_bootloader2() {
    asm volatile ("ijmp" ::"z" (0x3F00));
}


Jumping and updating the value of MCUSR

I then realised the bootloader[4] checks the reset source and only runs on an external reset:
ch = MCUSR;
if (! (ch &  _BV(EXTRF))) // if its a not an external reset...
    pp_start();  // skip bootloader

So I tried faking it before jumping, but it didn't work either:
    MCUSR = _BV(EXTRF);
    asm volatile ("ijmp" ::"z" (0x3F00));
}

Any help is appreciated. Thanks in advance!

edit:

Jumping to after the bootloader checks the reset source

Following @Edgar Bonet's suggestion, avoiding the if (!(ch & _BV(EXTRF))) appStart(); check in the bootloader by jumping to the instruction that follows it:
void jump_to_bootloader2() {
    // the bootloader assumes these are true:
    cli();
    SP = RAMEND;
    SREG = 0;
    MCUSR = 0;
    asm volatile ("eor  __zero_reg__, __zero_reg__");
    asm volatile ("eor  r1, r1");
    // jump to after the bootloader checks the reset source
    asm volatile ("ijmp" ::"z" (0x7e0au / 2));
    while(1);
}

I could see it entered the bootloader (LEDs flashed a couple times), but avrdude was not able to upload code.
The bootloader sets up a watchdog timer (WDT), so I'm suspecting avrdude waits for to long after sending the initial 0 0 0  and since the bootloader does not see anything on the serial port the WDT resets it.

References:

[1] AVR Instruction Set Manual: http://ww1.microchip.com/downloads/en/devicedoc/atmel-0856-avr-instruction-set-manual.pdf
[2] atmega328p datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf
[3] random forum thread I have also read: https://www.avrfreaks.net/forum/jump-bootloader-app-help-needed
[4] bootloader code: https://github.com/sparkfun/Arduino_Boards/blob/805def002e815509032fde37a674d2916af8aecc/sparkfun/avr/bootloaders/atmega/ATmegaBOOT_168.c#L299-L307


user37622: The source of Optiboot on Uno is here.
I would prefer a simple watchdog reset with
  wdt_enable(WDTO_15MS);
  while (true);

But I am not sure now if it runs sketch upload detection in bootloader.
For direct jump to Optiboot on Uno (one flash page) this should work
typedef void (*bootloader_jump_t)();
const bootloader_jump_t bootloader_jump = (bootloader_jump_t)((FLASHEND-511)>>1);

and then you can call bootloader_jump();
EDIT:
while the Optiboot 4 on Uno from factory doesn't check for new upload after watchdog reset or direct jump, Optiboot 8 has checks to handle this right

2818: <p>I'm trying to program an Arduino Uno (atmega328p) using only the serial Tx/Rx lines (no DTR, so no automatic reset). This is so I can upload code via WiFi/TCP using an ESP-01.</p>
<p>To achieve this, I need to Arduino sketch/application to reset itself (and run the bootloader) whenever it sees avrdude trying to upload code (e.g. when <code>0 0 </code> appears in the serial port).</p>
<p>I've tried a few approaches to jumping to the bootloader from application code but had no success so far.</p>
<ol>
<li><strong>Using the watchdog timer</strong></li>
</ol>
<pre><code>void reset_to_bootloader() {
    // see 11.2.2 MCUCR – MCU Control Register
    // Enable change of interrupt vectors
    MCUCR = (1&lt;&lt;IVCE);
    // Move interrupts to Boot Flash section
    MCUCR = (1&lt;&lt;IVSEL);

    // enable watchdog timer
    wdt_enable(WDTO_30MS);

    // block waiting for the watchdog to timeout and jump to the bootloader
    while(1);
}
</code></pre>
<p>Unfortunately, the datasheet[1] mentions the <code>IVSEL</code> bit is not available in atmega328p, so even though this compiles, it simple resets the MCU and the bootloader does not run.</p>
<ol start="2">
<li><strong>Jumping to the bootloader address directly</strong></li>
</ol>
<p>There is a table in the datasheet (Table 26-7. Boot Size Configuration, ATmega328P) that specifies the bootloader start address for the 4 supported bootloader section sizes, which are configured by the BOOTSZ fuse.</p>
<p>I have read the fuses and confirmed the bootloader section is 512-bytes (256 words) long.
Therefore, according to the datasheet, the Boot Loader Flash Section is <code>0x3F00 - 0x3FFF</code>. So, the start of the bootloader should be at word-address 0x3F00.</p>
<p>I have even dumped the whole 32768-byte flash off the MCU and confirmed the bootloader is at byte-address <code>0x7e00</code>, which matches the expected word-address <code>0x3F00</code> (<code>0x7e00 / 2 = 0x3F00</code>).</p>
<p>I have tried a couple methods of jumping to address 0x3F00. Here's a few. None of them seem to work. They just reset the MCU and the bootloader doesn't seem to run.</p>
<pre><code>void jump_to_bootloader() {
    size_t bootloader_addr = 0x3F00;

    void (*bootloader_ptr)() = (void (*)())(bootloader_addr);

    SREG = 0;
    wdt_disable();
    bootloader_ptr();
}

void jump_to_bootloader2() {
    asm volatile (&quot;ijmp&quot; ::&quot;z&quot; (0x3F00));
}
</code></pre>
<ol start="3">
<li><strong>Jumping and updating the value of <code>MCUSR</code></strong></li>
</ol>
<p>I then realised the bootloader[4] checks the reset source and only runs on an external reset:</p>
<pre><code>ch = MCUSR;
if (! (ch &amp;  _BV(EXTRF))) // if its a not an external reset...
    pp_start();  // skip bootloader
</code></pre>
<p>So I tried faking it before jumping, but it didn't work either:</p>
<pre><code>    MCUSR = _BV(EXTRF);
    asm volatile (&quot;ijmp&quot; ::&quot;z&quot; (0x3F00));
}
</code></pre>
<p>Any help is appreciated. Thanks in advance!</p>
<hr />
<p>edit:</p>
<ol start="4">
<li><strong>Jumping to after the bootloader checks the reset source</strong></li>
</ol>
<p>Following @Edgar Bonet's suggestion, avoiding the <code>if (!(ch &amp; _BV(EXTRF))) appStart();</code> check in the bootloader by jumping to the instruction that follows it:</p>
<pre><code>void jump_to_bootloader2() {
    // the bootloader assumes these are true:
    cli();
    SP = RAMEND;
    SREG = 0;
    MCUSR = 0;
    asm volatile (&quot;eor  __zero_reg__, __zero_reg__&quot;);
    asm volatile (&quot;eor  r1, r1&quot;);
    // jump to after the bootloader checks the reset source
    asm volatile (&quot;ijmp&quot; ::&quot;z&quot; (0x7e0au / 2));
    while(1);
}
</code></pre>
<p>I could see it entered the bootloader (LEDs flashed a couple times), but avrdude was not able to upload code.</p>
<p>The bootloader sets up a watchdog timer (WDT), so I'm suspecting avrdude waits for to long after sending the initial <code>0 0 0 </code> and since the bootloader does not see anything on the serial port the WDT resets it.</p>
<hr />
<p>References:</p>
<ul>
<li>[1] AVR Instruction Set Manual: <a href="http://ww1.microchip.com/downloads/en/devicedoc/atmel-0856-avr-instruction-set-manual.pdf" rel="nofollow noreferrer">http://ww1.microchip.com/downloads/en/devicedoc/atmel-0856-avr-instruction-set-manual.pdf</a></li>
<li>[2] atmega328p datasheet: <a href="http://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf" rel="nofollow noreferrer">http://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-7810-Automotive-Microcontrollers-ATmega328P_Datasheet.pdf</a></li>
<li>[3] random forum thread I have also read: <a href="https://www.avrfreaks.net/forum/jump-bootloader-app-help-needed" rel="nofollow noreferrer">https://www.avrfreaks.net/forum/jump-bootloader-app-help-needed</a></li>
<li>[4] bootloader code: <a href="https://github.com/sparkfun/Arduino_Boards/blob/805def002e815509032fde37a674d2916af8aecc/sparkfun/avr/bootloaders/atmega/ATmegaBOOT_168.c#L299-L307" rel="nofollow noreferrer">https://github.com/sparkfun/Arduino_Boards/blob/805def002e815509032fde37a674d2916af8aecc/sparkfun/avr/bootloaders/atmega/ATmegaBOOT_168.c#L299-L307</a></li>
</ul>
 37622: <p>The source of Optiboot on Uno is <a href="https://github.com/arduino/ArduinoCore-avr/blob/master/bootloaders/optiboot/optiboot.c" rel="nofollow noreferrer">here</a>.</p>
<p>I would prefer a simple watchdog reset with</p>
<pre><code>  wdt_enable(WDTO_15MS);
  while (true);
</code></pre>
<p>But I am not sure now if it runs sketch upload detection in bootloader.</p>
<p>For direct jump to Optiboot on Uno (one flash page) this should work</p>
<pre><code>typedef void (*bootloader_jump_t)();
const bootloader_jump_t bootloader_jump = (bootloader_jump_t)((FLASHEND-511)&gt;&gt;1);
</code></pre>
<p>and then you can call <code>bootloader_jump();</code></p>
<p>EDIT:
while the Optiboot 4 on Uno from factory doesn't check for new upload after watchdog reset or direct jump, Optiboot 8 has checks to handle this right</p>
