From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-7.5 required=3.0 tests=BAYES_00,DKIM_ADSP_CUSTOM_MED, DKIM_INVALID,DKIM_SIGNED,FREEMAIL_FORGED_FROMDOMAIN,FREEMAIL_FROM, HEADER_FROM_DIFFERENT_DOMAINS,HTML_MESSAGE,INCLUDES_CR_TRAILER,INCLUDES_PATCH, MAILING_LIST_MULTI,SPF_HELO_NONE,SPF_PASS autolearn=no autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id E2303C433B4 for ; Fri, 14 May 2021 07:22:15 +0000 (UTC) Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by mail.kernel.org (Postfix) with ESMTPS id 20CE4610A7 for ; Fri, 14 May 2021 07:22:15 +0000 (UTC) DMARC-Filter: OpenDMARC Filter v1.3.2 mail.kernel.org 20CE4610A7 Authentication-Results: mail.kernel.org; dmarc=fail (p=none dis=none) header.from=gmail.com Authentication-Results: mail.kernel.org; spf=pass smtp.mailfrom=qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Received: from localhost ([::1]:47894 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1lhS9G-000386-2S for qemu-devel@archiver.kernel.org; Fri, 14 May 2021 03:22:14 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]:47962) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1lhS8F-0002TP-Vo for qemu-devel@nongnu.org; Fri, 14 May 2021 03:21:13 -0400 Received: from mail-qk1-x730.google.com ([2607:f8b0:4864:20::730]:34493) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1lhS89-0000O9-NQ for qemu-devel@nongnu.org; Fri, 14 May 2021 03:21:11 -0400 Received: by mail-qk1-x730.google.com with SMTP id v8so9945218qkv.1 for ; Fri, 14 May 2021 00:21:03 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=mime-version:references:in-reply-to:from:date:message-id:subject:to :cc; bh=7MnkegemQbcE5ZstgmKNwYEWraRxcX7XeCp1Mi1t9tg=; b=aFHzaqDN/MwAt1ZK6rJRzCurOaIff4vM/WlXsedtZuKKROihMqqugREr5l/Vi6kr8t F9MSk2N6HMHlpXwHvvTxjZQg4hm8pvkcOYzWXwUxwouzK0zEi3XRDz+qwXk4Gt+AUkBq NOlKpB4q3S5lq3ds2bjVlZxvmZ25PBCnXlcZOjwZYseoAO0rrOsGMxsKNAhD+Ce7yZA1 jmHz7+jkoGm3MAtXMvJJIYN8beSV1QloV4wnBFd0e3DcCtOUAvymBvr4lYhN1g/EcfNU pctJ4niJOPb5z4otFfRsZTU2N4Su7ubfBgEPbiZ59DUTYi2YWmfIiJO+RPmQx4zBf4GT ryAg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:mime-version:references:in-reply-to:from:date :message-id:subject:to:cc; bh=7MnkegemQbcE5ZstgmKNwYEWraRxcX7XeCp1Mi1t9tg=; b=oSxK+iY4z+hY3X0RZeplzLaWf3GGDAr3JXdoLZ+2pMlC7yGPhTsnZ2U/1K+SSrlEoG GgnO8iP4U7bWzL5ynC+hGsYaYFWkKmz8Kpw0arpiwzvg37QFxGLxZigXpAXk0bixYkAO F3xIzgPCzOMxkTPQA6tmn1adktTxyNweSE3ezwlIzVNwaydhFh2QhEY5Q51B/zVWuIOk ar9Q59DjBcp/2UtocWRdSQjzFxqEuHR3IHHHZvaaSlofPtxhLmI2hhiYVhhFho8P4qlc SzqvBokRPjltyXcHzCQQT59/5MqZnYe005Q1CM9Bmm4jlCQ1LIj8EkdMPtiOxhGSnR0m AfSg== X-Gm-Message-State: AOAM532cYe0hk0t3+65U+CJVRlTsPXcHauWxr8hYiKdRA8CskMXnCeWF Ed4UTEVPEVFGUju6GhQMwMyq1fUW6OkRqwMc37w= X-Google-Smtp-Source: ABdhPJxwrNFXqJ1fVq1+KstZCNXDZfdFdXczF72Cl6zcvdgk0kFW2/SKwAy2NxbbJioaxJiWDe+A2UtYNYqtMzPlSMA= X-Received: by 2002:a05:620a:1344:: with SMTP id c4mr42148255qkl.489.1620976862372; Fri, 14 May 2021 00:21:02 -0700 (PDT) MIME-Version: 1.0 References: <20210505211849.101005-1-mrolnik@gmail.com> <20210505211849.101005-2-mrolnik@gmail.com> In-Reply-To: From: Michael Rolnik Date: Fri, 14 May 2021 10:20:48 +0300 Message-ID: Subject: Re: [RFC 1/1] Implement AVR watchdog timer To: Pavel Dovgalyuk Content-Type: multipart/alternative; boundary="0000000000001226dd05c2451654" Received-SPF: pass client-ip=2607:f8b0:4864:20::730; envelope-from=mrolnik@gmail.com; helo=mail-qk1-x730.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, HTML_MESSAGE=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.23 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Cc: Joaquin de Andres , Fred Konrad , Richard Henderson , QEMU Developers , =?UTF-8?Q?Philippe_Mathieu=2DDaud=C3=A9?= Errors-To: qemu-devel-bounces+qemu-devel=archiver.kernel.org@nongnu.org Sender: "Qemu-devel" --0000000000001226dd05c2451654 Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable Ok, thanks. Sent from my cell phone, please ignore typos On Thu, May 13, 2021, 3:27 PM Pavel Dovgalyuk wrote: > On 06.05.2021 00:18, Michael Rolnik wrote: > > Signed-off-by: Michael Rolnik > > --- > > MAINTAINERS | 2 + > > hw/avr/Kconfig | 1 + > > hw/avr/atmega.c | 15 +- > > hw/avr/atmega.h | 2 + > > hw/watchdog/Kconfig | 3 + > > hw/watchdog/avr_wdt.c | 274 +++++++++++++++++++++++++++++++++= + > > hw/watchdog/meson.build | 2 + > > hw/watchdog/trace-events | 5 + > > include/hw/watchdog/avr_wdt.h | 47 ++++++ > > target/avr/cpu.c | 3 + > > target/avr/cpu.h | 1 + > > target/avr/helper.c | 7 +- > > target/avr/translate.c | 38 ++++- > > 13 files changed, 391 insertions(+), 9 deletions(-) > > create mode 100644 hw/watchdog/avr_wdt.c > > create mode 100644 include/hw/watchdog/avr_wdt.h > > > > diff --git a/MAINTAINERS b/MAINTAINERS > > index 4c05ff8bba..e1fce736d2 100644 > > --- a/MAINTAINERS > > +++ b/MAINTAINERS > > @@ -1052,6 +1052,8 @@ F: include/hw/timer/avr_timer16.h > > F: hw/timer/avr_timer16.c > > F: include/hw/misc/avr_power.h > > F: hw/misc/avr_power.c > > +F: include/hw/watchdog/avr_wdt.h > > +F: hw/watchdog/avr_wdt.c > > > > Arduino > > M: Philippe Mathieu-Daud=C3=A9 > > diff --git a/hw/avr/Kconfig b/hw/avr/Kconfig > > index d31298c3cc..9939e4902f 100644 > > --- a/hw/avr/Kconfig > > +++ b/hw/avr/Kconfig > > @@ -3,6 +3,7 @@ config AVR_ATMEGA_MCU > > select AVR_TIMER16 > > select AVR_USART > > select AVR_POWER > > + select AVR_WDT > > > > config ARDUINO > > select AVR_ATMEGA_MCU > > diff --git a/hw/avr/atmega.c b/hw/avr/atmega.c > > index 44c6afebbb..31ceb1c21c 100644 > > --- a/hw/avr/atmega.c > > +++ b/hw/avr/atmega.c > > @@ -28,6 +28,7 @@ enum AtmegaPeripheral { > > GPIOG, GPIOH, GPIOI, GPIOJ, GPIOK, GPIOL, > > USART0, USART1, USART2, USART3, > > TIMER0, TIMER1, TIMER2, TIMER3, TIMER4, TIMER5, > > + WDT, > > PERIFMAX > > }; > > > > @@ -75,6 +76,7 @@ static const peripheral_cfg dev168_328[PERIFMAX] =3D = { > > [GPIOD] =3D { 0x29 }, > > [GPIOC] =3D { 0x26 }, > > [GPIOB] =3D { 0x23 }, > > + [WDT] =3D { 0x60 }, > > }, dev1280_2560[PERIFMAX] =3D { > > [USART3] =3D { 0x130, POWER1, 2 }, > > [TIMER5] =3D { 0x120, POWER1, 5, 0x73, 0x3a, true }, > > @@ -99,6 +101,7 @@ static const peripheral_cfg dev168_328[PERIFMAX] =3D= { > > [GPIOC] =3D { 0x26 }, > > [GPIOB] =3D { 0x23 }, > > [GPIOA] =3D { 0x20 }, > > + [WDT] =3D { 0x60 }, > > }; > > > > enum AtmegaIrq { > > @@ -118,6 +121,7 @@ enum AtmegaIrq { > > TIMER4_COMPC_IRQ, TIMER4_OVF_IRQ, > > TIMER5_CAPT_IRQ, TIMER5_COMPA_IRQ, TIMER5_COMPB_IRQ, > > TIMER5_COMPC_IRQ, TIMER5_OVF_IRQ, > > + WATCHDOG_TIMER_IRQ, > > IRQ_COUNT > > }; > > > > @@ -133,6 +137,7 @@ enum AtmegaIrq { > > #define TIMER_OVF_IRQ(n) (n * TIMER_IRQ_COUNT + TIMER0_OVF_IRQ) > > > > static const uint8_t irq168_328[IRQ_COUNT] =3D { > > + [WATCHDOG_TIMER_IRQ] =3D 7, > > [TIMER2_COMPA_IRQ] =3D 8, > > [TIMER2_COMPB_IRQ] =3D 9, > > [TIMER2_OVF_IRQ] =3D 10, > > @@ -147,6 +152,7 @@ static const uint8_t irq168_328[IRQ_COUNT] =3D { > > [USART0_DRE_IRQ] =3D 20, > > [USART0_TXC_IRQ] =3D 21, > > }, irq1280_2560[IRQ_COUNT] =3D { > > + [WATCHDOG_TIMER_IRQ] =3D 13, > > [TIMER2_COMPA_IRQ] =3D 14, > > [TIMER2_COMPB_IRQ] =3D 15, > > [TIMER2_OVF_IRQ] =3D 16, > > @@ -344,10 +350,17 @@ static void atmega_realize(DeviceState *dev, Erro= r > **errp) > > g_free(devname); > > } > > > > + /* Watchdog Timer */ > > + object_initialize_child(OBJECT(dev), "wdt", &s->wdt, TYPE_AVR_WDT)= ; > > + sysbus_realize(SYS_BUS_DEVICE(&s->wdt), &error_abort); > > + sysbus_mmio_map(SYS_BUS_DEVICE(&s->wdt), 0, > > + OFFSET_DATA + mc->dev[WDT].addr); > > + qdev_connect_gpio_out_named(cpudev, "wdr", 0, > > + qdev_get_gpio_in_named(DEVICE(&s->wdt), "wdr", 0))= ; > > + > > create_unimplemented_device("avr-twi", OFFSET_DATA + > 0x0b8, 6); > > create_unimplemented_device("avr-adc", OFFSET_DATA + > 0x078, 8); > > create_unimplemented_device("avr-ext-mem-ctrl", OFFSET_DATA + > 0x074, 2); > > - create_unimplemented_device("avr-watchdog", OFFSET_DATA + > 0x060, 1); > > create_unimplemented_device("avr-spi", OFFSET_DATA + > 0x04c, 3); > > create_unimplemented_device("avr-eeprom", OFFSET_DATA + > 0x03f, 3); > > } > > diff --git a/hw/avr/atmega.h b/hw/avr/atmega.h > > index a99ee15c7e..60bbd44bdd 100644 > > --- a/hw/avr/atmega.h > > +++ b/hw/avr/atmega.h > > @@ -13,6 +13,7 @@ > > > > #include "hw/char/avr_usart.h" > > #include "hw/timer/avr_timer16.h" > > +#include "hw/watchdog/avr_wdt.h" > > #include "hw/misc/avr_power.h" > > #include "target/avr/cpu.h" > > #include "qom/object.h" > > @@ -45,6 +46,7 @@ struct AtmegaMcuState { > > AVRMaskState pwr[POWER_MAX]; > > AVRUsartState usart[USART_MAX]; > > AVRTimer16State timer[TIMER_MAX]; > > + AVRWatchdogState wdt; > > uint64_t xtal_freq_hz; > > }; > > > > diff --git a/hw/watchdog/Kconfig b/hw/watchdog/Kconfig > > index 66e1d029e3..e0f89d2fe0 100644 > > --- a/hw/watchdog/Kconfig > > +++ b/hw/watchdog/Kconfig > > @@ -20,3 +20,6 @@ config WDT_IMX2 > > > > config WDT_SBSA > > bool > > + > > +config AVR_WDT > > + bool > > diff --git a/hw/watchdog/avr_wdt.c b/hw/watchdog/avr_wdt.c > > new file mode 100644 > > index 0000000000..cbd6457c8b > > --- /dev/null > > +++ b/hw/watchdog/avr_wdt.c > > @@ -0,0 +1,274 @@ > > +/* > > + * AVR watchdog > > + * > > + * Copyright (c) 2021 Michael Rolnik > > + * > > + * This library is free software; you can redistribute it and/or > > + * modify it under the terms of the GNU Lesser General Public > > + * License as published by the Free Software Foundation; either > > + * version 2.1 of the License, or (at your option) any later version. > > + * > > + * This library is distributed in the hope that it will be useful, > > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > > + * Lesser General Public License for more details. > > + * > > + * You should have received a copy of the GNU Lesser General Public > > + * License along with this library; if not, see > > + * > > + */ > > + > > +#include "qemu/osdep.h" > > +#include "qapi/error.h" > > +#include "qemu/log.h" > > +#include "hw/irq.h" > > +#include "hw/watchdog/avr_wdt.h" > > +#include "trace.h" > > +#include "target/avr/cpu.h" > > +#include "sysemu/runstate.h" > > + > > +/* Field masks */ > > +#define WDTCSR_MASK_WDP0 0x01 > > +#define WDTCSR_MASK_WDP1 0x02 > > +#define WDTCSR_MASK_WDP2 0x04 > > +#define WDTCSR_MASK_WDE 0x08 > > +#define WDTCSR_MASK_WDCE 0x10 > > +#define WDTCSR_MASK_WDP3 0x20 > > +#define WDTCSR_MASK_WDIE 0x40 > > +#define WDTCSR_MASK_WDIF 0x80 > > + > > +#define WDTCSR_SHFT_WDP0 0x00 > > +#define WDTCSR_SHFT_WDP1 0x01 > > +#define WDTCSR_SHFT_WDP2 0x02 > > +#define WDTCSR_SHFT_WDE 0x03 > > +#define WDTCSR_SHFT_WDCE 0x04 > > +#define WDTCSR_SHFT_WDP3 0x05 > > +#define WDTCSR_SHFT_WDIE 0x06 > > +#define WDTCSR_SHFT_WDIF 0x07 > > + > > +#define MCUSR_MASK_WDRF 0x04 > > +#define MCUSR_ADDR (OFFSET_DATA + 0x55) > > + > > +/* Helper macros */ > > +#define WDP0(csr) ((csr & WDTCSR_MASK_WDP0) >> WDTCSR_SHFT_WDP0) > > +#define WDP1(csr) ((csr & WDTCSR_MASK_WDP1) >> WDTCSR_SHFT_WDP1) > > +#define WDP2(csr) ((csr & WDTCSR_MASK_WDP2) >> WDTCSR_SHFT_WDP2) > > +#define WDP3(csr) ((csr & WDTCSR_MASK_WDP3) >> WDTCSR_SHFT_WDP3) > > +#define WDP(csr) ((WDP3(csr) << 3) | (WDP2(csr) << 2) | \ > > + (WDP1(csr) << 1) | (WDP0(csr) << 0)) > > +#define WDIE(csr) ((csr & WDTCSR_MASK_WDIE) >> WDTCSR_SHFT_WDIE) > > +#define WDE(csr) ((csr & WDTCSR_MASK_WDE) >> WDTCSR_SHFT_WDE) > > +#define WCE(csr) ((csr & WDTCSR_MASK_WCE) >> WDTCSR_SHFT_WCE) > > + > > +#define DB_PRINT(fmt, args...) /* Nothing */ > > + > > +#define MS2NS(n) ((n) * 1000000ull) > > I think there is SCALE_MS define for that. > > > + > > +static void set_bits(uint8_t *addr, uint8_t bits) > > +{ > > + *addr |=3D bits; > > +} > > + > > +static void rst_bits(uint8_t *addr, uint8_t bits) > > +{ > > + *addr &=3D ~bits; > > +} > > + > > +static void avr_wdt_reset_alarm(AVRWatchdogState *wdt) > > +{ > > + uint32_t csr =3D wdt->csr; > > + int wdp =3D WDP(csr); > > + > > + if (WDIE(csr) =3D=3D 0 && WDE(csr) =3D=3D 0) { > > + /* watchdog is stopped */ > > + return; > > + } > > + > > + timer_mod_ns(wdt->timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + > > + (MS2NS(15) << wdp)); > > +} > > + > > +static void avr_wdt_interrupt(void *opaque) > > +{ > > + AVRWatchdogState *wdt =3D opaque; > > + int8_t csr =3D wdt->csr; > > + > > + if (WDIE(csr) =3D=3D 1) { > > + /* Interrupt Mode */ > > + set_bits(&wdt->csr, WDTCSR_MASK_WDIF); > > + qemu_set_irq(wdt->irq, 1); > > + trace_avr_wdt_interrupt(); > > + rst_bits(&wdt->csr, WDTCSR_MASK_WDIF | WDTCSR_MASK_WDIE); > > + } > > + > > + if (WDE(csr) =3D=3D 1) { > > + /* System Reset Mode */ > > + qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET); > > + } > > + > > + avr_wdt_reset_alarm(wdt); > > +} > > + > > +static void avr_wdt_reset(DeviceState *dev) > > +{ > > + AVRWatchdogState *wdt =3D AVR_WDT(dev); > > + > > + wdt->csr =3D 0; > > + qemu_set_irq(wdt->irq, 0); > > + avr_wdt_reset_alarm(wdt); > > +} > > + > > +static uint64_t avr_wdt_read(void *opaque, hwaddr offset, unsigned siz= e) > > +{ > > + assert(size =3D=3D 1); > > + AVRWatchdogState *wdt =3D opaque; > > + uint8_t retval =3D wdt->csr; > > + > > + trace_avr_wdt_read(offset, retval); > > + > > + return (uint64_t)retval; > > +} > > + > > +static void avr_wdt_write(void *opaque, hwaddr offset, > > + uint64_t val64, unsigned size) > > +{ > > + assert(size =3D=3D 1); > > + AVRWatchdogState *wdt =3D opaque; > > + uint8_t val =3D (uint8_t)val64; > > + uint8_t set1 =3D val; /* bits that should be set to 1 */ > > + uint8_t set0 =3D ~val;/* bits that should be set to 0 */ > > + uint8_t mcusr =3D 0; > > + > > + /* > > + * Bit 7 - WDIF: Watchdog Interrupt Flag > > + * This bit is set when a time-out occurs in the Watchdog Timer > and the > > + * Watchdog Timer is configured for interrupt. WDIF is cleared by > hardware > > + * when executing the corresponding interrupt handling vector. > > + * Alternatively, WDIF is cleared by writing a logic one to the > flag. > > + * When the I-bit in SREG and WDIE are set, the Watchdog Time-out > Interrupt > > + * is executed. > > + */ > > + if (val & WDTCSR_MASK_WDIF) { > > + rst_bits(&set1, WDTCSR_MASK_WDIF); /* don't set 1 */ > > + set_bits(&set0, WDTCSR_MASK_WDIF); /* set 0 */ > > + } else { > > + rst_bits(&set1, WDTCSR_MASK_WDIF); /* don't set 1 */ > > + rst_bits(&set0, WDTCSR_MASK_WDIF); /* don't set 0 */ > > + } > > + > > + /* > > + * Bit 4 - WDCE: Watchdog Change Enable > > + * This bit is used in timed sequences for changing WDE and > prescaler > > + * bits. To clear the WDE bit, and/or change the prescaler bits, > > + * WDCE must be set. > > + * Once written to one, hardware will clear WDCE after four clock > cycles. > > + */ > > + if (!(val & WDTCSR_MASK_WDCE)) { > > + uint8_t bits =3D WDTCSR_MASK_WDE | WDTCSR_MASK_WDP0 | > WDTCSR_MASK_WDP1 | > > + WDTCSR_MASK_WDP2 | WDTCSR_MASK_WDP3; > > + rst_bits(&set1, bits); > > + rst_bits(&set0, bits); > > + } > > + > > + /* > > + * Bit 3 - WDE: Watchdog System Reset Enable > > + * WDE is overridden by WDRF in MCUSR. This means that WDE is > always set > > + * when WDRF is set. To clear WDE, WDRF must be cleared first. Th= is > > + * feature ensures multiple resets during conditions causing > failure, and > > + * a safe start-up after the failure. > > + */ > > + cpu_physical_memory_read(MCUSR_ADDR, &mcusr, sizeof(mcusr)); > > + if (mcusr & MCUSR_MASK_WDRF) { > > + set_bits(&set1, WDTCSR_MASK_WDE); > > + rst_bits(&set0, WDTCSR_MASK_WDE); > > + } > > + > > + /* update CSR value */ > > + assert((set0 & set1) =3D=3D 0); > > + > > + val =3D wdt->csr; > > + set_bits(&val, set1); > > + rst_bits(&val, set0); > > + wdt->csr =3D val; > > + trace_avr_wdt_write(offset, val); > > + avr_wdt_reset_alarm(wdt); > > + > > + /* > > + * Bit 6 - WDIE: Watchdog Interrupt Enable > > + * When this bit is written to one and the I-bit in the Status > Register is > > + * set, the Watchdog Interrupt is enabled. If WDE is cleared in > > + * combination with this setting, the Watchdog Timer is in > Interrupt Mode, > > + * and the corresponding interrupt is executed if time-out in the > Watchdog > > + * Timer occurs. > > + * If WDE is set, the Watchdog Timer is in Interrupt and System > Reset Mode. > > + * The first time-out in the Watchdog Timer will set WDIF. > Executing the > > + * corresponding interrupt vector will clear WDIE and WDIF > automatically by > > + * hardware (the Watchdog goes to System Reset Mode). This is > useful for > > + * keeping the Watchdog Timer security while using the interrupt. > To stay > > + * in Interrupt and System Reset Mode, WDIE must be set after eac= h > > + * interrupt. This should however not be done within the interrup= t > service > > + * routine itself, as this might compromise the safety-function o= f > the > > + * Watchdog System Reset mode. If the interrupt is not executed > before the > > + * next time-out, a System Reset will be applied. > > + */ > > + if ((val & WDTCSR_MASK_WDIE) && (wdt->csr & WDTCSR_MASK_WDIF)) { > > + avr_wdt_interrupt(opaque); > > + } > > +} > > + > > +static const MemoryRegionOps avr_wdt_ops =3D { > > + .read =3D avr_wdt_read, > > + .write =3D avr_wdt_write, > > + .endianness =3D DEVICE_NATIVE_ENDIAN, > > + .impl =3D {.max_access_size =3D 1} > > +}; > > + > > +static void avr_wdt_wdr(void *opaque, int irq, int level) > > +{ > > + AVRWatchdogState *wdt =3D AVR_WDT(opaque); > > + > > + avr_wdt_reset_alarm(wdt); > > +} > > + > > +static void avr_wdt_init(Object *obj) > > +{ > > + AVRWatchdogState *s =3D AVR_WDT(obj); > > + > > + sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq); > > + > > + memory_region_init_io(&s->iomem, obj, &avr_wdt_ops, > > + s, "avr-wdt", 0xa); > > + > > + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem); > > + qdev_init_gpio_in_named(DEVICE(s), avr_wdt_wdr, "wdr", 1); > > +} > > + > > +static void avr_wdt_realize(DeviceState *dev, Error **errp) > > +{ > > + AVRWatchdogState *s =3D AVR_WDT(dev); > > + > > + s->timer =3D timer_new_ns(QEMU_CLOCK_VIRTUAL, avr_wdt_interrupt, s= ); > > +} > > + > > +static void avr_wdt_class_init(ObjectClass *klass, void *data) > > +{ > > + DeviceClass *dc =3D DEVICE_CLASS(klass); > > + > > + dc->reset =3D avr_wdt_reset; > > + dc->realize =3D avr_wdt_realize; > > +} > > + > > +static const TypeInfo avr_wdt_info =3D { > > + .name =3D TYPE_AVR_WDT, > > + .parent =3D TYPE_SYS_BUS_DEVICE, > > + .instance_size =3D sizeof(AVRWatchdogState), > > + .instance_init =3D avr_wdt_init, > > + .class_init =3D avr_wdt_class_init, > > +}; > > + > > +static void avr_wdt_register_types(void) > > +{ > > + type_register_static(&avr_wdt_info); > > +} > > + > > +type_init(avr_wdt_register_types) > > diff --git a/hw/watchdog/meson.build b/hw/watchdog/meson.build > > index 054c403dea..8db2be8317 100644 > > --- a/hw/watchdog/meson.build > > +++ b/hw/watchdog/meson.build > > @@ -6,3 +6,5 @@ softmmu_ss.add(when: 'CONFIG_WDT_DIAG288', if_true: > files('wdt_diag288.c')) > > softmmu_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: > files('wdt_aspeed.c')) > > softmmu_ss.add(when: 'CONFIG_WDT_IMX2', if_true: files('wdt_imx2.c')) > > softmmu_ss.add(when: 'CONFIG_WDT_SBSA', if_true: files('sbsa_gwdt.c')= ) > > + > > +specific_ss.add(when: 'CONFIG_AVR_WDT', if_true: files('avr_wdt.c')) > > diff --git a/hw/watchdog/trace-events b/hw/watchdog/trace-events > > index 3124ca1f1b..ac14773179 100644 > > --- a/hw/watchdog/trace-events > > +++ b/hw/watchdog/trace-events > > @@ -5,3 +5,8 @@ cmsdk_apb_watchdog_read(uint64_t offset, uint64_t data, > unsigned size) "CMSDK AP > > cmsdk_apb_watchdog_write(uint64_t offset, uint64_t data, unsigned > size) "CMSDK APB watchdog write: offset 0x%" PRIx64 " data 0x%" PRIx64 " > size %u" > > cmsdk_apb_watchdog_reset(void) "CMSDK APB watchdog: reset" > > cmsdk_apb_watchdog_lock(uint32_t lock) "CMSDK APB watchdog: lock %" > PRIu32 > > + > > +# avr_wdt.c > > +avr_wdt_read(uint8_t addr, uint8_t value) "wdt read addr:%u value:%u" > > +avr_wdt_write(uint8_t addr, uint8_t value) "wdt write addr:%u value:%u= " > > +avr_wdt_interrupt(void) "" > > diff --git a/include/hw/watchdog/avr_wdt.h > b/include/hw/watchdog/avr_wdt.h > > new file mode 100644 > > index 0000000000..8433663013 > > --- /dev/null > > +++ b/include/hw/watchdog/avr_wdt.h > > @@ -0,0 +1,47 @@ > > +/* > > + * AVR watchdog > > + * > > + * Copyright (c) 2021 Michael Rolnik > > + * > > + * This library is free software; you can redistribute it and/or > > + * modify it under the terms of the GNU Lesser General Public > > + * License as published by the Free Software Foundation; either > > + * version 2.1 of the License, or (at your option) any later version. > > + * > > + * This library is distributed in the hope that it will be useful, > > + * but WITHOUT ANY WARRANTY; without even the implied warranty of > > + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU > > + * Lesser General Public License for more details. > > + * > > + * You should have received a copy of the GNU Lesser General Public > > + * License along with this library; if not, see > > + * > > + */ > > + > > +#ifndef HW_WATCHDOG_AVR_WDT_H > > +#define HW_WATCHDOG_AVR_WDT_H > > + > > +#include "hw/sysbus.h" > > +#include "qemu/timer.h" > > +#include "hw/hw.h" > > +#include "qom/object.h" > > + > > +#define TYPE_AVR_WDT "avr-wdt" > > +OBJECT_DECLARE_SIMPLE_TYPE(AVRWatchdogState, AVR_WDT) > > + > > +struct AVRWatchdogState { > > + /* */ > > + SysBusDevice parent_obj; > > + > > + /* */ > > + MemoryRegion iomem; > > + MemoryRegion imsk_iomem; > > + MemoryRegion ifr_iomem; > > + QEMUTimer *timer; > > + qemu_irq irq; > > + > > + /* registers */ > > + uint8_t csr; > > +}; > > + > > You should add vmstate which includes timer and csr. It will allow > reverse debugging available with icount enabled. > > > +#endif /* HW_WATCHDOG_AVR_WDT_H */ > > diff --git a/target/avr/cpu.c b/target/avr/cpu.c > > index 0f4596932b..d5eb785833 100644 > > --- a/target/avr/cpu.c > > +++ b/target/avr/cpu.c > > @@ -131,6 +131,9 @@ static void avr_cpu_initfn(Object *obj) > > /* Set the number of interrupts supported by the CPU. */ > > qdev_init_gpio_in(DEVICE(cpu), avr_cpu_set_int, > > sizeof(cpu->env.intsrc) * 8); > > + > > + /* register watchdog timer reset interrupt */ > > + qdev_init_gpio_out_named(DEVICE(cpu), &cpu->wdr, "wdr", 1); > > } > > > > static ObjectClass *avr_cpu_class_by_name(const char *cpu_model) > > diff --git a/target/avr/cpu.h b/target/avr/cpu.h > > index d148e8c75a..f8f5641c8b 100644 > > --- a/target/avr/cpu.h > > +++ b/target/avr/cpu.h > > @@ -152,6 +152,7 @@ typedef struct AVRCPU { > > > > CPUNegativeOffsetState neg; > > CPUAVRState env; > > + qemu_irq wdr; /* reset WDT */ > > } AVRCPU; > > > > extern const struct VMStateDescription vms_avr_cpu; > > diff --git a/target/avr/helper.c b/target/avr/helper.c > > index 35e1019594..dd88057e5f 100644 > > --- a/target/avr/helper.c > > +++ b/target/avr/helper.c > > @@ -24,6 +24,7 @@ > > #include "exec/exec-all.h" > > #include "exec/address-spaces.h" > > #include "exec/helper-proto.h" > > +#include "hw/irq.h" > > > > bool avr_cpu_exec_interrupt(CPUState *cs, int interrupt_request) > > { > > @@ -188,11 +189,9 @@ void helper_break(CPUAVRState *env) > > > > void helper_wdr(CPUAVRState *env) > > { > > - CPUState *cs =3D env_cpu(env); > > + AVRCPU *cpu =3D env_archcpu(env); > > > > - /* WD is not implemented yet, placeholder */ > > - cs->exception_index =3D EXCP_DEBUG; > > - cpu_loop_exit(cs); > > + qemu_set_irq(cpu->wdr, 1); > > } > > > > /* > > diff --git a/target/avr/translate.c b/target/avr/translate.c > > index 850c5941d9..b7b716f4a0 100644 > > --- a/target/avr/translate.c > > +++ b/target/avr/translate.c > > @@ -1411,6 +1411,9 @@ static bool trans_SBIC(DisasContext *ctx, arg_SBI= C > *a) > > { > > TCGv temp =3D tcg_const_i32(a->reg); > > > > + if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) { > > + gen_io_start(); > > + } > > gen_helper_inb(temp, cpu_env, temp); > > tcg_gen_andi_tl(temp, temp, 1 << a->bit); > > ctx->skip_cond =3D TCG_COND_EQ; > > Translation should stop at inb-related instructions in icount mode, > because icount is valid only at the end of the block. Reading it in the > middle makes execution non-deterministic. > > > @@ -1429,6 +1432,9 @@ static bool trans_SBIS(DisasContext *ctx, arg_SBI= S > *a) > > { > > TCGv temp =3D tcg_const_i32(a->reg); > > > > + if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) { > > + gen_io_start(); > > + } > > gen_helper_inb(temp, cpu_env, temp); > > tcg_gen_andi_tl(temp, temp, 1 << a->bit); > > ctx->skip_cond =3D TCG_COND_NE; > > @@ -1611,6 +1617,9 @@ static TCGv gen_get_zaddr(void) > > static void gen_data_store(DisasContext *ctx, TCGv data, TCGv addr) > > { > > if (ctx->tb->flags & TB_FLAGS_FULL_ACCESS) { > > + if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) { > > + gen_io_start(); > > + } > > gen_helper_fullwr(cpu_env, data, addr); > > } else { > > tcg_gen_qemu_st8(data, addr, MMU_DATA_IDX); /* mem[addr] =3D > data */ > > @@ -1620,6 +1629,9 @@ static void gen_data_store(DisasContext *ctx, TCG= v > data, TCGv addr) > > static void gen_data_load(DisasContext *ctx, TCGv data, TCGv addr) > > { > > if (ctx->tb->flags & TB_FLAGS_FULL_ACCESS) { > > + if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) { > > + gen_io_start(); > > + } > > gen_helper_fullrd(data, cpu_env, addr); > > } else { > > tcg_gen_qemu_ld8u(data, addr, MMU_DATA_IDX); /* data =3D > mem[addr] */ > > @@ -2325,6 +2337,9 @@ static bool trans_IN(DisasContext *ctx, arg_IN *a= ) > > TCGv Rd =3D cpu_r[a->rd]; > > TCGv port =3D tcg_const_i32(a->imm); > > > > + if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) { > > + gen_io_start(); > > + } > > gen_helper_inb(Rd, cpu_env, port); > > > > tcg_temp_free_i32(port); > > @@ -2341,6 +2356,9 @@ static bool trans_OUT(DisasContext *ctx, arg_OUT > *a) > > TCGv Rd =3D cpu_r[a->rd]; > > TCGv port =3D tcg_const_i32(a->imm); > > > > + if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) { > > + gen_io_start(); > > + } > > gen_helper_outb(cpu_env, port, Rd); > > > > tcg_temp_free_i32(port); > > @@ -2641,6 +2659,9 @@ static bool trans_SBI(DisasContext *ctx, arg_SBI > *a) > > TCGv data =3D tcg_temp_new_i32(); > > TCGv port =3D tcg_const_i32(a->reg); > > > > + if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) { > > + gen_io_start(); > > + } > > gen_helper_inb(data, cpu_env, port); > > tcg_gen_ori_tl(data, data, 1 << a->bit); > > gen_helper_outb(cpu_env, port, data); > > @@ -2660,6 +2681,9 @@ static bool trans_CBI(DisasContext *ctx, arg_CBI > *a) > > TCGv data =3D tcg_temp_new_i32(); > > TCGv port =3D tcg_const_i32(a->reg); > > > > + if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) { > > + gen_io_start(); > > + } > > gen_helper_inb(data, cpu_env, port); > > tcg_gen_andi_tl(data, data, ~(1 << a->bit)); > > gen_helper_outb(cpu_env, port, data); > > @@ -2830,6 +2854,9 @@ static bool trans_SLEEP(DisasContext *ctx, > arg_SLEEP *a) > > */ > > static bool trans_WDR(DisasContext *ctx, arg_WDR *a) > > { > > + if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) { > > + gen_io_start(); > > + } > > gen_helper_wdr(cpu_env); > > > > return true; > > @@ -2919,6 +2946,9 @@ void gen_intermediate_code(CPUState *cs, > TranslationBlock *tb, int max_insns) > > * This flag is set by ST/LD instruction we will regenerate i= t > ONLY > > * with mem/cpu memory access instead of mem access > > */ > > + if (tb_cflags(tb) & CF_USE_ICOUNT) { > > + gen_io_start(); > > + } > > max_insns =3D 1; > > } > > if (ctx.singlestep) { > > @@ -2955,6 +2985,10 @@ void gen_intermediate_code(CPUState *cs, > TranslationBlock *tb, int max_insns) > > goto done_generating; > > } > > > > + if (num_insns =3D=3D max_insns && (tb_cflags(tb) & CF_LAST_IO)= ) { > > + gen_io_start(); > > + } > > + > > /* Conditionally skip the next instruction, if indicated. */ > > if (ctx.skip_cond !=3D TCG_COND_NEVER) { > > skip_label =3D gen_new_label(); > > @@ -2998,10 +3032,6 @@ void gen_intermediate_code(CPUState *cs, > TranslationBlock *tb, int max_insns) > > && (ctx.npc - pc_start) * 2 < TARGET_PAGE_SIZE - 4 > > && !tcg_op_buf_full()); > > > > - if (tb->cflags & CF_LAST_IO) { > > - gen_io_end(); > > - } > > - > > bool nonconst_skip =3D canonicalize_skip(&ctx); > > > > switch (ctx.bstate) { > > > > --0000000000001226dd05c2451654 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
Ok, thanks.

Sent from my cell phone, please ignore typos

On Thu, May 13, 2021= , 3:27 PM Pavel Dovgalyuk <= pavel.dovgalyuk@ispras.ru> wrote:
On 06.05.2021 00:18, Michael Rolnik wrote:
> Signed-off-by: Michael Rolnik <mrolnik@gmail.com>
> ---
>=C2=A0 =C2=A0MAINTAINERS=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0 =C2=A0 =C2=A0 =C2=A0|=C2=A0 =C2=A02 +
>=C2=A0 =C2=A0hw/avr/Kconfig=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 |=C2=A0 =C2=A01 +
>=C2=A0 =C2=A0hw/avr/atmega.c=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0|=C2=A0 15 +-
>=C2=A0 =C2=A0hw/avr/atmega.h=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0|=C2=A0 =C2=A02 +
>=C2=A0 =C2=A0hw/watchdog/Kconfig=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0|=C2=A0 =C2=A03 +
>=C2=A0 =C2=A0hw/watchdog/avr_wdt.c=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0| 2= 74 ++++++++++++++++++++++++++++++++++
>=C2=A0 =C2=A0hw/watchdog/meson.build=C2=A0 =C2=A0 =C2=A0 =C2=A0|=C2=A0 = =C2=A02 +
>=C2=A0 =C2=A0hw/watchdog/trace-events=C2=A0 =C2=A0 =C2=A0 |=C2=A0 =C2= =A05 +
>=C2=A0 =C2=A0include/hw/watchdog/avr_wdt.h |=C2=A0 47 ++++++
>=C2=A0 =C2=A0target/avr/cpu.c=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 |=C2=A0 =C2=A03 +
>=C2=A0 =C2=A0target/avr/cpu.h=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 |=C2=A0 =C2=A01 +
>=C2=A0 =C2=A0target/avr/helper.c=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2= =A0|=C2=A0 =C2=A07 +-
>=C2=A0 =C2=A0target/avr/translate.c=C2=A0 =C2=A0 =C2=A0 =C2=A0 |=C2=A0 = 38 ++++-
>=C2=A0 =C2=A013 files changed, 391 insertions(+), 9 deletions(-)
>=C2=A0 =C2=A0create mode 100644 hw/watchdog/avr_wdt.c
>=C2=A0 =C2=A0create mode 100644 include/hw/watchdog/avr_wdt.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 4c05ff8bba..e1fce736d2 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1052,6 +1052,8 @@ F: include/hw/timer/avr_timer16.h
>=C2=A0 =C2=A0F: hw/timer/avr_timer16.c
>=C2=A0 =C2=A0F: include/hw/misc/avr_power.h
>=C2=A0 =C2=A0F: hw/misc/avr_power.c
> +F: include/hw/watchdog/avr_wdt.h
> +F: hw/watchdog/avr_wdt.c
>=C2=A0 =C2=A0
>=C2=A0 =C2=A0Arduino
>=C2=A0 =C2=A0M: Philippe Mathieu-Daud=C3=A9 <f4bug@amsat.org> > diff --git a/hw/avr/Kconfig b/hw/avr/Kconfig
> index d31298c3cc..9939e4902f 100644
> --- a/hw/avr/Kconfig
> +++ b/hw/avr/Kconfig
> @@ -3,6 +3,7 @@ config AVR_ATMEGA_MCU
>=C2=A0 =C2=A0 =C2=A0 =C2=A0select AVR_TIMER16
>=C2=A0 =C2=A0 =C2=A0 =C2=A0select AVR_USART
>=C2=A0 =C2=A0 =C2=A0 =C2=A0select AVR_POWER
> +=C2=A0 =C2=A0 select AVR_WDT
>=C2=A0 =C2=A0
>=C2=A0 =C2=A0config ARDUINO
>=C2=A0 =C2=A0 =C2=A0 =C2=A0select AVR_ATMEGA_MCU
> diff --git a/hw/avr/atmega.c b/hw/avr/atmega.c
> index 44c6afebbb..31ceb1c21c 100644
> --- a/hw/avr/atmega.c
> +++ b/hw/avr/atmega.c
> @@ -28,6 +28,7 @@ enum AtmegaPeripheral {
>=C2=A0 =C2=A0 =C2=A0 =C2=A0GPIOG, GPIOH, GPIOI, GPIOJ, GPIOK, GPIOL, >=C2=A0 =C2=A0 =C2=A0 =C2=A0USART0, USART1, USART2, USART3,
>=C2=A0 =C2=A0 =C2=A0 =C2=A0TIMER0, TIMER1, TIMER2, TIMER3, TIMER4, TIME= R5,
> +=C2=A0 =C2=A0 WDT,
>=C2=A0 =C2=A0 =C2=A0 =C2=A0PERIFMAX
>=C2=A0 =C2=A0};
>=C2=A0 =C2=A0
> @@ -75,6 +76,7 @@ static const peripheral_cfg dev168_328[PERIFMAX] =3D= {
>=C2=A0 =C2=A0 =C2=A0 =C2=A0[GPIOD]=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0=3D= {=C2=A0 0x29 },
>=C2=A0 =C2=A0 =C2=A0 =C2=A0[GPIOC]=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0=3D= {=C2=A0 0x26 },
>=C2=A0 =C2=A0 =C2=A0 =C2=A0[GPIOB]=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0=3D= {=C2=A0 0x23 },
> +=C2=A0 =C2=A0 [WDT]=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0=3D {=C2= =A0 0x60 },
>=C2=A0 =C2=A0}, dev1280_2560[PERIFMAX] =3D {
>=C2=A0 =C2=A0 =C2=A0 =C2=A0[USART3]=C2=A0 =C2=A0 =C2=A0 =C2=A0 =3D { 0x= 130, POWER1, 2 },
>=C2=A0 =C2=A0 =C2=A0 =C2=A0[TIMER5]=C2=A0 =C2=A0 =C2=A0 =C2=A0 =3D { 0x= 120, POWER1, 5, 0x73, 0x3a, true },
> @@ -99,6 +101,7 @@ static const peripheral_cfg dev168_328[PERIFMAX] = =3D {
>=C2=A0 =C2=A0 =C2=A0 =C2=A0[GPIOC]=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0=3D= {=C2=A0 0x26 },
>=C2=A0 =C2=A0 =C2=A0 =C2=A0[GPIOB]=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0=3D= {=C2=A0 0x23 },
>=C2=A0 =C2=A0 =C2=A0 =C2=A0[GPIOA]=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0=3D= {=C2=A0 0x20 },
> +=C2=A0 =C2=A0 [WDT]=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0=3D {=C2= =A0 0x60 },
>=C2=A0 =C2=A0};
>=C2=A0 =C2=A0
>=C2=A0 =C2=A0enum AtmegaIrq {
> @@ -118,6 +121,7 @@ enum AtmegaIrq {
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0TIMER4_COMPC_IRQ, TIMER4_OVF_I= RQ,
>=C2=A0 =C2=A0 =C2=A0 =C2=A0TIMER5_CAPT_IRQ, TIMER5_COMPA_IRQ, TIMER5_CO= MPB_IRQ,
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0TIMER5_COMPC_IRQ, TIMER5_OVF_I= RQ,
> +=C2=A0 =C2=A0 WATCHDOG_TIMER_IRQ,
>=C2=A0 =C2=A0 =C2=A0 =C2=A0IRQ_COUNT
>=C2=A0 =C2=A0};
>=C2=A0 =C2=A0
> @@ -133,6 +137,7 @@ enum AtmegaIrq {
>=C2=A0 =C2=A0#define TIMER_OVF_IRQ(n)=C2=A0 =C2=A0 (n * TIMER_IRQ_COUNT= + TIMER0_OVF_IRQ)
>=C2=A0 =C2=A0
>=C2=A0 =C2=A0static const uint8_t irq168_328[IRQ_COUNT] =3D {
> +=C2=A0 =C2=A0 [WATCHDOG_TIMER_IRQ]=C2=A0 =C2=A0 =3D 7,
>=C2=A0 =C2=A0 =C2=A0 =C2=A0[TIMER2_COMPA_IRQ]=C2=A0 =C2=A0 =C2=A0 =3D 8= ,
>=C2=A0 =C2=A0 =C2=A0 =C2=A0[TIMER2_COMPB_IRQ]=C2=A0 =C2=A0 =C2=A0 =3D 9= ,
>=C2=A0 =C2=A0 =C2=A0 =C2=A0[TIMER2_OVF_IRQ]=C2=A0 =C2=A0 =C2=A0 =C2=A0 = =3D 10,
> @@ -147,6 +152,7 @@ static const uint8_t irq168_328[IRQ_COUNT] =3D { >=C2=A0 =C2=A0 =C2=A0 =C2=A0[USART0_DRE_IRQ]=C2=A0 =C2=A0 =C2=A0 =C2=A0 = =3D 20,
>=C2=A0 =C2=A0 =C2=A0 =C2=A0[USART0_TXC_IRQ]=C2=A0 =C2=A0 =C2=A0 =C2=A0 = =3D 21,
>=C2=A0 =C2=A0}, irq1280_2560[IRQ_COUNT] =3D {
> +=C2=A0 =C2=A0 [WATCHDOG_TIMER_IRQ]=C2=A0 =C2=A0 =3D 13,
>=C2=A0 =C2=A0 =C2=A0 =C2=A0[TIMER2_COMPA_IRQ]=C2=A0 =C2=A0 =C2=A0 =3D 1= 4,
>=C2=A0 =C2=A0 =C2=A0 =C2=A0[TIMER2_COMPB_IRQ]=C2=A0 =C2=A0 =C2=A0 =3D 1= 5,
>=C2=A0 =C2=A0 =C2=A0 =C2=A0[TIMER2_OVF_IRQ]=C2=A0 =C2=A0 =C2=A0 =C2=A0 = =3D 16,
> @@ -344,10 +350,17 @@ static void atmega_realize(DeviceState *dev, Err= or **errp)
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0g_free(devname);
>=C2=A0 =C2=A0 =C2=A0 =C2=A0}
>=C2=A0 =C2=A0
> +=C2=A0 =C2=A0 /* Watchdog Timer */
> +=C2=A0 =C2=A0 object_initialize_child(OBJECT(dev), "wdt", &= amp;s->wdt, TYPE_AVR_WDT);
> +=C2=A0 =C2=A0 sysbus_realize(SYS_BUS_DEVICE(&s->wdt), &err= or_abort);
> +=C2=A0 =C2=A0 sysbus_mmio_map(SYS_BUS_DEVICE(&s->wdt), 0,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= OFFSET_DATA + mc->dev[WDT].addr);
> +=C2=A0 =C2=A0 qdev_connect_gpio_out_named(cpudev, "wdr", 0,=
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= qdev_get_gpio_in_named(DEVICE(&s->wdt), "wdr", 0));
> +
>=C2=A0 =C2=A0 =C2=A0 =C2=A0create_unimplemented_device("avr-twi&qu= ot;,=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 OFFSET_DATA + 0x0b8, 6);
>=C2=A0 =C2=A0 =C2=A0 =C2=A0create_unimplemented_device("avr-adc&qu= ot;,=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 OFFSET_DATA + 0x078, 8);
>=C2=A0 =C2=A0 =C2=A0 =C2=A0create_unimplemented_device("avr-ext-me= m-ctrl", OFFSET_DATA + 0x074, 2);
> -=C2=A0 =C2=A0 create_unimplemented_device("avr-watchdog",= =C2=A0 =C2=A0 =C2=A0OFFSET_DATA + 0x060, 1);
>=C2=A0 =C2=A0 =C2=A0 =C2=A0create_unimplemented_device("avr-spi&qu= ot;,=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 OFFSET_DATA + 0x04c, 3);
>=C2=A0 =C2=A0 =C2=A0 =C2=A0create_unimplemented_device("avr-eeprom= ",=C2=A0 =C2=A0 =C2=A0 =C2=A0OFFSET_DATA + 0x03f, 3);
>=C2=A0 =C2=A0}
> diff --git a/hw/avr/atmega.h b/hw/avr/atmega.h
> index a99ee15c7e..60bbd44bdd 100644
> --- a/hw/avr/atmega.h
> +++ b/hw/avr/atmega.h
> @@ -13,6 +13,7 @@
>=C2=A0 =C2=A0
>=C2=A0 =C2=A0#include "hw/char/avr_usart.h"
>=C2=A0 =C2=A0#include "hw/timer/avr_timer16.h"
> +#include "hw/watchdog/avr_wdt.h"
>=C2=A0 =C2=A0#include "hw/misc/avr_power.h"
>=C2=A0 =C2=A0#include "target/avr/cpu.h"
>=C2=A0 =C2=A0#include "qom/object.h"
> @@ -45,6 +46,7 @@ struct AtmegaMcuState {
>=C2=A0 =C2=A0 =C2=A0 =C2=A0AVRMaskState pwr[POWER_MAX];
>=C2=A0 =C2=A0 =C2=A0 =C2=A0AVRUsartState usart[USART_MAX];
>=C2=A0 =C2=A0 =C2=A0 =C2=A0AVRTimer16State timer[TIMER_MAX];
> +=C2=A0 =C2=A0 AVRWatchdogState wdt;
>=C2=A0 =C2=A0 =C2=A0 =C2=A0uint64_t xtal_freq_hz;
>=C2=A0 =C2=A0};
>=C2=A0 =C2=A0
> diff --git a/hw/watchdog/Kconfig b/hw/watchdog/Kconfig
> index 66e1d029e3..e0f89d2fe0 100644
> --- a/hw/watchdog/Kconfig
> +++ b/hw/watchdog/Kconfig
> @@ -20,3 +20,6 @@ config WDT_IMX2
>=C2=A0 =C2=A0
>=C2=A0 =C2=A0config WDT_SBSA
>=C2=A0 =C2=A0 =C2=A0 =C2=A0bool
> +
> +config AVR_WDT
> +=C2=A0 =C2=A0 bool
> diff --git a/hw/watchdog/avr_wdt.c b/hw/watchdog/avr_wdt.c
> new file mode 100644
> index 0000000000..cbd6457c8b
> --- /dev/null
> +++ b/hw/watchdog/avr_wdt.c
> @@ -0,0 +1,274 @@
> +/*
> + * AVR watchdog
> + *
> + * Copyright (c) 2021 Michael Rolnik
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.=
> + *
> + * This library is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.=C2=A0 See the= GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public > + * License along with this library; if not, see
> + * <http://www.gnu.org/licenses/lgpl-2.= 1.html>
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qapi/error.h"
> +#include "qemu/log.h"
> +#include "hw/irq.h"
> +#include "hw/watchdog/avr_wdt.h"
> +#include "trace.h"
> +#include "target/avr/cpu.h"
> +#include "sysemu/runstate.h"
> +
> +/* Field masks */
> +#define WDTCSR_MASK_WDP0=C2=A0 =C2=A0 0x01
> +#define WDTCSR_MASK_WDP1=C2=A0 =C2=A0 0x02
> +#define WDTCSR_MASK_WDP2=C2=A0 =C2=A0 0x04
> +#define WDTCSR_MASK_WDE=C2=A0 =C2=A0 =C2=A00x08
> +#define WDTCSR_MASK_WDCE=C2=A0 =C2=A0 0x10
> +#define WDTCSR_MASK_WDP3=C2=A0 =C2=A0 0x20
> +#define WDTCSR_MASK_WDIE=C2=A0 =C2=A0 0x40
> +#define WDTCSR_MASK_WDIF=C2=A0 =C2=A0 0x80
> +
> +#define WDTCSR_SHFT_WDP0=C2=A0 =C2=A0 0x00
> +#define WDTCSR_SHFT_WDP1=C2=A0 =C2=A0 0x01
> +#define WDTCSR_SHFT_WDP2=C2=A0 =C2=A0 0x02
> +#define WDTCSR_SHFT_WDE=C2=A0 =C2=A0 =C2=A00x03
> +#define WDTCSR_SHFT_WDCE=C2=A0 =C2=A0 0x04
> +#define WDTCSR_SHFT_WDP3=C2=A0 =C2=A0 0x05
> +#define WDTCSR_SHFT_WDIE=C2=A0 =C2=A0 0x06
> +#define WDTCSR_SHFT_WDIF=C2=A0 =C2=A0 0x07
> +
> +#define MCUSR_MASK_WDRF=C2=A0 =C2=A0 =C2=A00x04
> +#define MCUSR_ADDR=C2=A0 =C2=A0 =C2=A0 (OFFSET_DATA + 0x55)
> +
> +/* Helper macros */
> +#define WDP0(csr)=C2=A0 =C2=A0 =C2=A0 =C2=A0((csr & WDTCSR_MASK_W= DP0) >> WDTCSR_SHFT_WDP0)
> +#define WDP1(csr)=C2=A0 =C2=A0 =C2=A0 =C2=A0((csr & WDTCSR_MASK_W= DP1) >> WDTCSR_SHFT_WDP1)
> +#define WDP2(csr)=C2=A0 =C2=A0 =C2=A0 =C2=A0((csr & WDTCSR_MASK_W= DP2) >> WDTCSR_SHFT_WDP2)
> +#define WDP3(csr)=C2=A0 =C2=A0 =C2=A0 =C2=A0((csr & WDTCSR_MASK_W= DP3) >> WDTCSR_SHFT_WDP3)
> +#define WDP(csr)=C2=A0 =C2=A0 =C2=A0 =C2=A0 ((WDP3(csr) << 3) |= (WDP2(csr) << 2) | \
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0(WDP1(csr) << 1) | (WDP0(csr) << 0))
> +#define WDIE(csr)=C2=A0 =C2=A0 =C2=A0 =C2=A0((csr & WDTCSR_MASK_W= DIE) >> WDTCSR_SHFT_WDIE)
> +#define WDE(csr)=C2=A0 =C2=A0 =C2=A0 =C2=A0 ((csr & WDTCSR_MASK_W= DE) >> WDTCSR_SHFT_WDE)
> +#define WCE(csr)=C2=A0 =C2=A0 =C2=A0 =C2=A0 ((csr & WDTCSR_MASK_W= CE) >> WDTCSR_SHFT_WCE)
> +
> +#define DB_PRINT(fmt, args...) /* Nothing */
> +
> +#define MS2NS(n)=C2=A0 =C2=A0 =C2=A0 =C2=A0 ((n) * 1000000ull)

I think there is SCALE_MS define for that.

> +
> +static void set_bits(uint8_t *addr, uint8_t bits)
> +{
> +=C2=A0 =C2=A0 *addr |=3D bits;
> +}
> +
> +static void rst_bits(uint8_t *addr, uint8_t bits)
> +{
> +=C2=A0 =C2=A0 *addr &=3D ~bits;
> +}
> +
> +static void avr_wdt_reset_alarm(AVRWatchdogState *wdt)
> +{
> +=C2=A0 =C2=A0 uint32_t csr =3D wdt->csr;
> +=C2=A0 =C2=A0 int wdp =3D WDP(csr);
> +
> +=C2=A0 =C2=A0 if (WDIE(csr) =3D=3D 0 && WDE(csr) =3D=3D 0) {<= br> > +=C2=A0 =C2=A0 =C2=A0 =C2=A0 /* watchdog is stopped */
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 return;
> +=C2=A0 =C2=A0 }
> +
> +=C2=A0 =C2=A0 timer_mod_ns(wdt->timer, qemu_clock_get_ns(QEMU_CLOC= K_VIRTUAL) +
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 (MS2NS(15) << wdp));<= br> > +}
> +
> +static void avr_wdt_interrupt(void *opaque)
> +{
> +=C2=A0 =C2=A0 AVRWatchdogState *wdt =3D opaque;
> +=C2=A0 =C2=A0 int8_t csr =3D wdt->csr;
> +
> +=C2=A0 =C2=A0 if (WDIE(csr) =3D=3D 1) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 /* Interrupt Mode */
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 set_bits(&wdt->csr, WDTCSR_MASK_WD= IF);
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 qemu_set_irq(wdt->irq, 1);
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 trace_avr_wdt_interrupt();
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 rst_bits(&wdt->csr, WDTCSR_MASK_WD= IF | WDTCSR_MASK_WDIE);
> +=C2=A0 =C2=A0 }
> +
> +=C2=A0 =C2=A0 if (WDE(csr) =3D=3D 1) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 /* System Reset Mode */
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 qemu_system_reset_request(SHUTDOWN_CAUSE_= GUEST_RESET);
> +=C2=A0 =C2=A0 }
> +
> +=C2=A0 =C2=A0 avr_wdt_reset_alarm(wdt);
> +}
> +
> +static void avr_wdt_reset(DeviceState *dev)
> +{
> +=C2=A0 =C2=A0 AVRWatchdogState *wdt =3D AVR_WDT(dev);
> +
> +=C2=A0 =C2=A0 wdt->csr =3D 0;
> +=C2=A0 =C2=A0 qemu_set_irq(wdt->irq, 0);
> +=C2=A0 =C2=A0 avr_wdt_reset_alarm(wdt);
> +}
> +
> +static uint64_t avr_wdt_read(void *opaque, hwaddr offset, unsigned si= ze)
> +{
> +=C2=A0 =C2=A0 assert(size =3D=3D 1);
> +=C2=A0 =C2=A0 AVRWatchdogState *wdt =3D opaque;
> +=C2=A0 =C2=A0 uint8_t retval =3D wdt->csr;
> +
> +=C2=A0 =C2=A0 trace_avr_wdt_read(offset, retval);
> +
> +=C2=A0 =C2=A0 return (uint64_t)retval;
> +}
> +
> +static void avr_wdt_write(void *opaque, hwaddr offset,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 uint64_t val64, unsigned size)
> +{
> +=C2=A0 =C2=A0 assert(size =3D=3D 1);
> +=C2=A0 =C2=A0 AVRWatchdogState *wdt =3D opaque;
> +=C2=A0 =C2=A0 uint8_t val =3D (uint8_t)val64;
> +=C2=A0 =C2=A0 uint8_t set1 =3D val; /* bits that should be set to 1 *= /
> +=C2=A0 =C2=A0 uint8_t set0 =3D ~val;/* bits that should be set to 0 *= /
> +=C2=A0 =C2=A0 uint8_t mcusr =3D 0;
> +
> +=C2=A0 =C2=A0 /*
> +=C2=A0 =C2=A0 =C2=A0*=C2=A0 Bit 7 - WDIF: Watchdog Interrupt Flag
> +=C2=A0 =C2=A0 =C2=A0*=C2=A0 This bit is set when a time-out occurs in= the Watchdog Timer and the
> +=C2=A0 =C2=A0 =C2=A0*=C2=A0 Watchdog Timer is configured for interrup= t. WDIF is cleared by hardware
> +=C2=A0 =C2=A0 =C2=A0*=C2=A0 when executing the corresponding interrup= t handling vector.
> +=C2=A0 =C2=A0 =C2=A0*=C2=A0 Alternatively, WDIF is cleared by writing= a logic one to the flag.
> +=C2=A0 =C2=A0 =C2=A0*=C2=A0 When the I-bit in SREG and WDIE are set, = the Watchdog Time-out Interrupt
> +=C2=A0 =C2=A0 =C2=A0*=C2=A0 is executed.
> +=C2=A0 =C2=A0 =C2=A0*/
> +=C2=A0 =C2=A0 if (val & WDTCSR_MASK_WDIF) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 rst_bits(&set1, WDTCSR_MASK_WDIF);=C2= =A0 /* don't set 1 */
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 set_bits(&set0, WDTCSR_MASK_WDIF);=C2= =A0 /* set 0 */
> +=C2=A0 =C2=A0 } else {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 rst_bits(&set1, WDTCSR_MASK_WDIF);=C2= =A0 /* don't set 1 */
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 rst_bits(&set0, WDTCSR_MASK_WDIF);=C2= =A0 /* don't set 0 */
> +=C2=A0 =C2=A0 }
> +
> +=C2=A0 =C2=A0 /*
> +=C2=A0 =C2=A0 =C2=A0*=C2=A0 Bit 4 - WDCE: Watchdog Change Enable
> +=C2=A0 =C2=A0 =C2=A0*=C2=A0 This bit is used in timed sequences for c= hanging WDE and prescaler
> +=C2=A0 =C2=A0 =C2=A0*=C2=A0 bits. To clear the WDE bit, and/or change= the prescaler bits,
> +=C2=A0 =C2=A0 =C2=A0*=C2=A0 WDCE must be set.
> +=C2=A0 =C2=A0 =C2=A0*=C2=A0 Once written to one, hardware will clear = WDCE after four clock cycles.
> +=C2=A0 =C2=A0 =C2=A0*/
> +=C2=A0 =C2=A0 if (!(val & WDTCSR_MASK_WDCE)) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 uint8_t bits =3D WDTCSR_MASK_WDE | WDTCSR= _MASK_WDP0 | WDTCSR_MASK_WDP1 |
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0WDTCSR_MASK_WDP2 | WDTCSR_MASK_WDP3;
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 rst_bits(&set1, bits);
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 rst_bits(&set0, bits);
> +=C2=A0 =C2=A0 }
> +
> +=C2=A0 =C2=A0 /*
> +=C2=A0 =C2=A0 =C2=A0*=C2=A0 Bit 3 - WDE: Watchdog System Reset Enable=
> +=C2=A0 =C2=A0 =C2=A0*=C2=A0 WDE is overridden by WDRF in MCUSR. This = means that WDE is always set
> +=C2=A0 =C2=A0 =C2=A0*=C2=A0 when WDRF is set. To clear WDE, WDRF must= be cleared first. This
> +=C2=A0 =C2=A0 =C2=A0*=C2=A0 feature ensures multiple resets during co= nditions causing failure, and
> +=C2=A0 =C2=A0 =C2=A0*=C2=A0 a safe start-up after the failure.
> +=C2=A0 =C2=A0 =C2=A0*/
> +=C2=A0 =C2=A0 cpu_physical_memory_read(MCUSR_ADDR, &mcusr, sizeof= (mcusr));
> +=C2=A0 =C2=A0 if (mcusr & MCUSR_MASK_WDRF) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 set_bits(&set1, WDTCSR_MASK_WDE);
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 rst_bits(&set0, WDTCSR_MASK_WDE);
> +=C2=A0 =C2=A0 }
> +
> +=C2=A0 =C2=A0 /*=C2=A0 update CSR value */
> +=C2=A0 =C2=A0 assert((set0 & set1) =3D=3D 0);
> +
> +=C2=A0 =C2=A0 val =3D wdt->csr;
> +=C2=A0 =C2=A0 set_bits(&val, set1);
> +=C2=A0 =C2=A0 rst_bits(&val, set0);
> +=C2=A0 =C2=A0 wdt->csr =3D val;
> +=C2=A0 =C2=A0 trace_avr_wdt_write(offset, val);
> +=C2=A0 =C2=A0 avr_wdt_reset_alarm(wdt);
> +
> +=C2=A0 =C2=A0 /*
> +=C2=A0 =C2=A0 =C2=A0*=C2=A0 Bit 6 - WDIE: Watchdog Interrupt Enable > +=C2=A0 =C2=A0 =C2=A0*=C2=A0 When this bit is written to one and the I= -bit in the Status Register is
> +=C2=A0 =C2=A0 =C2=A0*=C2=A0 set, the Watchdog Interrupt is enabled. I= f WDE is cleared in
> +=C2=A0 =C2=A0 =C2=A0*=C2=A0 combination with this setting, the Watchd= og Timer is in Interrupt Mode,
> +=C2=A0 =C2=A0 =C2=A0*=C2=A0 and the corresponding interrupt is execut= ed if time-out in the Watchdog
> +=C2=A0 =C2=A0 =C2=A0*=C2=A0 Timer occurs.
> +=C2=A0 =C2=A0 =C2=A0*=C2=A0 If WDE is set, the Watchdog Timer is in I= nterrupt and System Reset Mode.
> +=C2=A0 =C2=A0 =C2=A0*=C2=A0 The first time-out in the Watchdog Timer = will set WDIF. Executing the
> +=C2=A0 =C2=A0 =C2=A0*=C2=A0 corresponding interrupt vector will clear= WDIE and WDIF automatically by
> +=C2=A0 =C2=A0 =C2=A0*=C2=A0 hardware (the Watchdog goes to System Res= et Mode). This is useful for
> +=C2=A0 =C2=A0 =C2=A0*=C2=A0 keeping the Watchdog Timer security while= using the interrupt. To stay
> +=C2=A0 =C2=A0 =C2=A0*=C2=A0 in Interrupt and System Reset Mode, WDIE = must be set after each
> +=C2=A0 =C2=A0 =C2=A0*=C2=A0 interrupt. This should however not be don= e within the interrupt service
> +=C2=A0 =C2=A0 =C2=A0*=C2=A0 routine itself, as this might compromise = the safety-function of the
> +=C2=A0 =C2=A0 =C2=A0*=C2=A0 Watchdog System Reset mode. If the interr= upt is not executed before the
> +=C2=A0 =C2=A0 =C2=A0*=C2=A0 next time-out, a System Reset will be app= lied.
> +=C2=A0 =C2=A0 =C2=A0*/
> +=C2=A0 =C2=A0 if ((val & WDTCSR_MASK_WDIE) && (wdt->cs= r & WDTCSR_MASK_WDIF)) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 avr_wdt_interrupt(opaque);
> +=C2=A0 =C2=A0 }
> +}
> +
> +static const MemoryRegionOps avr_wdt_ops =3D {
> +=C2=A0 =C2=A0 .read =3D avr_wdt_read,
> +=C2=A0 =C2=A0 .write =3D avr_wdt_write,
> +=C2=A0 =C2=A0 .endianness =3D DEVICE_NATIVE_ENDIAN,
> +=C2=A0 =C2=A0 .impl =3D {.max_access_size =3D 1}
> +};
> +
> +static void avr_wdt_wdr(void *opaque, int irq, int level)
> +{
> +=C2=A0 =C2=A0 AVRWatchdogState *wdt =3D AVR_WDT(opaque);
> +
> +=C2=A0 =C2=A0 avr_wdt_reset_alarm(wdt);
> +}
> +
> +static void avr_wdt_init(Object *obj)
> +{
> +=C2=A0 =C2=A0 AVRWatchdogState *s =3D AVR_WDT(obj);
> +
> +=C2=A0 =C2=A0 sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq); > +
> +=C2=A0 =C2=A0 memory_region_init_io(&s->iomem, obj, &avr_w= dt_ops,
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0= =C2=A0 =C2=A0 =C2=A0 s, "avr-wdt", 0xa);
> +
> +=C2=A0 =C2=A0 sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem)= ;
> +=C2=A0 =C2=A0 qdev_init_gpio_in_named(DEVICE(s), avr_wdt_wdr, "w= dr", 1);
> +}
> +
> +static void avr_wdt_realize(DeviceState *dev, Error **errp)
> +{
> +=C2=A0 =C2=A0 AVRWatchdogState *s =3D AVR_WDT(dev);
> +
> +=C2=A0 =C2=A0 s->timer =3D timer_new_ns(QEMU_CLOCK_VIRTUAL, avr_wd= t_interrupt, s);
> +}
> +
> +static void avr_wdt_class_init(ObjectClass *klass, void *data)
> +{
> +=C2=A0 =C2=A0 DeviceClass *dc =3D DEVICE_CLASS(klass);
> +
> +=C2=A0 =C2=A0 dc->reset =3D avr_wdt_reset;
> +=C2=A0 =C2=A0 dc->realize =3D avr_wdt_realize;
> +}
> +
> +static const TypeInfo avr_wdt_info =3D {
> +=C2=A0 =C2=A0 .name=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =3D TYPE_AVR_WD= T,
> +=C2=A0 =C2=A0 .parent=C2=A0 =C2=A0 =C2=A0 =C2=A0 =3D TYPE_SYS_BUS_DEV= ICE,
> +=C2=A0 =C2=A0 .instance_size =3D sizeof(AVRWatchdogState),
> +=C2=A0 =C2=A0 .instance_init =3D avr_wdt_init,
> +=C2=A0 =C2=A0 .class_init=C2=A0 =C2=A0 =3D avr_wdt_class_init,
> +};
> +
> +static void avr_wdt_register_types(void)
> +{
> +=C2=A0 =C2=A0 type_register_static(&avr_wdt_info);
> +}
> +
> +type_init(avr_wdt_register_types)
> diff --git a/hw/watchdog/meson.build b/hw/watchdog/meson.build
> index 054c403dea..8db2be8317 100644
> --- a/hw/watchdog/meson.build
> +++ b/hw/watchdog/meson.build
> @@ -6,3 +6,5 @@ softmmu_ss.add(when: 'CONFIG_WDT_DIAG288', if_= true: files('wdt_diag288.c'))
>=C2=A0 =C2=A0softmmu_ss.add(when: 'CONFIG_ASPEED_SOC', if_true:= files('wdt_aspeed.c'))
>=C2=A0 =C2=A0softmmu_ss.add(when: 'CONFIG_WDT_IMX2', if_true: f= iles('wdt_imx2.c'))
>=C2=A0 =C2=A0softmmu_ss.add(when: 'CONFIG_WDT_SBSA', if_true: f= iles('sbsa_gwdt.c'))
> +
> +specific_ss.add(when: 'CONFIG_AVR_WDT', if_true: files('a= vr_wdt.c'))
> diff --git a/hw/watchdog/trace-events b/hw/watchdog/trace-events
> index 3124ca1f1b..ac14773179 100644
> --- a/hw/watchdog/trace-events
> +++ b/hw/watchdog/trace-events
> @@ -5,3 +5,8 @@ cmsdk_apb_watchdog_read(uint64_t offset, uint64_t data= , unsigned size) "CMSDK AP
>=C2=A0 =C2=A0cmsdk_apb_watchdog_write(uint64_t offset, uint64_t data, u= nsigned size) "CMSDK APB watchdog write: offset 0x%" PRIx64 "= ; data 0x%" PRIx64 " size %u"
>=C2=A0 =C2=A0cmsdk_apb_watchdog_reset(void) "CMSDK APB watchdog: r= eset"
>=C2=A0 =C2=A0cmsdk_apb_watchdog_lock(uint32_t lock) "CMSDK APB wat= chdog: lock %" PRIu32
> +
> +# avr_wdt.c
> +avr_wdt_read(uint8_t addr, uint8_t value) "wdt read addr:%u valu= e:%u"
> +avr_wdt_write(uint8_t addr, uint8_t value) "wdt write addr:%u va= lue:%u"
> +avr_wdt_interrupt(void) ""
> diff --git a/include/hw/watchdog/avr_wdt.h b/include/hw/watchdog/avr_w= dt.h
> new file mode 100644
> index 0000000000..8433663013
> --- /dev/null
> +++ b/include/hw/watchdog/avr_wdt.h
> @@ -0,0 +1,47 @@
> +/*
> + * AVR watchdog
> + *
> + * Copyright (c) 2021 Michael Rolnik
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2.1 of the License, or (at your option) any later version.=
> + *
> + * This library is distributed in the hope that it will be useful, > + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.=C2=A0 See the= GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public > + * License along with this library; if not, see
> + * <http://www.gnu.org/licenses/lgpl-2.= 1.html>
> + */
> +
> +#ifndef HW_WATCHDOG_AVR_WDT_H
> +#define HW_WATCHDOG_AVR_WDT_H
> +
> +#include "hw/sysbus.h"
> +#include "qemu/timer.h"
> +#include "hw/hw.h"
> +#include "qom/object.h"
> +
> +#define TYPE_AVR_WDT "avr-wdt"
> +OBJECT_DECLARE_SIMPLE_TYPE(AVRWatchdogState, AVR_WDT)
> +
> +struct AVRWatchdogState {
> +=C2=A0 =C2=A0 /* <private> */
> +=C2=A0 =C2=A0 SysBusDevice parent_obj;
> +
> +=C2=A0 =C2=A0 /* <public> */
> +=C2=A0 =C2=A0 MemoryRegion iomem;
> +=C2=A0 =C2=A0 MemoryRegion imsk_iomem;
> +=C2=A0 =C2=A0 MemoryRegion ifr_iomem;
> +=C2=A0 =C2=A0 QEMUTimer *timer;
> +=C2=A0 =C2=A0 qemu_irq irq;
> +
> +=C2=A0 =C2=A0 /* registers */
> +=C2=A0 =C2=A0 uint8_t csr;
> +};
> +

You should add vmstate which includes timer and csr. It will allow
reverse debugging available with icount enabled.

> +#endif /* HW_WATCHDOG_AVR_WDT_H */
> diff --git a/target/avr/cpu.c b/target/avr/cpu.c
> index 0f4596932b..d5eb785833 100644
> --- a/target/avr/cpu.c
> +++ b/target/avr/cpu.c
> @@ -131,6 +131,9 @@ static void avr_cpu_initfn(Object *obj)
>=C2=A0 =C2=A0 =C2=A0 =C2=A0/* Set the number of interrupts supported by= the CPU. */
>=C2=A0 =C2=A0 =C2=A0 =C2=A0qdev_init_gpio_in(DEVICE(cpu), avr_cpu_set_i= nt,
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 = =C2=A0 =C2=A0 =C2=A0sizeof(cpu->env.intsrc) * 8);
> +
> +=C2=A0 =C2=A0 /* register watchdog timer reset interrupt */
> +=C2=A0 =C2=A0 qdev_init_gpio_out_named(DEVICE(cpu), &cpu->wdr,= "wdr", 1);
>=C2=A0 =C2=A0}
>=C2=A0 =C2=A0
>=C2=A0 =C2=A0static ObjectClass *avr_cpu_class_by_name(const char *cpu_= model)
> diff --git a/target/avr/cpu.h b/target/avr/cpu.h
> index d148e8c75a..f8f5641c8b 100644
> --- a/target/avr/cpu.h
> +++ b/target/avr/cpu.h
> @@ -152,6 +152,7 @@ typedef struct AVRCPU {
>=C2=A0 =C2=A0
>=C2=A0 =C2=A0 =C2=A0 =C2=A0CPUNegativeOffsetState neg;
>=C2=A0 =C2=A0 =C2=A0 =C2=A0CPUAVRState env;
> +=C2=A0 =C2=A0 qemu_irq wdr; /* reset WDT */
>=C2=A0 =C2=A0} AVRCPU;
>=C2=A0 =C2=A0
>=C2=A0 =C2=A0extern const struct VMStateDescription vms_avr_cpu;
> diff --git a/target/avr/helper.c b/target/avr/helper.c
> index 35e1019594..dd88057e5f 100644
> --- a/target/avr/helper.c
> +++ b/target/avr/helper.c
> @@ -24,6 +24,7 @@
>=C2=A0 =C2=A0#include "exec/exec-all.h"
>=C2=A0 =C2=A0#include "exec/address-spaces.h"
>=C2=A0 =C2=A0#include "exec/helper-proto.h"
> +#include "hw/irq.h"
>=C2=A0 =C2=A0
>=C2=A0 =C2=A0bool avr_cpu_exec_interrupt(CPUState *cs, int interrupt_re= quest)
>=C2=A0 =C2=A0{
> @@ -188,11 +189,9 @@ void helper_break(CPUAVRState *env)
>=C2=A0 =C2=A0
>=C2=A0 =C2=A0void helper_wdr(CPUAVRState *env)
>=C2=A0 =C2=A0{
> -=C2=A0 =C2=A0 CPUState *cs =3D env_cpu(env);
> +=C2=A0 =C2=A0 AVRCPU *cpu =3D env_archcpu(env);
>=C2=A0 =C2=A0
> -=C2=A0 =C2=A0 /* WD is not implemented yet, placeholder */
> -=C2=A0 =C2=A0 cs->exception_index =3D EXCP_DEBUG;
> -=C2=A0 =C2=A0 cpu_loop_exit(cs);
> +=C2=A0 =C2=A0 qemu_set_irq(cpu->wdr, 1);
>=C2=A0 =C2=A0}
>=C2=A0 =C2=A0
>=C2=A0 =C2=A0/*
> diff --git a/target/avr/translate.c b/target/avr/translate.c
> index 850c5941d9..b7b716f4a0 100644
> --- a/target/avr/translate.c
> +++ b/target/avr/translate.c
> @@ -1411,6 +1411,9 @@ static bool trans_SBIC(DisasContext *ctx, arg_SB= IC *a)
>=C2=A0 =C2=A0{
>=C2=A0 =C2=A0 =C2=A0 =C2=A0TCGv temp =3D tcg_const_i32(a->reg);
>=C2=A0 =C2=A0
> +=C2=A0 =C2=A0 if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 gen_io_start();
> +=C2=A0 =C2=A0 }
>=C2=A0 =C2=A0 =C2=A0 =C2=A0gen_helper_inb(temp, cpu_env, temp);
>=C2=A0 =C2=A0 =C2=A0 =C2=A0tcg_gen_andi_tl(temp, temp, 1 << a->= ;bit);
>=C2=A0 =C2=A0 =C2=A0 =C2=A0ctx->skip_cond =3D TCG_COND_EQ;

Translation should stop at inb-related instructions in icount mode,
because icount is valid only at the end of the block. Reading it in the middle makes execution non-deterministic.

> @@ -1429,6 +1432,9 @@ static bool trans_SBIS(DisasContext *ctx, arg_SB= IS *a)
>=C2=A0 =C2=A0{
>=C2=A0 =C2=A0 =C2=A0 =C2=A0TCGv temp =3D tcg_const_i32(a->reg);
>=C2=A0 =C2=A0
> +=C2=A0 =C2=A0 if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 gen_io_start();
> +=C2=A0 =C2=A0 }
>=C2=A0 =C2=A0 =C2=A0 =C2=A0gen_helper_inb(temp, cpu_env, temp);
>=C2=A0 =C2=A0 =C2=A0 =C2=A0tcg_gen_andi_tl(temp, temp, 1 << a->= ;bit);
>=C2=A0 =C2=A0 =C2=A0 =C2=A0ctx->skip_cond =3D TCG_COND_NE;
> @@ -1611,6 +1617,9 @@ static TCGv gen_get_zaddr(void)
>=C2=A0 =C2=A0static void gen_data_store(DisasContext *ctx, TCGv data, T= CGv addr)
>=C2=A0 =C2=A0{
>=C2=A0 =C2=A0 =C2=A0 =C2=A0if (ctx->tb->flags & TB_FLAGS_FULL= _ACCESS) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 if (tb_cflags(ctx->tb) & CF_USE_IC= OUNT) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 gen_io_start();
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 }
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0gen_helper_fullwr(cpu_env, dat= a, addr);
>=C2=A0 =C2=A0 =C2=A0 =C2=A0} else {
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0tcg_gen_qemu_st8(data, addr, M= MU_DATA_IDX); /* mem[addr] =3D data */
> @@ -1620,6 +1629,9 @@ static void gen_data_store(DisasContext *ctx, TC= Gv data, TCGv addr)
>=C2=A0 =C2=A0static void gen_data_load(DisasContext *ctx, TCGv data, TC= Gv addr)
>=C2=A0 =C2=A0{
>=C2=A0 =C2=A0 =C2=A0 =C2=A0if (ctx->tb->flags & TB_FLAGS_FULL= _ACCESS) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 if (tb_cflags(ctx->tb) & CF_USE_IC= OUNT) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 gen_io_start();
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 }
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0gen_helper_fullrd(data, cpu_en= v, addr);
>=C2=A0 =C2=A0 =C2=A0 =C2=A0} else {
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0tcg_gen_qemu_ld8u(data, addr, = MMU_DATA_IDX); /* data =3D mem[addr] */
> @@ -2325,6 +2337,9 @@ static bool trans_IN(DisasContext *ctx, arg_IN *= a)
>=C2=A0 =C2=A0 =C2=A0 =C2=A0TCGv Rd =3D cpu_r[a->rd];
>=C2=A0 =C2=A0 =C2=A0 =C2=A0TCGv port =3D tcg_const_i32(a->imm);
>=C2=A0 =C2=A0
> +=C2=A0 =C2=A0 if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 gen_io_start();
> +=C2=A0 =C2=A0 }
>=C2=A0 =C2=A0 =C2=A0 =C2=A0gen_helper_inb(Rd, cpu_env, port);
>=C2=A0 =C2=A0
>=C2=A0 =C2=A0 =C2=A0 =C2=A0tcg_temp_free_i32(port);
> @@ -2341,6 +2356,9 @@ static bool trans_OUT(DisasContext *ctx, arg_OUT= *a)
>=C2=A0 =C2=A0 =C2=A0 =C2=A0TCGv Rd =3D cpu_r[a->rd];
>=C2=A0 =C2=A0 =C2=A0 =C2=A0TCGv port =3D tcg_const_i32(a->imm);
>=C2=A0 =C2=A0
> +=C2=A0 =C2=A0 if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 gen_io_start();
> +=C2=A0 =C2=A0 }
>=C2=A0 =C2=A0 =C2=A0 =C2=A0gen_helper_outb(cpu_env, port, Rd);
>=C2=A0 =C2=A0
>=C2=A0 =C2=A0 =C2=A0 =C2=A0tcg_temp_free_i32(port);
> @@ -2641,6 +2659,9 @@ static bool trans_SBI(DisasContext *ctx, arg_SBI= *a)
>=C2=A0 =C2=A0 =C2=A0 =C2=A0TCGv data =3D tcg_temp_new_i32();
>=C2=A0 =C2=A0 =C2=A0 =C2=A0TCGv port =3D tcg_const_i32(a->reg);
>=C2=A0 =C2=A0
> +=C2=A0 =C2=A0 if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 gen_io_start();
> +=C2=A0 =C2=A0 }
>=C2=A0 =C2=A0 =C2=A0 =C2=A0gen_helper_inb(data, cpu_env, port);
>=C2=A0 =C2=A0 =C2=A0 =C2=A0tcg_gen_ori_tl(data, data, 1 << a->= bit);
>=C2=A0 =C2=A0 =C2=A0 =C2=A0gen_helper_outb(cpu_env, port, data);
> @@ -2660,6 +2681,9 @@ static bool trans_CBI(DisasContext *ctx, arg_CBI= *a)
>=C2=A0 =C2=A0 =C2=A0 =C2=A0TCGv data =3D tcg_temp_new_i32();
>=C2=A0 =C2=A0 =C2=A0 =C2=A0TCGv port =3D tcg_const_i32(a->reg);
>=C2=A0 =C2=A0
> +=C2=A0 =C2=A0 if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 gen_io_start();
> +=C2=A0 =C2=A0 }
>=C2=A0 =C2=A0 =C2=A0 =C2=A0gen_helper_inb(data, cpu_env, port);
>=C2=A0 =C2=A0 =C2=A0 =C2=A0tcg_gen_andi_tl(data, data, ~(1 << a-&= gt;bit));
>=C2=A0 =C2=A0 =C2=A0 =C2=A0gen_helper_outb(cpu_env, port, data);
> @@ -2830,6 +2854,9 @@ static bool trans_SLEEP(DisasContext *ctx, arg_S= LEEP *a)
>=C2=A0 =C2=A0 */
>=C2=A0 =C2=A0static bool trans_WDR(DisasContext *ctx, arg_WDR *a)
>=C2=A0 =C2=A0{
> +=C2=A0 =C2=A0 if (tb_cflags(ctx->tb) & CF_USE_ICOUNT) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 gen_io_start();
> +=C2=A0 =C2=A0 }
>=C2=A0 =C2=A0 =C2=A0 =C2=A0gen_helper_wdr(cpu_env);
>=C2=A0 =C2=A0
>=C2=A0 =C2=A0 =C2=A0 =C2=A0return true;
> @@ -2919,6 +2946,9 @@ void gen_intermediate_code(CPUState *cs, Transla= tionBlock *tb, int max_insns)
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 * This flag is set by ST/LD i= nstruction we will regenerate it ONLY
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 * with mem/cpu memory access = instead of mem access
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 */
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 if (tb_cflags(tb) & CF_USE_ICOUNT) {<= br> > +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 gen_io_start();
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 }
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0max_insns =3D 1;
>=C2=A0 =C2=A0 =C2=A0 =C2=A0}
>=C2=A0 =C2=A0 =C2=A0 =C2=A0if (ctx.singlestep) {
> @@ -2955,6 +2985,10 @@ void gen_intermediate_code(CPUState *cs, Transl= ationBlock *tb, int max_insns)
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0goto done_genera= ting;
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0}
>=C2=A0 =C2=A0
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 if (num_insns =3D=3D max_insns &&= (tb_cflags(tb) & CF_LAST_IO)) {
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 gen_io_start();
> +=C2=A0 =C2=A0 =C2=A0 =C2=A0 }
> +
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0/* Conditionally skip the next= instruction, if indicated.=C2=A0 */
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0if (ctx.skip_cond !=3D TCG_CON= D_NEVER) {
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0skip_label =3D g= en_new_label();
> @@ -2998,10 +3032,6 @@ void gen_intermediate_code(CPUState *cs, Transl= ationBlock *tb, int max_insns)
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 && (ctx= .npc - pc_start) * 2 < TARGET_PAGE_SIZE - 4
>=C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 =C2=A0 && !tcg= _op_buf_full());
>=C2=A0 =C2=A0
> -=C2=A0 =C2=A0 if (tb->cflags & CF_LAST_IO) {
> -=C2=A0 =C2=A0 =C2=A0 =C2=A0 gen_io_end();
> -=C2=A0 =C2=A0 }
> -
>=C2=A0 =C2=A0 =C2=A0 =C2=A0bool nonconst_skip =3D canonicalize_skip(&am= p;ctx);
>=C2=A0 =C2=A0
>=C2=A0 =C2=A0 =C2=A0 =C2=A0switch (ctx.bstate) {
>

--0000000000001226dd05c2451654--