[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [RFC 1/1] Implement AVR watchdog timer
From: |
Philippe Mathieu-Daudé |
Subject: |
Re: [RFC 1/1] Implement AVR watchdog timer |
Date: |
Sat, 15 May 2021 11:45:13 +0200 |
User-agent: |
Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Thunderbird/78.8.1 |
+Pavel/Alex
On 5/3/21 10:08 PM, Michael Rolnik wrote:
> Hi all,
>
> I was about to make icount work. but, there is something I still don't
> understand. I have this code
>
> timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, avr_wdt_interrupt, s);
>
> and then
>
> void avr_wdt_interrupt(/* some arguments */) {
> #define MS2NS(n) ((n) * 1000000ull)
> timer_mod_ns(timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + MS2NS(15));
> }
>
> when running with --icount shift=0, *avr_wdt_interrupt* is called about
> every 1K instructions, however it should have been called 15M
> instructions as shift=0 makes every instruction to be executed in 1
> virtual ns.
>
> What am I doing wrong?
>
> Thank you,
> Michael Rolnik
>
>
> On Mon, May 3, 2021 at 4:36 PM Michael Rolnik <mrolnik@gmail.com
> <mailto:mrolnik@gmail.com>> wrote:
>
> Hi Fred.
>
> 1. thanks
> 2. It seems I have forgotten to set those flags.
> 3. 15ms is easy to test 8s will take 533 times longer, so in my case
> 3200 instructions which is totally incorrect. I don't understand why
> as I program the timer in virtual nanoseconds and not host time.
> 4. I hope Richard could help with icount.
>
> best regards,
> Michael Rolnik
>
> On Mon, May 3, 2021 at 4:15 PM Fred Konrad <konrad@adacore.com
> <mailto:konrad@adacore.com>> wrote:
>
>
>
> Le 5/2/21 à 10:10 PM, Michael Rolnik a écrit :
> > Signed-off-by: Michael Rolnik <mrolnik@gmail.com
> <mailto:mrolnik@gmail.com>>
> > ---
> > hw/avr/Kconfig | 1 +
> > hw/avr/atmega.c | 15 ++-
> > hw/avr/atmega.h | 2 +
> > hw/watchdog/Kconfig | 3 +
> > hw/watchdog/avr_wdt.c | 190
> ++++++++++++++++++++++++++++++++++
> > 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 +-
> > 11 files changed, 271 insertions(+), 5 deletions(-)
> > create mode 100644 hw/watchdog/avr_wdt.c
> > create mode 100644 include/hw/watchdog/avr_wdt.h
> >
> > 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] = {
> > [GPIOD] = { 0x29 },
> > [GPIOC] = { 0x26 },
> > [GPIOB] = { 0x23 },
> > + [WDT] = { 0x60 },
> > }, dev1280_2560[PERIFMAX] = {
> > [USART3] = { 0x130, POWER1, 2 },
> > [TIMER5] = { 0x120, POWER1, 5, 0x73, 0x3a, true },
> > @@ -99,6 +101,7 @@ static const peripheral_cfg
> dev168_328[PERIFMAX] = {
> > [GPIOC] = { 0x26 },
> > [GPIOB] = { 0x23 },
> > [GPIOA] = { 0x20 },
> > + [WDT] = { 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] = {
> > + [WATCHDOG_TIMER_IRQ] = 7,
> > [TIMER2_COMPA_IRQ] = 8,
> > [TIMER2_COMPB_IRQ] = 9,
> > [TIMER2_OVF_IRQ] = 10,
> > @@ -147,6 +152,7 @@ static const uint8_t irq168_328[IRQ_COUNT] = {
> > [USART0_DRE_IRQ] = 20,
> > [USART0_TXC_IRQ] = 21,
> > }, irq1280_2560[IRQ_COUNT] = {
> > + [WATCHDOG_TIMER_IRQ] = 13,
> > [TIMER2_COMPA_IRQ] = 14,
> > [TIMER2_COMPB_IRQ] = 15,
> > [TIMER2_OVF_IRQ] = 16,
> > @@ -344,10 +350,17 @@ static void atmega_realize(DeviceState
> *dev, Error **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..4ce1029a64
> > --- /dev/null
> > +++ b/hw/watchdog/avr_wdt.c
> > @@ -0,0 +1,190 @@
> > +/*
> > + * AVR watchdog
> > + *
> > + * Copyright (c) 2018 Michael Rolnik
>
> 2021?
>
> > + *
> > + * 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
> > + * <http://www.gnu.org/licenses/lgpl-2.1.html
> <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"
> > +
> > +/* 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_WCE 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_WCE 0x04
> > +#define WDTCSR_SHFT_WDP3 0x05
> > +#define WDTCSR_SHFT_WDIE 0x06
> > +#define WDTCSR_SHFT_WDIF 0x07
> > +
> > +/* 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)
> > +
> > +static void avr_wdt_reset_alarm(AVRWatchdogState *wdt)
> > +{
> > + uint32_t csr = wdt->csr;
> > + int wdp = WDP(csr);
> > + assert(wdp <= 9);
>
> Maybe qemu_log(..) instead and pick a default value?
>
> > +
> > + if (WDIE(csr) == 0 && WDE(csr) == 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 = opaque;
> > + int8_t csr = wdt->csr;
> > +
> > + if (WDE(csr) == 0 && WDIE(csr) == 0) {
> > + /* Stopped */
> > +
> > + } else if (WDE(csr) == 0 && WDIE(csr) == 1) {
> > + /* Interrupt Mode */
> > + wdt->csr |= WDTCSR_MASK_WDIF;
> > + qemu_set_irq(wdt->irq, 1);
> > + trace_avr_wdt_interrupt();
> > + } else if (WDE(csr) == 1 && WDIE(csr) == 0) {
> > + /* System Reset Mode */
>
> qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
>
> +/- setting the MCUSR, WDRF flags would be nice.
>
> > + } else if (WDE(csr) == 1 && WDIE(csr) == 1) {
> > + /* Interrupt and System Reset Mode */
> > + wdt->csr |= WDTCSR_MASK_WDIF;
> > + qemu_set_irq(wdt->irq, 1);
> > + trace_avr_wdt_interrupt();
> > + }
> > +
> > + avr_wdt_reset_alarm(wdt);
> > +}
> > +
> > +static void avr_wdt_reset(DeviceState *dev)
> > +{
> > + AVRWatchdogState *wdt = AVR_WDT(dev);
> > +
> > + wdt->csr = 0;
> > + qemu_set_irq(wdt->irq, 0);
> > + avr_wdt_reset_alarm(wdt);
> > +}
> > +
> > +static uint64_t avr_wdt_read(void *opaque, hwaddr offset,
> unsigned size)
> > +{
> > + assert(size == 1);
> > + AVRWatchdogState *wdt = opaque;
> > + uint8_t retval = 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 == 1);
> > + AVRWatchdogState *wdt = opaque;
> > + uint8_t val8 = (uint8_t)val64;
> > +
> > + trace_avr_wdt_write(offset, val8);
> > +
> > + wdt->csr = val8;
> > + avr_wdt_reset_alarm(wdt);
> > +}
> > +
> > +static const MemoryRegionOps avr_wdt_ops = {
> > + .read = avr_wdt_read,
> > + .write = avr_wdt_write,
> > + .endianness = DEVICE_NATIVE_ENDIAN,
> > + .impl = {.max_access_size = 1}
> > +};
> > +
> > +static void avr_wdt_wdr(void *opaque, int irq, int level)
> > +{
> > + AVRWatchdogState *wdt = AVR_WDT(opaque);
> > +
> > + avr_wdt_reset_alarm(wdt);
> > +}
> > +
> > +static void avr_wdt_init(Object *obj)
> > +{
> > + AVRWatchdogState *s = 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 = AVR_WDT(dev);
> > +
> > + s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
> avr_wdt_interrupt, s);
> > +}
> > +
> > +static void avr_wdt_class_init(ObjectClass *klass, void *data)
> > +{
> > + DeviceClass *dc = DEVICE_CLASS(klass);
> > +
> > + dc->reset = avr_wdt_reset;
> > + dc->realize = avr_wdt_realize;
> > +}
> > +
> > +static const TypeInfo avr_wdt_info = {
> > + .name = TYPE_AVR_WDT,
> > + .parent = TYPE_SYS_BUS_DEVICE,
> > + .instance_size = sizeof(AVRWatchdogState),
> > + .instance_init = avr_wdt_init,
> > + .class_init = 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..2679e8f2a6
> > --- /dev/null
> > +++ b/include/hw/watchdog/avr_wdt.h
> > @@ -0,0 +1,47 @@
> > +/*
> > + * AVR 16-bit timer
>
> 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
> > + * <http://www.gnu.org/licenses/lgpl-2.1.html
> <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 {
> > + /* <private> */
> > + SysBusDevice parent_obj;
> > +
> > + /* <public> */
> > + MemoryRegion iomem;
> > + MemoryRegion imsk_iomem;
> > + MemoryRegion ifr_iomem;
> > + QEMUTimer *timer;
> > + qemu_irq irq;
> > +
> > + /* registers */
> > + uint8_t csr;
> > +};
> > +
> > +#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 = env_cpu(env);
> > + AVRCPU *cpu = env_archcpu(env);
> >
> > - /* WD is not implemented yet, placeholder */
> > - cs->exception_index = EXCP_DEBUG;
> > - cpu_loop_exit(cs);
> > + qemu_set_irq(cpu->wdr, 1);
> > }
> >
> > /*
> >
>
> Thanks!
>
>
>
> --
> Best Regards,
> Michael Rolnik
>
>
>
> --
> Best Regards,
> Michael Rolnik