qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [PATCH 14/18] hw: add QEMU model for Faraday system timers


From: Dante
Subject: [Qemu-devel] [PATCH 14/18] hw: add QEMU model for Faraday system timers
Date: Fri, 18 Jan 2013 14:32:21 +0800

Signed-off-by: Kuo-Jung Su <address@hidden>
---
 hw/ftpwmtmr010.c |  226 ++++++++++++++++++++++++++++
 hw/fttmr010.c    |  442 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 668 insertions(+)
 create mode 100644 hw/ftpwmtmr010.c
 create mode 100644 hw/fttmr010.c

diff --git a/hw/ftpwmtmr010.c b/hw/ftpwmtmr010.c
new file mode 100644
index 0000000..357e758
--- /dev/null
+++ b/hw/ftpwmtmr010.c
@@ -0,0 +1,226 @@
+/*
+ * Faraday FTPWMTMR010 Timer.
+ *
+ * Copyright (c) 2012 Faraday Technology
+ *
+ * Written by Dante Su <address@hidden>
+ *
+ * This code is licensed under the GPL.
+ */
+
+#include "hw.h"
+#include "qemu/timer.h"
+#include "sysemu/sysemu.h"
+#include "sysbus.h"
+
+typedef struct _ftpwmtmr010_state ftpwmtmr010_state;
+
+typedef struct {
+    uint32_t ctrl;
+    uint32_t cntb;
+    int id;
+    uint64_t timeout;
+    uint64_t countdown;
+    qemu_irq irq;
+    QEMUTimer *qtimer;
+    ftpwmtmr010_state *chip;
+} ftpwmtmr010_timer;
+
+struct _ftpwmtmr010_state {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+    ftpwmtmr010_timer timer[8];
+    uint32_t freq;        /* desired source clock */
+    uint32_t step;        /* get_ticks_per_sec() / freq */
+    uint32_t stat;
+};
+
+static uint64_t 
+ftpwmtmr010_mem_read(void *opaque, hwaddr addr, unsigned size)
+{
+    ftpwmtmr010_state *s = (ftpwmtmr010_state *) opaque;
+    ftpwmtmr010_timer *t;
+    uint64_t now = qemu_get_clock_ns(vm_clock);
+    uint64_t ret = 0;
+
+    if (addr == 0x00) {
+        ret = s->stat;
+    } else if (addr >= 0x10 && addr < 0x90) {
+        t = s->timer + (addr >> 4) - 1;
+        switch(addr & 0x0f) {
+        case 0x00:
+            return t->ctrl;
+        case 0x04:
+            return t->cntb;
+        case 0x0c:
+            if ((t->ctrl & 0x02) && (t->timeout > now))
+                ret = (t->timeout - now) / s->step;
+            break;
+        }
+    }
+
+    return ret;
+}
+
+static void 
+ftpwmtmr010_mem_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
+{
+    ftpwmtmr010_state *s = (ftpwmtmr010_state *) opaque;
+    ftpwmtmr010_timer *t;
+
+    if (addr == 0x00) {
+        int i;
+        s->stat &= ~((uint32_t)val & 0xffffffff);
+        for (i = 0; i < 8; ++i) {
+            if (val & (1 << i))
+                qemu_irq_lower(s->timer[i].irq);
+        }
+    } else if (addr >= 0x10 && addr < 0x90) {
+        t = s->timer + (addr >> 4) - 1;
+        switch(addr & 0x0f) {
+        case 0x00:
+            t->ctrl = (uint32_t)val;
+            if (t->ctrl & (1 << 2)) {
+                t->countdown = (uint64_t)t->cntb * (uint64_t)s->step;
+            }
+            if (t->ctrl & (1 << 1)) {
+                t->timeout = t->countdown + qemu_get_clock_ns(vm_clock);
+                qemu_mod_timer(t->qtimer, t->timeout);
+            }
+            break;
+        case 0x04:
+            t->cntb = (uint32_t)val;
+            break;
+        }
+    }
+}
+
+static const MemoryRegionOps ftpwmtmr010_mem_ops = {
+    .read  = ftpwmtmr010_mem_read,
+    .write = ftpwmtmr010_mem_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void ftpwmtmr010_timer_tick(void *opaque)
+{
+    ftpwmtmr010_timer *t = (ftpwmtmr010_timer *) opaque;
+    ftpwmtmr010_state *s = t->chip;
+
+    /* if the timer has been enabled/started */
+    if (!(t->ctrl & (1 << 1)))
+        return;
+
+    /* if the interrupt enabled */
+    if (t->ctrl & (1 << 5)) {
+        s->stat |= 1 << t->id;
+        if (t->ctrl & (1 << 6))
+            qemu_irq_pulse(t->irq);
+        else
+            qemu_irq_raise(t->irq);
+    }
+
+    /* if auto-reload is enabled */
+    if (t->ctrl & (1 << 4)) {
+        t->timeout = t->countdown + qemu_get_clock_ns(vm_clock);
+        qemu_mod_timer(t->qtimer, t->timeout);
+    } else {
+        t->ctrl &= ~(1 << 1);
+    }
+}
+
+static void ftpwmtmr010_reset(DeviceState *d)
+{
+    int i;
+    ftpwmtmr010_state *s = DO_UPCAST(ftpwmtmr010_state, busdev.qdev, d);
+    
+    s->stat = 0;
+    
+    for (i = 0; i < 8; ++i) {
+        s->timer[i].ctrl = 0;
+        s->timer[i].cntb = 0;
+        s->timer[i].timeout = 0;
+        qemu_irq_lower(s->timer[i].irq);
+        qemu_del_timer(s->timer[i].qtimer);
+    }
+}
+
+static int ftpwmtmr010_init(SysBusDevice *dev)
+{
+    int i;
+    ftpwmtmr010_state *s;
+
+    s = FROM_SYSBUS(ftpwmtmr010_state, dev);
+
+    s->step = (uint64_t)get_ticks_per_sec() / (uint64_t)s->freq;
+
+    printf("qemu: ftpwmtmr010 freq=%d\n", s->freq);
+
+    memory_region_init_io(&s->iomem, &ftpwmtmr010_mem_ops, s, "ftpwmtmr010", 
0x1000);
+    sysbus_init_mmio(dev, &s->iomem);
+    for (i = 0; i < 8; i ++) {
+        s->timer[i].id = i;
+        s->timer[i].chip = s;
+        s->timer[i].qtimer = qemu_new_timer_ns(vm_clock, 
ftpwmtmr010_timer_tick, &s->timer[i]);
+        sysbus_init_irq(dev, &s->timer[i].irq);
+    }
+
+    return 0;
+}
+
+static const VMStateDescription vmstate_ftpwmtmr010_timer_regs = {
+    .name = "ftpwmtmr010_timer",
+    .version_id = 2,
+    .minimum_version_id = 2,
+    .minimum_version_id_old = 2,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32(ctrl, ftpwmtmr010_timer),
+        VMSTATE_UINT32(cntb, ftpwmtmr010_timer),
+        VMSTATE_END_OF_LIST(),
+    },
+};
+
+static const VMStateDescription vmstate_ftpwmtmr010_regs = {
+    .name = "ftpwmtmr010",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32(stat, ftpwmtmr010_state),
+        VMSTATE_UINT32(freq, ftpwmtmr010_state),
+        VMSTATE_UINT32(step, ftpwmtmr010_state),
+        VMSTATE_STRUCT_ARRAY(timer, ftpwmtmr010_state, 8, 1,
+                        vmstate_ftpwmtmr010_timer_regs, ftpwmtmr010_timer),
+        VMSTATE_END_OF_LIST(),
+    }
+};
+
+static Property ftpwmtmr010_dev_properties[] = {
+    DEFINE_PROP_UINT32("freq", ftpwmtmr010_state, freq, 66000000),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void ftpwmtmr010_dev_class_init(ObjectClass *klass, void *data)
+{
+    SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass);
+    DeviceClass *k = DEVICE_CLASS(klass);
+
+    sdc->init  = ftpwmtmr010_init;
+    k->vmsd    = &vmstate_ftpwmtmr010_regs;
+    k->props   = ftpwmtmr010_dev_properties;
+    k->reset   = ftpwmtmr010_reset;
+    k->no_user = 1;
+}
+
+static TypeInfo ftpwmtmr010_dev_info = {
+    .name          = "ftpwmtmr010",
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(ftpwmtmr010_state),
+    .class_init    = ftpwmtmr010_dev_class_init,
+};
+
+static void ftpwmtmr010_register_types(void)
+{
+    type_register_static(&ftpwmtmr010_dev_info);
+}
+
+type_init(ftpwmtmr010_register_types)
diff --git a/hw/fttmr010.c b/hw/fttmr010.c
new file mode 100644
index 0000000..e30cb3d
--- /dev/null
+++ b/hw/fttmr010.c
@@ -0,0 +1,442 @@
+/*
+ * Faraday FTTMR010 Timer.
+ *
+ * Copyright (c) 2012 Faraday Technology
+ *
+ * Written by Dante Su <address@hidden>
+ *
+ * This code is licensed under the GPL.
+ */
+
+#include "hw.h"
+#include "qemu/timer.h"
+#include "sysemu/sysemu.h"
+#include "sysbus.h"
+
+#ifndef min
+#define min(a, b)                ((a) < (b) ? (a) : (b))
+#endif
+
+#ifndef max
+#define max(a, b)                ((a) > (b) ? (a) : (b))
+#endif
+
+#define REG_TMR_ID(off)            ((off) >> 4)
+#define REG_TMR_COUNTER            0x00
+#define REG_TMR_RELOAD            0x04
+#define REG_TMR_MATCH1            0x08
+#define REG_TMR_MATCH2            0x0C
+
+#define REG_CR                    0x30
+#define REG_ISR                    0x34
+#define REG_IMR                    0x38
+#define REG_REV                    0x3C
+
+#define CR_TMR_EN(id)            (0x01 << ((id) * 3))    /* timer enable */
+#define CR_TMR_OFEN(id)            (0x04 << ((id) * 3))    /* timer overflow 
interrupt enable */
+#define CR_TMR_COUNTUP(id)        (0x01 << (9 + (id)))    /* timer count-up 
mode */
+
+#define ISR_MATCH1(id)            (0x01 << ((id) * 3))    /* timer match 1 */
+#define ISR_MATCH2(id)            (0x02 << ((id) * 3))    /* timer match 2 */
+#define ISR_OF(id)                (0x04 << ((id) * 3))    /* timer overflow */
+
+typedef struct _fttmr010_state fttmr010_state;
+
+typedef struct {
+    int id;
+    int up;
+    fttmr010_state *chip;
+    qemu_irq irq;
+    QEMUTimer *qtimer;
+    uint64_t start;
+    uint32_t intr_match1 : 1;
+    uint32_t intr_match2 : 1;
+
+    /* HW register caches */
+    uint64_t counter;
+    uint64_t reload;
+    uint32_t match1;
+    uint32_t match2;
+
+} fttmr010_timer;
+
+struct _fttmr010_state {
+    SysBusDevice busdev;
+    MemoryRegion iomem;
+    qemu_irq irq;
+    fttmr010_timer timer[3];
+    uint32_t freq;        /* desired source clock */
+    uint64_t step;        /* get_ticks_per_sec() / freq */
+    
+    /* HW register caches */
+    uint32_t cr;
+    uint32_t isr;
+    uint32_t imr;
+};
+
+static void fttmr010_timer_restart(fttmr010_timer *t)
+{
+    fttmr010_state *s = t->chip;
+    uint64_t interval;
+    int pending = 0;
+    
+    t->intr_match1 = 0;
+    t->intr_match2 = 0;
+
+    /* check match1 */
+    if (t->up && t->match1 <= t->counter)
+        t->intr_match1 = 1;
+    if (!t->up && t->match1 >= t->counter)
+        t->intr_match1 = 1;
+    if (t->match1 == t->counter) {
+        s->isr |= ISR_MATCH1(t->id);
+        ++pending;
+    }
+        
+    /* check match2 */
+    if (t->up && t->match2 <= t->counter)
+        t->intr_match2 = 1;
+    if (!t->up && t->match2 >= t->counter)
+        t->intr_match2 = 1;
+    if (t->match2 == t->counter) {
+        s->isr |= ISR_MATCH2(t->id);
+        ++pending;
+    }
+
+    /* determine delay interval */
+    if (t->up) {
+        if ((t->match1 > t->counter) && (t->match2 > t->counter))
+            interval = min(t->match1, t->match2) - t->counter;
+        else if (t->match1 > t->counter)
+            interval = t->match1 - t->counter;
+        else if (t->match2 > t->reload)
+            interval = t->match2 - t->counter;
+        else
+            interval = 0xffffffffULL - t->counter;
+    } else {
+        if ((t->match1 < t->counter) && (t->match2 < t->counter))
+            interval = t->counter - max(t->match1, t->match2);
+        else if (t->match1 < t->reload)
+            interval = t->counter - t->match1;
+        else if (t->match2 < t->reload)
+            interval = t->counter - t->match2;
+        else
+            interval = t->counter;
+    }
+
+    if (pending) {
+        qemu_irq_pulse(s->irq);
+        qemu_irq_pulse(t->irq);
+    }
+    t->start = qemu_get_clock_ns(vm_clock);
+    qemu_mod_timer(t->qtimer, t->start + interval * s->step);
+}
+
+static uint64_t fttmr010_update_counter(fttmr010_timer *t)
+{
+    fttmr010_state *s = t->chip;
+    uint64_t now = qemu_get_clock_ns(vm_clock);
+    uint64_t elapsed;
+    int pending = 0;
+
+    if (s->cr & CR_TMR_EN(t->id)) {
+        /* get elapsed time */
+        elapsed = (now - t->start) / s->step;
+
+        /* convert to count-up/count-down value */
+        if (t->up) {
+            t->counter = t->counter + elapsed;
+        } else {
+            if (t->counter > elapsed)
+                t->counter -= elapsed;
+            else
+                t->counter = 0;
+        }
+        t->start = now;
+
+        /* check match1 */
+        if (!t->intr_match1) {
+            if (t->up && t->match1 <= t->counter) {
+                t->intr_match1 = 1;
+                s->isr |= ISR_MATCH1(t->id);
+                ++pending;
+            }
+            if (!t->up && t->match1 >= t->counter) {
+                t->intr_match1 = 1;
+                s->isr |= ISR_MATCH1(t->id);
+                ++pending;
+            }
+        }
+        
+        /* check match2 */
+        if (!t->intr_match2) {
+            if (t->up && t->match2 <= t->counter) {
+                t->intr_match2 = 1;
+                s->isr |= ISR_MATCH2(t->id);
+                ++pending;
+            }
+            if (!t->up && t->match2 >= t->counter) {
+                t->intr_match2 = 1;
+                s->isr |= ISR_MATCH2(t->id);
+                ++pending;
+            }
+        }
+        
+        /* check overflow/underflow */
+        if (t->up && t->counter >= 0xffffffffULL) {
+            if (s->cr & CR_TMR_OFEN(t->id)) {
+                s->isr |= ISR_OF(t->id);
+                ++pending;
+            }
+            t->counter = t->reload;
+            fttmr010_timer_restart(t);
+        }
+        if (!t->up && t->counter == 0) {
+            if (s->cr & CR_TMR_OFEN(t->id)) {
+                s->isr |= ISR_OF(t->id);
+                ++pending;
+            }
+            t->counter = t->reload;
+            fttmr010_timer_restart(t);
+        }
+    }
+
+    if (pending) {
+        qemu_irq_pulse(s->irq);
+        qemu_irq_pulse(t->irq);
+    }
+
+    return t->counter;
+}
+
+static uint64_t 
+fttmr010_mem_read(void *opaque, hwaddr addr, unsigned size)
+{
+    fttmr010_state *s = (fttmr010_state *) opaque;
+    fttmr010_timer *t;
+    uint64_t ret = 0;
+
+    switch (addr) {
+    case REG_CR:
+        return s->cr;
+    case REG_ISR:
+        return s->isr;
+    case REG_IMR:
+        return s->imr;
+    case REG_REV:
+        return 0x00010801;
+    default:
+        if (addr < 0x30) {
+            t = s->timer + REG_TMR_ID(addr);
+            switch(addr & 0x0f) {
+            case REG_TMR_COUNTER:
+                ret = fttmr010_update_counter(t);
+                break;
+            case REG_TMR_RELOAD:
+                return t->reload;
+            case REG_TMR_MATCH1:
+                return t->match1;
+            case REG_TMR_MATCH2:
+                return t->match2;
+            }
+        }
+        break;
+    }
+
+    return ret;
+}
+
+static void 
+fttmr010_mem_write(void *opaque, hwaddr addr, uint64_t val, unsigned size)
+{
+    fttmr010_state *s = (fttmr010_state *) opaque;
+    fttmr010_timer *t;
+    int i;
+
+    switch (addr) {
+    case REG_CR:
+        s->cr = (uint32_t)val;
+        for (i = 0; i < 3; ++i) {
+            t = s->timer + i;
+            if (s->cr & CR_TMR_COUNTUP(t->id))
+                t->up = 1;
+            else
+                t->up = 0;
+            if (s->cr & CR_TMR_EN(t->id))
+                fttmr010_timer_restart(t);
+            else
+                qemu_del_timer(t->qtimer);
+        }
+        break;
+    case REG_ISR:
+        s->isr &= ~((uint32_t)val);
+        break;
+    case REG_IMR:
+        s->imr = (uint32_t)val;
+        break;
+    default:
+        if (addr < 0x30) {
+            t = s->timer + REG_TMR_ID(addr);
+            switch(addr & 0x0f) {
+            case REG_TMR_COUNTER:
+                t->counter = (uint32_t)val;
+                break;
+            case REG_TMR_RELOAD:
+                t->reload = (uint32_t)val;
+                break;
+            case REG_TMR_MATCH1:
+                t->match1 = (uint32_t)val;
+                break;
+            case REG_TMR_MATCH2:
+                t->match2 = (uint32_t)val;
+                break;
+            }
+        }
+        break;
+    }
+}
+
+static const MemoryRegionOps fttmr010_mem_ops = {
+    .read  = fttmr010_mem_read,
+    .write = fttmr010_mem_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void fttmr010_timer_tick(void *opaque)
+{
+    fttmr010_timer *t = (fttmr010_timer *) opaque;
+    fttmr010_state *s = t->chip;
+    uint64_t now;
+    
+    /* if the timer has been enabled/started */
+    if (!(s->cr & CR_TMR_EN(t->id)))
+        return;
+
+    fttmr010_update_counter(t);
+#if 1
+    if (t->reload == t->counter)
+        return;
+#endif
+
+    now = qemu_get_clock_ns(vm_clock);
+
+    if (t->up) {
+        if (!t->intr_match1 && t->match1 > t->counter) {
+            qemu_mod_timer(t->qtimer, now + (t->match1 - t->counter) * 
s->step);
+        } else if (!t->intr_match2 && t->match2 > t->counter) {
+            qemu_mod_timer(t->qtimer, now + (t->match2 - t->counter) * 
s->step);
+        } else {
+            qemu_mod_timer(t->qtimer, now + (0xffffffffULL - t->counter) * 
s->step);
+        }
+    } else {    
+        if (!t->intr_match1 && t->match1 < t->counter) {
+            qemu_mod_timer(t->qtimer, now + (t->counter - t->match1) * 
s->step);
+        } else if (!t->intr_match2 && t->match2 < t->counter) {
+            qemu_mod_timer(t->qtimer, now + (t->counter - t->match2) * 
s->step);
+        } else {
+            qemu_mod_timer(t->qtimer, now + t->counter * s->step);
+        }
+    }
+}
+
+static void fttmr010_reset(DeviceState *d)
+{
+    int i;
+    fttmr010_state *s = DO_UPCAST(fttmr010_state, busdev.qdev, d);
+    
+    s->cr  = 0;
+    s->isr = 0;
+    s->imr = 0;
+    qemu_irq_lower(s->irq);
+    
+    for (i = 0; i < 3; ++i) {
+        s->timer[i].counter= 0;
+        s->timer[i].reload = 0;
+        s->timer[i].match1 = 0;
+        s->timer[i].match2 = 0;
+        qemu_irq_lower(s->timer[i].irq);
+        qemu_del_timer(s->timer[i].qtimer);
+    }
+}
+
+static int fttmr010_init(SysBusDevice *dev)
+{
+    int i;
+    fttmr010_state *s = FROM_SYSBUS(fttmr010_state, dev);
+
+    s->step = (uint64_t)get_ticks_per_sec() / (uint64_t)s->freq;
+
+    printf("qemu: fttmr010 freq=%d\n", s->freq);
+
+    memory_region_init_io(&s->iomem, &fttmr010_mem_ops, s, "fttmr010", 0x1000);
+    sysbus_init_mmio(dev, &s->iomem);
+    sysbus_init_irq(dev, &s->irq);
+    for (i = 0; i < 3; ++i) {
+        s->timer[i].id = i;
+        s->timer[i].chip = s;
+        s->timer[i].qtimer = qemu_new_timer_ns(vm_clock, fttmr010_timer_tick, 
&s->timer[i]);
+        sysbus_init_irq(dev, &s->timer[i].irq);
+    }
+
+    return 0;
+}
+
+static const VMStateDescription vmstate_fttmr010_timer_regs = {
+    .name = "fttmr010_timer",
+    .version_id = 2,
+    .minimum_version_id = 2,
+    .minimum_version_id_old = 2,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT64(counter, fttmr010_timer),
+        VMSTATE_UINT64(reload, fttmr010_timer),
+        VMSTATE_UINT32(match1, fttmr010_timer),
+        VMSTATE_UINT32(match2, fttmr010_timer),
+        VMSTATE_END_OF_LIST(),
+    },
+};
+
+static const VMStateDescription vmstate_fttmr010_regs = {
+    .name = "fttmr010",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .minimum_version_id_old = 1,
+    .fields = (VMStateField[]) {
+        VMSTATE_UINT32(cr, fttmr010_state),
+        VMSTATE_UINT32(isr, fttmr010_state),
+        VMSTATE_UINT32(imr, fttmr010_state),
+        VMSTATE_STRUCT_ARRAY(timer, fttmr010_state, 3, 1,
+                        vmstate_fttmr010_timer_regs, fttmr010_timer),
+        VMSTATE_END_OF_LIST(),
+    }
+};
+
+static Property fttmr010_dev_properties[] = {
+    DEFINE_PROP_UINT32("freq", fttmr010_state, freq, 66000000),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void fttmr010_dev_class_init(ObjectClass *klass, void *data)
+{
+    SysBusDeviceClass *sdc = SYS_BUS_DEVICE_CLASS(klass);
+    DeviceClass *k = DEVICE_CLASS(klass);
+
+    sdc->init  = fttmr010_init;
+    k->vmsd    = &vmstate_fttmr010_regs;
+    k->props   = fttmr010_dev_properties;
+    k->reset   = fttmr010_reset;
+    k->no_user = 1;
+}
+
+static TypeInfo fttmr010_dev_info = {
+    .name          = "fttmr010",
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(fttmr010_state),
+    .class_init    = fttmr010_dev_class_init,
+};
+
+static void fttmr010_register_types(void)
+{
+    type_register_static(&fttmr010_dev_info);
+}
+
+type_init(fttmr010_register_types)
-- 
1.7.9.5


********************* Confidentiality Notice ************************
This electronic message and any attachments may contain
confidential and legally privileged information or
information which is otherwise protected from disclosure.
If you are not the intended recipient,please do not disclose
the contents, either in whole or in part, to anyone,and
immediately delete the message and any attachments from
your computer system and destroy all hard copies.
Thank you for your cooperation.
***********************************************************************




reply via email to

[Prev in Thread] Current Thread [Next in Thread]