pl4y.store

4 - Sources

hw/arm/calypso/calypso_l1ctl_tap.c

.c 124 lignes - 3,2KB

hw/arm/calypso/calypso_l1ctl_tap.c
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "qemu/osdep.h"
#include "qemu/bswap.h"
#include "hw/arm/calypso/calypso_l1.h"
#include "hw/arm/calypso/calypso_l1ctl_tap.h"
#include <fcntl.h>
#include <unistd.h>

#define SERCOMM_FLAG        0x7E
#define SERCOMM_ESCAPE      0x7D
#define SERCOMM_ESCAPE_XOR  0x20
#define SERCOMM_DLCI_L1CTL  5

#define L1CTL_DATA_IND      0x03
#define L1CTL_RACH_CONF     0x0c
#define L1CTL_DATA_CONF     0x0f

#define SHM_DCCH_CFG        "/dev/shm/calypso_dcch_cfg"
#define DCCH_RELEASED       0xFF

static struct {
    enum { SC_IDLE, SC_IN_FRAME, SC_ESCAPE } state;
    uint8_t buf[512];
    int len;
    uint8_t last_chan_nr;
    uint32_t dcch_seq;
} tap = { .last_chan_nr = 0xFF };

static void dcch_cfg_publish(int kind, int ss, uint8_t chan_nr)
{
    uint8_t b[16] = {0};
    tap.dcch_seq++;
    memcpy(b, &tap.dcch_seq, 4);
    b[4] = (uint8_t)kind;
    b[5] = (uint8_t)ss;
    b[6] = chan_nr & 0x07;
    b[7] = chan_nr;
    int fd = open(SHM_DCCH_CFG, O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if (fd < 0) {
        return;
    }
    if (write(fd, b, sizeof(b)) < 0) {
        close(fd);
        return;
    }
    close(fd);
}

static void frame_complete(void)
{
    if (tap.len < 3 || tap.buf[0] != SERCOMM_DLCI_L1CTL) {
        return;
    }
    const uint8_t *payload = &tap.buf[2];
    int plen = tap.len - 2;
    uint8_t type = payload[0];

    if (type == L1CTL_RACH_CONF && plen >= 12) {
        calypso_l1_rach_conf(ldl_be_p(payload + 8));
        return;
    }
    if ((type != L1CTL_DATA_CONF && type != L1CTL_DATA_IND) || plen < 5) {
        return;
    }
    uint8_t chan_nr = payload[4];
    int kind = -1, ss = 0;
    if ((chan_nr & 0xE0) == 0x20) {
        kind = 0;
        ss = (chan_nr >> 3) & 0x03;
    } else if ((chan_nr & 0xC0) == 0x40) {
        kind = 1;
        ss = (chan_nr >> 3) & 0x07;
    }
    bool tch = ((chan_nr & 0xF8) == 0x08) || ((chan_nr & 0xF0) == 0x10);
    bool dedicated = tch || kind >= 0;
    if (dedicated) {
        calypso_l1_dcch_active();
        calypso_l1_dcch_is_tch(tch);
    }
    if (kind >= 0 && chan_nr != tap.last_chan_nr) {
        tap.last_chan_nr = chan_nr;
        dcch_cfg_publish(kind, ss, chan_nr);
        calypso_l1_dcch_set(kind, ss);
    }
}

void calypso_l1ctl_tap_tx_byte(uint8_t byte)
{
    switch (tap.state) {
    case SC_IDLE:
        if (byte == SERCOMM_FLAG) {
            tap.state = SC_IN_FRAME;
            tap.len = 0;
        }
        break;
    case SC_IN_FRAME:
        if (byte == SERCOMM_FLAG) {
            if (tap.len > 0) {
                frame_complete();
            }
            tap.len = 0;
        } else if (byte == SERCOMM_ESCAPE) {
            tap.state = SC_ESCAPE;
        } else if (tap.len < (int)sizeof(tap.buf)) {
            tap.buf[tap.len++] = byte;
        }
        break;
    case SC_ESCAPE:
        if (tap.len < (int)sizeof(tap.buf)) {
            tap.buf[tap.len++] = byte ^ SERCOMM_ESCAPE_XOR;
        }
        tap.state = SC_IN_FRAME;
        break;
    }
}

void calypso_l1ctl_tap_channel_released(void)
{
    if (tap.last_chan_nr == 0xFF) {
        return;
    }
    tap.last_chan_nr = 0xFF;
    dcch_cfg_publish(DCCH_RELEASED, 0, 0);
}

hw/arm/calypso/calypso_l1_grgsm.c

.c 1193 lignes - 32KB

hw/arm/calypso/calypso_l1_grgsm.c
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "qemu/osdep.h"
#include "qemu/main-loop.h"
#include "qemu/bswap.h"
#include "exec/cpu-common.h"
#include "hw/arm/calypso/calypso_api.h"
#include "hw/arm/calypso/calypso_l1.h"
#include "hw/arm/calypso/calypso_l1ctl_tap.h"
#include "hw/arm/calypso/calypso_trf6151.h"
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>

#define GSMTAP_PORT             4730
#define SCH_PORT                4731
#define GSMTAP_HDR_LEN          16
#define GSMTAP_TYPE_UM          0x01
#define GSMTAP_CHANNEL_BCCH     0x01
#define GSMTAP_CHANNEL_AGCH     0x04
#define GSMTAP_CHANNEL_SDCCH4   0x07
#define GSMTAP_CHANNEL_TCH_F    0x09
#define GSMTAP_CHANNEL_ACCH     0x80
#define GSMTAP_CHANNEL_SACCH    (GSMTAP_CHANNEL_SDCCH4 | GSMTAP_CHANNEL_ACCH)
#define GSMTAP_CHANNEL_TCH_ACCH (GSMTAP_CHANNEL_TCH_F | GSMTAP_CHANNEL_ACCH)

#define SHM_RACH        "/dev/shm/calypso_rach"
#define SHM_SDCCH_UL    "/dev/shm/calypso_sdcch_ul"
#define SHM_FACCH_UL    "/dev/shm/calypso_tch_facch_ul"
#define SHM_TSACCH_UL   "/dev/shm/calypso_tch_sacch_ul"
#define SHM_TCH_UL      "/dev/shm/calypso_tch_ul"
#define SHM_TCH_DL      "/dev/shm/calypso_tch_dl"
#define SHM_TCH_CFG     "/dev/shm/calypso_tch_cfg"
#define SHM_KC          "/dev/shm/calypso_kc_l1"

#define TOA_ON_TIME     23
#define SNR_NOMINAL     0x7000
#define RF_SERVING_DBM  (-60)
#define RF_SILENT_DBM   (-110)

#define SB_MAX_AGE_FRAMES   104
#define AGCH_TTL_TICKS      100
#define SDCCH_TTL_TICKS     4000
#define SDCCH_MAX_PRESENT   8
#define SDCCH_RING_N        32
#define SDCCH_UL_DEDUP_TICKS 60
#define SDCCH_UL_WINDOW_OFS 6
#define DCCH_GUARD_TICKS    108
#define TCH_DL_Q_N          8
#define TCH_DL_PREFETCH     4
#define TCH_DL_SLOT         48
#define TCH_DL_HOLD         5
#define TCH_TTL_TICKS       26
#define TCH_UL_SLOTS        16
#define TCH_UL_SLOT_SZ      64
#define KC_PUBLISH_EVERY    22
#define KC_RECLEN           32

#define L1_LOG(fmt, ...) fprintf(stderr, "[l1] " fmt "\n", ##__VA_ARGS__)

struct sdcch_entry {
    uint8_t  l2[23];
    uint32_t fn;
    uint32_t tick;
    uint16_t reps;
};

static struct {
    bool     pending;
    uint8_t  page;
    uint16_t d_task_md, d_task_d, d_task_u, d_task_ra, d_burst_d, d_fn;
    uint32_t tick;

    uint8_t  si_set[6][23];
    bool     si_have[6];
    uint8_t  si_buf[23];
    bool     si_valid;
    int      si_rr;
    unsigned si_rot;
    uint8_t  sacch_buf[23];
    bool     sacch_have;
    bool     sacch_real;

    int      serving_arfcn;
    uint8_t  sb_bsic;
    uint32_t sb_fn;
    bool     sb_valid;
    uint32_t sb_capture_fn;

    uint8_t  agch_buf[23];
    bool     agch_valid;
    uint32_t agch_tick;

    struct sdcch_entry sdcch_ring[SDCCH_RING_N];
    uint32_t sdcch_head, sdcch_tail;
    uint32_t sdcch_last_fn;
    bool     sdcch_valid;
    uint8_t  sdcch_ss;
    bool     sdcch_ss_set;
    bool     sdcch_ch8;

    bool     dcch_guard_armed;
    uint32_t dcch_guard_tick;
    bool     dcch_is_tch;

    uint8_t  tch_dl_q[TCH_DL_Q_N][33];
    uint32_t tch_dl_q_seq[TCH_DL_Q_N];
    unsigned tch_dl_q_head, tch_dl_q_tail;
    uint32_t tch_dl_seq;
    uint8_t  facch_dl[23];
    bool     facch_dl_valid;
    uint32_t facch_dl_tick;
    uint8_t  tsacch_dl[23];
    bool     tsacch_dl_valid;
    uint32_t tsacch_dl_tick;
    bool     tch_cfg_valid;

    uint8_t  last_ra;
    uint32_t rach_conf_fn[256];

    uint32_t l1s_addr;
    uint32_t last_rach_addr;
} g;

static uint32_t elf_symbol(const char *path, const char *want)
{
    FILE *f = fopen(path, "rb");
    if (!f) {
        return 0;
    }
    fseek(f, 0, SEEK_END);
    long sz = ftell(f);
    fseek(f, 0, SEEK_SET);
    if (sz < 52 || sz > (64L << 20)) {
        fclose(f);
        return 0;
    }
    uint8_t *b = g_malloc((size_t)sz);
    size_t got = fread(b, 1, (size_t)sz, f);
    fclose(f);
    uint32_t ret = 0;
    if (got == (size_t)sz && b[0] == 0x7f && b[1] == 'E' && b[2] == 'L' &&
        b[3] == 'F' && b[4] == 1) {
#define R16(o) ((uint32_t)b[o] | ((uint32_t)b[(o) + 1] << 8))
#define R32(o) (R16(o) | (R16((o) + 2) << 16))
        uint32_t shoff = R32(0x20), shent = R16(0x2e), shnum = R16(0x30);
        for (uint32_t si = 0; si < shnum; si++) {
            uint32_t sh = shoff + si * shent;
            if ((long)(sh + 40) > sz) {
                break;
            }
            if (R32(sh + 4) != 2) {
                continue;
            }
            uint32_t symoff = R32(sh + 0x10), symsz = R32(sh + 0x14);
            uint32_t link = R32(sh + 0x18), entsz = R32(sh + 0x24);
            uint32_t strsh = shoff + link * shent;
            if ((long)(strsh + 40) > sz || entsz < 16) {
                break;
            }
            uint32_t stroff = R32(strsh + 0x10), strsz = R32(strsh + 0x14);
            for (uint32_t o = 0; o + 16 <= symsz && (long)(symoff + o + 16) <= sz;
                 o += entsz) {
                uint32_t ni = R32(symoff + o);
                if (ni < strsz && !strcmp((const char *)(b + stroff + ni), want)) {
                    ret = R32(symoff + o + 4);
                    break;
                }
            }
            break;
        }
#undef R16
#undef R32
    }
    g_free(b);
    return ret;
}

static uint32_t arm_read32(uint32_t addr)
{
    uint32_t v = 0;
    if (!addr) {
        return 0;
    }
    cpu_physical_memory_read(addr, &v, sizeof(v));
    return le32_to_cpu(v);
}

uint32_t calypso_l1s_fn(void)
{
    return arm_read32(g.l1s_addr);
}

static uint32_t last_rach_fn(void)
{
    return arm_read32(g.last_rach_addr);
}

static int tc51(long fn)
{
    return (int)((fn % 51 + 51) % 51);
}

static void ndb_put_l2(uint16_t *w, const uint8_t *l2)
{
    for (int i = 0; i < 23; i += 2) {
        uint8_t lo = l2[i], hi = (i + 1 < 23) ? l2[i + 1] : 0x2B;
        w[3 + i / 2] = (uint16_t)(lo | (hi << 8));
    }
}

static void ndb_put_fr(uint16_t *w, const uint8_t *fr)
{
    for (int i = 0; i < 32; i += 2) {
        w[3 + i / 2] = (uint16_t)(((uint16_t)fr[i] << 8) | fr[i + 1]);
    }
    w[3 + 16] = (uint16_t)((uint16_t)fr[32] << 8);
}

static void ndb_block(unsigned off, uint16_t hdr, const uint8_t *l2)
{
    uint16_t *w = api_ndb(off);
    w[0] = hdr;
    w[1] = 0;
    w[2] = 0;
    ndb_put_l2(w, l2);
}

static void rp_serv_demod(uint8_t page, uint16_t toa, uint16_t pm)
{
    uint16_t *d = api_rp(page, RP_A_SERV_DEMOD);
    d[D_TOA] = toa;
    d[D_PM] = pm;
    d[D_ANGLE] = 0;
    d[D_SNR] = SNR_NOMINAL;
}

static bool on_serving_cell(void)
{
    int tuned = calypso_trf6151_arfcn();
    return tuned < 0 || tuned == g.serving_arfcn;
}

static uint16_t apm_nominal(void)
{
    return calypso_trf6151_apm_for_rf(on_serving_cell() ? RF_SERVING_DBM : RF_SILENT_DBM);
}

static uint32_t encode_sb(uint8_t bsic, uint32_t fn)
{
    uint16_t t1 = (uint16_t)(fn / (26u * 51u));
    uint8_t t2 = (uint8_t)(fn % 26u);
    uint8_t t3 = (uint8_t)(fn % 51u);
    uint8_t t3p = (t3 == 0) ? 0 : ((t3 - 1) / 10);
    uint32_t sb = 0;
    sb |= ((uint32_t)(bsic & 0x3f)) << 2;
    sb |= ((uint32_t)(t1 & 0x001)) << 23;
    sb |= ((uint32_t)(t1 & 0x1fe)) << 7;
    sb |= ((uint32_t)(t1 & 0x600)) >> 9;
    sb |= ((uint32_t)(t2 & 0x1f)) << 18;
    sb |= ((uint32_t)(t3p & 1)) << 24;
    sb |= ((uint32_t)(t3p & 6)) << 15;
    return sb;
}

static void dispatch_pm(uint8_t page)
{
    uint16_t pm = apm_nominal();
    uint16_t *a = api_rp(page, RP_A_PM);
    a[0] = pm;
    a[1] = pm;
    a[2] = pm;
    *api_rp(page, RP_D_TASK_MD) = PM_DSP_TASK;
}

static void dispatch_sb(uint8_t page)
{
    if (!g.sb_valid || !on_serving_cell()) {
        return;
    }
    if ((int)(calypso_trx_get_fn() - g.sb_capture_fn) > SB_MAX_AGE_FRAMES) {
        return;
    }
    uint32_t sb = encode_sb(g.sb_bsic, g.sb_fn);
    uint16_t *a = api_rp(page, RP_A_SCH);
    a[0] = 0;
    a[1] = 0;
    a[2] = 0;
    a[3] = (uint16_t)(sb & 0xFFFF);
    a[4] = (uint16_t)(sb >> 16);
    rp_serv_demod(page, TOA_ON_TIME, apm_nominal());
    *api_rp(page, RP_D_TASK_MD) = SB_DSP_TASK;
}

static void present_ccch(uint8_t page, const uint8_t *l2)
{
    ndb_block(NDB_A_CD, 0, l2);
    *api_rp(page, RP_D_TASK_D) = ALLC_DSP_TASK;
    rp_serv_demod(page, TOA_ON_TIME, apm_nominal());
}

static void rotate_si(void)
{
    for (int k = 1; k <= 6; k++) {
        int s = (g.si_rr + k) % 6;
        if (g.si_have[s]) {
            g.si_rr = s;
            return;
        }
    }
}

static bool dcch_si_guard(void)
{
    if (!g.dcch_guard_armed) {
        return false;
    }
    if (g.tick - g.dcch_guard_tick > DCCH_GUARD_TICKS) {
        g.dcch_guard_armed = false;
        calypso_l1ctl_tap_channel_released();
        L1_LOG("canal dedie libere");
        return false;
    }
    return true;
}

static void dispatch_allc(uint8_t page)
{
    if (g.agch_valid) {
        if (g.tick - g.agch_tick > AGCH_TTL_TICKS) {
            g.agch_valid = false;
        } else {
            int tc = tc51(calypso_l1s_fn());
            if ((tc >= 6 && tc <= 9) || (tc >= 12 && tc <= 19)) {
                present_ccch(page, g.agch_buf);
                return;
            }
        }
    }

    int lo = g.sdcch_ss_set ? (g.sdcch_ch8 ? 0 : 22) : 0;
    int hi = g.sdcch_ss_set ? (g.sdcch_ch8 ? 31 : 39) : 39;
    while (g.sdcch_tail != g.sdcch_head) {
        struct sdcch_entry *e = &g.sdcch_ring[g.sdcch_head % SDCCH_RING_N];
        if (g.tick - e->tick > SDCCH_TTL_TICKS) {
            g.sdcch_head++;
            continue;
        }
        int tc = tc51(calypso_l1s_fn());
        if (tc < lo || tc > hi) {
            break;
        }
        present_ccch(page, e->l2);
        e->reps++;
        if (g.d_burst_d >= 3 || e->reps >= SDCCH_MAX_PRESENT) {
            g.sdcch_head++;
        }
        if (g.sdcch_tail == g.sdcch_head) {
            g.sdcch_valid = false;
        }
        return;
    }
    if (g.sdcch_tail == g.sdcch_head) {
        g.sdcch_valid = false;
    }

    if (g.sacch_have) {
        int tc = tc51(calypso_l1s_fn());
        if (tc >= 42 && tc <= 46) {
            present_ccch(page, g.sacch_buf);
            return;
        }
    }

    if (g.sdcch_valid) {
        return;
    }
    if (g.d_burst_d == 0) {
        rotate_si();
        memcpy(g.si_buf, g.si_set[g.si_rr], 23);
    }
    if (g.dcch_guard_armed) {
        *api_ndb(NDB_A_CD) = 0x0003;
        *api_rp(page, RP_D_TASK_D) = ALLC_DSP_TASK;
        return;
    }
    ndb_block(NDB_A_CD, 0, g.si_buf);
    for (uint8_t p = 0; p < 2; p++) {
        *api_rp(p, RP_D_TASK_D) = ALLC_DSP_TASK;
        rp_serv_demod(p, TOA_ON_TIME, apm_nominal());
    }
}

static bool tch_fresh(bool valid, uint32_t tick)
{
    return valid && (g.tick - tick <= TCH_TTL_TICKS);
}

static void tch_serv_demod(void)
{
    uint16_t pm = apm_nominal();
    rp_serv_demod(0, TOA_ON_TIME, pm);
    rp_serv_demod(1, TOA_ON_TIME, pm);
}

static unsigned tch_dl_depth(void)
{
    return g.tch_dl_q_tail - g.tch_dl_q_head;
}

static void tch_dl_push(const uint8_t *fr, uint32_t seq)
{
    unsigned i = g.tch_dl_q_tail % TCH_DL_Q_N;
    memcpy(g.tch_dl_q[i], fr, 33);
    g.tch_dl_q_seq[i] = seq;
    g.tch_dl_q_tail++;
}

static void tch_dl_poll(void)
{
    static int fd = -2;
    if (fd == -2) {
        fd = open(SHM_TCH_DL, O_CREAT | O_RDWR, 0644);
    }
    if (fd < 0) {
        return;
    }
    uint8_t hdr[8];
    if (pread(fd, hdr, sizeof(hdr), 0) != (ssize_t)sizeof(hdr)) {
        return;
    }
    uint32_t w_seq, n_slots;
    memcpy(&w_seq, hdr, 4);
    memcpy(&n_slots, hdr + 4, 4);
    if (n_slots == 0 || n_slots > 4096 || w_seq == 0 || w_seq == g.tch_dl_seq) {
        return;
    }
    if (w_seq < g.tch_dl_seq) {
        g.tch_dl_seq = 0;
        g.tch_dl_q_head = g.tch_dl_q_tail = 0;
    }
    if (tch_dl_depth() >= TCH_DL_PREFETCH) {
        return;
    }
    uint32_t next = g.tch_dl_seq + 1;
    if (g.tch_dl_seq == 0 || (w_seq - next) >= n_slots) {
        uint32_t behind = (w_seq > next) ? (w_seq - next) : 0;
        if (g.tch_dl_seq && behind >= n_slots) {
            static unsigned nlog;
            if (nlog++ < 10) {
                L1_LOG("TCH DL : %u trames sautees (anneau de %u)",
                       behind - (n_slots - 1), n_slots);
            }
        }
        next = w_seq;
    }
    uint8_t buf[TCH_DL_SLOT];
    off_t off = 8 + (off_t)((next - 1) % n_slots) * TCH_DL_SLOT;
    if (pread(fd, buf, sizeof(buf), off) != (ssize_t)sizeof(buf)) {
        return;
    }
    uint32_t sseq;
    memcpy(&sseq, buf, 4);
    if (sseq != next) {
        return;
    }
    g.tch_dl_seq = next;
    tch_dl_push(buf + 8, next);
}

static void dispatch_tch_dl(void)
{
    tch_serv_demod();
    static unsigned waited;
    bool ready = true;
    if (*api_ndb(NDB_A_DD_0) & B_BLUD) {
        if (waited < TCH_DL_HOLD) {
            waited++;
            ready = false;
        }
    }
    if (ready) {
        waited = 0;
        if (g.tch_dl_q_head != g.tch_dl_q_tail) {
            unsigned i = g.tch_dl_q_head++ % TCH_DL_Q_N;
            uint16_t *w = api_ndb(NDB_A_DD_0);
            w[0] = B_BLUD;
            w[1] = 0;
            w[2] = 0;
            ndb_put_fr(w, g.tch_dl_q[i]);
        }
    }
    if (tch_fresh(g.facch_dl_valid, g.facch_dl_tick)) {
        ndb_block(NDB_A_FD, B_BLUD, g.facch_dl);
        g.facch_dl_valid = false;
    }
}

static void dispatch_tch_sacch(void)
{
    tch_serv_demod();
    if (!tch_fresh(g.tsacch_dl_valid, g.tsacch_dl_tick)) {
        return;
    }
    ndb_block(NDB_A_CD, B_BLUD, g.tsacch_dl);
    g.tsacch_dl_valid = false;
}

static void dcch_sacch_present(void)
{
    static const uint8_t base4[4] = { 42, 46, 93, 97 };
    static const uint8_t base8[8] = { 32, 36, 40, 44, 83, 87, 91, 95 };
    if (g.dcch_is_tch) {
        return;
    }
    uint32_t b;
    if (g.sdcch_ch8) {
        b = base8[(g.sdcch_ss / 4u) & 7u];
    } else {
        unsigned idx = 0;
        switch (g.sdcch_ss) {
        case 26: idx = 1; break;
        case 32: idx = 2; break;
        case 36: idx = 3; break;
        default: break;
        }
        b = base4[idx];
    }
    uint32_t f102 = calypso_trx_get_fn() % 102u;
    if (f102 < b || f102 > b + 3u) {
        return;
    }
    if (*api_ndb(NDB_A_CD) & B_BLUD) {
        return;
    }
    if (!g.sacch_have) {
        return;
    }
    ndb_block(NDB_A_CD, B_BLUD, g.sacch_buf);
}

static int sideband_open(const char *path, off_t len)
{
    int fd = open(path, O_CREAT | O_RDWR, 0644);
    if (fd >= 0 && ftruncate(fd, len) < 0) {
        close(fd);
        return -1;
    }
    return fd;
}

static void sideband_write(int fd, const void *buf, size_t n, off_t off)
{
    if (fd >= 0 && pwrite(fd, buf, n, off) < 0) {
        return;
    }
}

static void publish_l2(int *fdp, const char *path, uint32_t *seq, const uint8_t *l2,
                       uint16_t task_u)
{
    if (*fdp == -2) {
        *fdp = sideband_open(path, 48);
    }
    uint32_t fn = calypso_trx_get_fn(), l1s = calypso_l1s_fn();
    uint8_t buf[48] = {0};
    (*seq)++;
    memcpy(buf + 0, seq, 4);
    memcpy(buf + 4, &l1s, 4);
    memcpy(buf + 8, &fn, 4);
    memcpy(buf + 12, &task_u, 2);
    buf[14] = (uint8_t)(l1s % 51);
    memcpy(buf + 16, l2, 23);
    sideband_write(*fdp, buf, sizeof(buf), 0);
}

static void publish_speech(const uint8_t *fr)
{
    static int fd = -2;
    static uint32_t seq;
    if (fd == -2) {
        fd = sideband_open(SHM_TCH_UL, 8 + TCH_UL_SLOTS * TCH_UL_SLOT_SZ);
        uint32_t hdr[2] = { 0, TCH_UL_SLOTS };
        sideband_write(fd, hdr, sizeof(hdr), 0);
    }
    uint32_t fn = calypso_trx_get_fn(), l1s = calypso_l1s_fn();
    uint8_t buf[TCH_UL_SLOT_SZ] = {0};
    seq++;
    memcpy(buf + 0, &seq, 4);
    memcpy(buf + 4, &l1s, 4);
    memcpy(buf + 8, &fn, 4);
    memcpy(buf + 16, fr, 33);
    sideband_write(fd, buf, sizeof(buf), 8 + (off_t)((seq - 1) % TCH_UL_SLOTS) * TCH_UL_SLOT_SZ);
    sideband_write(fd, &seq, 4, 0);
}

static bool take_ul(unsigned off, uint8_t *out, int n)
{
    uint16_t *w = api_ndb(off);
    if (!(w[0] & B_BLUD)) {
        return false;
    }
    for (int i = 0; i < n; i += 2) {
        uint16_t v = w[3 + i / 2];
        uint8_t first = (n == 33) ? (uint8_t)(v >> 8) : (uint8_t)(v & 0xff);
        uint8_t second = (n == 33) ? (uint8_t)(v & 0xff) : (uint8_t)(v >> 8);
        out[i] = first;
        if (i + 1 < n) {
            out[i + 1] = second;
        }
    }
    w[0] &= (uint16_t)~B_BLUD;
    return true;
}

static bool capture_tch_ul(uint16_t task_u)
{
    static int fd_facch = -2, fd_sacch = -2;
    static uint32_t seq_facch, seq_sacch;
    uint8_t l2[23], fr[33];
    switch (task_u & 0x7FFF) {
    case TCHT_DSP_TASK:
        if (take_ul(NDB_A_FU, l2, 23)) {
            publish_l2(&fd_facch, SHM_FACCH_UL, &seq_facch, l2, task_u);
        }
        if (take_ul(NDB_A_DU_1, fr, 33)) {
            publish_speech(fr);
        }
        return true;
    case TCHA_DSP_TASK:
        if (take_ul(NDB_A_CU, l2, 23)) {
            publish_l2(&fd_sacch, SHM_TSACCH_UL, &seq_sacch, l2, task_u);
        }
        return true;
    case TCHD_DSP_TASK:
        return true;
    default:
        return false;
    }
}

static void capture_sdcch_ul(uint16_t task_u)
{
    static int fd = -2;
    static uint32_t seq;
    static uint8_t last[23];
    static uint32_t last_tick;
    static bool have_last;

    uint8_t win[30];
    const uint8_t *src = (const uint8_t *)api_ndb(NDB_A_CU + SDCCH_UL_WINDOW_OFS);
    memcpy(win, src, sizeof(win));
    int kk = 0;
    for (int j = 0; j <= 6; j++) {
        uint8_t a = win[j], c = win[j + 1], l = win[j + 2];
        int sapi = (a >> 2) & 7;
        bool addr_ok = (a & 0x01) && ((a & 0x60) == 0) && (sapi == 0 || sapi == 3);
        bool ctrl_ok = (c != 0x2b) && (c != 0xff);
        bool len_ok = (l & 0x01) && ((l >> 2) <= 20);
        if (addr_ok && ctrl_ok && len_ok) {
            kk = j;
            break;
        }
    }
    const uint8_t *l2 = win + kk;
    if (have_last && !memcmp(last, l2, 23) && g.tick - last_tick < SDCCH_UL_DEDUP_TICKS) {
        return;
    }
    memcpy(last, l2, 23);
    last_tick = g.tick;
    have_last = true;
    if (l2[1] == 0x03) {
        return;
    }
    publish_l2(&fd, SHM_SDCCH_UL, &seq, l2, task_u);
}

static void l1_reset(void)
{
    static unsigned n;
    if (n++ < 3) {
        L1_LOG("reset L1 (d_dsp_page=0)");
    }
    g.agch_valid = false;
    g.sdcch_valid = false;
    g.sdcch_ss_set = false;
    g.dcch_guard_armed = false;
    calypso_l1ctl_tap_channel_released();
}

void calypso_l1_page_written(uint16_t v)
{
    if (!(v & B_GSM_TASK)) {
        if (v == 0) {
            l1_reset();
        }
        return;
    }
    uint8_t page = (v & B_GSM_PAGE) ? 1 : 0;
    g.page = page;
    g.d_task_d = *api_wp(page, WP_D_TASK_D);
    g.d_task_u = *api_wp(page, WP_D_TASK_U);
    g.d_task_md = *api_wp(page, WP_D_TASK_MD);
    g.d_task_ra = *api_wp(page, WP_D_TASK_RA);
    g.d_fn = *api_wp(page, WP_D_FN);
    if (g.d_fn == 0) {
        g.d_fn = (uint16_t)(calypso_trx_get_fn() & 0xFFFF);
    }
    g.pending = true;

    if (g.dcch_guard_armed &&
        (g.d_task_u == DUL_DSP_TASK || g.d_task_u == TCHT_DSP_TASK ||
         g.d_task_u == TCHA_DSP_TASK)) {
        g.dcch_guard_tick = g.tick;
    }
    if (g.d_task_u != 0 && !capture_tch_ul(g.d_task_u)) {
        capture_sdcch_ul(g.d_task_u);
    }
}

void calypso_l1_burst_written(uint16_t d_burst_d)
{
    g.d_burst_d = (uint16_t)(d_burst_d & 3);
}

void calypso_l1_rach_written(uint16_t d_rach, uint32_t fn)
{
    static int fd = -2;
    static uint32_t seq;
    if (fd == -2) {
        fd = sideband_open(SHM_RACH, 16);
    }
    uint8_t ra = (uint8_t)(d_rach >> 8);
    uint8_t bsic = (uint8_t)((d_rach & 0xFF) >> 2);
    uint8_t buf[16] = {0};
    seq++;
    memcpy(buf + 0, &seq, 4);
    buf[4] = ra;
    buf[5] = bsic;
    memcpy(buf + 8, &fn, 4);
    sideband_write(fd, buf, sizeof(buf), 0);
    g.last_ra = ra;
}

void calypso_l1_rach_conf(uint32_t fn)
{
    g.rach_conf_fn[g.last_ra] = fn;
}

bool calypso_l1_read_override(uint32_t off, uint16_t *out)
{
    if (!g.sb_valid || !on_serving_cell()) {
        switch (off) {
        case API_NDB + NDB_D_FB_DET:
        case API_NDB + NDB_A_SYNC_DEMOD + 2 * D_TOA:
        case API_NDB + NDB_A_SYNC_DEMOD + 2 * D_PM:
        case API_NDB + NDB_A_SYNC_DEMOD + 2 * D_ANGLE:
        case API_NDB + NDB_A_SYNC_DEMOD + 2 * D_SNR:
            *out = 0;
            return true;
        default:
            return false;
        }
    }
    switch (off) {
    case API_NDB + NDB_D_FB_DET:
        *out = 1;
        return true;
    case API_NDB + NDB_A_SYNC_DEMOD + 2 * D_PM:
        *out = apm_nominal();
        return true;
    case API_NDB + NDB_A_SYNC_DEMOD + 2 * D_TOA:
    case API_NDB + NDB_A_SYNC_DEMOD + 2 * D_ANGLE:
    case API_R_PAGE(0) + RP_A_SERV_DEMOD + 2 * D_TOA:
    case API_R_PAGE(1) + RP_A_SERV_DEMOD + 2 * D_TOA:
        *out = 0;
        return true;
    case API_NDB + NDB_A_SYNC_DEMOD + 2 * D_SNR:
        *out = SNR_NOMINAL;
        return true;
    default:
        return false;
    }
}

bool calypso_l1_si_valid(void)
{
    return g.si_valid;
}

void calypso_l1_dcch_set(int kind, int ss)
{
    static const uint8_t b4[4] = { 22, 26, 32, 36 };
    uint8_t base = kind ? (uint8_t)((ss & 7) * 4) : b4[ss & 3];
    if (g.sdcch_ss_set && g.sdcch_ss == base && g.sdcch_ch8 == (kind != 0)) {
        return;
    }
    g.sdcch_ss = base;
    g.sdcch_ss_set = true;
    g.sdcch_ch8 = (kind != 0);
    L1_LOG("canal dedie SDCCH/%d SS=%d : fenetre a_cd fn%%51 %u-%u",
           kind ? 8 : 4, ss, base, base + 3);
}

void calypso_l1_dcch_active(void)
{
    g.dcch_guard_tick = g.tick;
    g.dcch_guard_armed = true;
}

void calypso_l1_dcch_is_tch(bool on)
{
    g.dcch_is_tch = on;
}

static void feed_agch(const uint8_t *l2, int len)
{
    uint8_t mt = l2[2];
    bool imm = (mt == 0x3f || mt == 0x3a || mt == 0x3b);
    uint8_t cur = g.agch_buf[2];
    bool cur_imm = (cur == 0x3f || cur == 0x3a || cur == 0x3b);
    if (!imm && g.agch_valid && cur_imm && g.tick - g.agch_tick <= AGCH_TTL_TICKS) {
        return;
    }
    int n = len < 23 ? len : 23;
    memcpy(g.agch_buf, l2, n);
    memset(g.agch_buf + n, 0x2B, 23 - n);

    if (mt == 0x3f && n >= 10) {
        uint8_t ra = g.agch_buf[7];
        uint32_t lr = last_rach_fn();
        uint32_t memo = lr ? lr : g.rach_conf_fn[ra];
        if (memo) {
            uint16_t t1p = (uint16_t)((memo / 1326u) % 32u);
            uint8_t t2 = (uint8_t)(memo % 26u);
            uint8_t t3 = (uint8_t)(memo % 51u);
            g.agch_buf[8] = (uint8_t)((t1p << 3) | ((t3 >> 3) & 7));
            g.agch_buf[9] = (uint8_t)(((t3 & 7) << 5) | (t2 & 0x1f));
        }
    }
    g.agch_valid = true;
    g.agch_tick = g.tick;
}

static void feed_sdcch(const uint8_t *l2, int len, uint32_t fn)
{
    if (fn && fn == g.sdcch_last_fn) {
        return;
    }
    g.sdcch_last_fn = fn;
    if (g.sdcch_tail - g.sdcch_head >= SDCCH_RING_N) {
        static unsigned nlog;
        if (nlog++ < 5) {
            L1_LOG("SDCCH DL : anneau plein, plus ancien bloc abandonne");
        }
        g.sdcch_head++;
    }
    struct sdcch_entry *e = &g.sdcch_ring[g.sdcch_tail % SDCCH_RING_N];
    int n = len < 23 ? len : 23;
    memcpy(e->l2, l2, n);
    memset(e->l2 + n, 0x2B, 23 - n);
    e->fn = fn;
    e->tick = g.tick;
    e->reps = 0;
    g.sdcch_tail++;
    g.sdcch_valid = true;
}

static void feed_sacch(const uint8_t *l2, int len)
{
    int n = len < 23 ? len : 23;
    bool si56 = false;
    for (int i = 2; i + 1 < n && i < 8; i++) {
        if (l2[i] == 0x06 && (l2[i + 1] == 0x1d || l2[i + 1] == 0x1e)) {
            si56 = true;
            break;
        }
    }
    if (!si56) {
        return;
    }
    memcpy(g.sacch_buf, l2, n);
    memset(g.sacch_buf + n, 0x2b, 23 - n);
    g.sacch_have = true;
    g.sacch_real = true;
}

static void feed_l2_23(uint8_t *dst, bool *valid, uint32_t *tick,
                       const uint8_t *l2, int len)
{
    int n = len < 23 ? len : 23;
    memcpy(dst, l2, n);
    memset(dst + n, 0x2B, 23 - n);
    *valid = true;
    *tick = g.tick;
}

static void feed_si(const uint8_t *l2, int len)
{
    int n = len < 23 ? len : 23;
    int slot = -1;
    if (n >= 3 && l2[1] == 0x06 && l2[2] >= 0x19 && l2[2] <= 0x1e) {
        slot = l2[2] - 0x19;
    }
    if (slot >= 0) {
        memcpy(g.si_set[slot], l2, n);
        memset(g.si_set[slot] + n, 0x2B, 23 - n);
        g.si_have[slot] = true;
    }
    if (slot == 2 && n >= 10 && !g.sacch_real) {
        uint8_t *s6 = g.sacch_buf;
        memset(s6, 0x2b, 23);
        s6[0] = 0x00;
        s6[1] = 0x00;
        s6[2] = 0x03;
        s6[3] = 0x03;
        s6[4] = (uint8_t)((11 << 2) | 0x01);
        s6[5] = 0x06;
        s6[6] = 0x1e;
        memcpy(s6 + 7, l2 + 3, 7);
        s6[14] = 0x0f;
        s6[15] = 0xff;
        g.sacch_have = true;
    }
    memcpy(g.si_buf, l2, n);
    memset(g.si_buf + n, 0x2B, 23 - n);
    g.si_valid = true;
}

static void gsmtap_readable(void *opaque)
{
    int fd = (int)(intptr_t)opaque;
    uint8_t buf[512];
    for (;;) {
        ssize_t n = recv(fd, buf, sizeof(buf), MSG_DONTWAIT);
        if (n < GSMTAP_HDR_LEN + 3) {
            if (n < 0) {
                break;
            }
            continue;
        }
        if (buf[2] != GSMTAP_TYPE_UM) {
            continue;
        }
        uint8_t sub = buf[12];
        const uint8_t *l2 = buf + GSMTAP_HDR_LEN;
        int len = (int)n - GSMTAP_HDR_LEN;
        uint8_t mt = l2[2];
        switch (sub) {
        case GSMTAP_CHANNEL_BCCH:
            if (l2[1] == 0x06 && mt >= 0x19 && mt <= 0x1e) {
                feed_si(l2, len);
            }
            break;
        case GSMTAP_CHANNEL_AGCH:
            if (l2[1] == 0x06 && (mt == 0x3f || mt == 0x39 || mt == 0x3a ||
                                  mt == 0x21 || mt == 0x22 || mt == 0x24)) {
                feed_agch(l2, len);
            }
            break;
        case GSMTAP_CHANNEL_SDCCH4:
            feed_sdcch(l2, len, ldl_be_p(buf + 8));
            break;
        case GSMTAP_CHANNEL_SACCH:
            if (len >= 7) {
                feed_sacch(l2, len);
            }
            break;
        case GSMTAP_CHANNEL_TCH_F:
            feed_l2_23(g.facch_dl, &g.facch_dl_valid, &g.facch_dl_tick, l2, len);
            break;
        case GSMTAP_CHANNEL_TCH_ACCH:
            feed_l2_23(g.tsacch_dl, &g.tsacch_dl_valid, &g.tsacch_dl_tick, l2, len);
            break;
        default:
            break;
        }
    }
}

static void sch_readable(void *opaque)
{
    int fd = (int)(intptr_t)opaque;
    uint8_t buf[64];
    for (;;) {
        ssize_t n = recv(fd, buf, sizeof(buf), MSG_DONTWAIT);
        if (n < 0) {
            break;
        }
        if (n < 16 || memcmp(buf, "SCH2", 4) != 0) {
            continue;
        }
        int32_t bsic = (int32_t)ldl_le_p(buf + 4);
        int32_t fn = (int32_t)ldl_le_p(buf + 8);
        int32_t arfcn = (int32_t)ldl_le_p(buf + 12);
        bool first = !g.sb_valid;
        g.sb_bsic = (uint8_t)(bsic & 0x3f);
        g.sb_fn = (uint32_t)fn;
        g.serving_arfcn = arfcn;
        g.sb_valid = true;
        g.sb_capture_fn = calypso_trx_get_fn();
        if (first) {
            L1_LOG("synchro gr-gsm : ARFCN=%d BSIC=%u FN=%u", arfcn, g.sb_bsic, g.sb_fn);
        }
        calypso_trx_autosync_fn((uint32_t)fn);
    }
}

static int udp_listen(uint16_t port, IOHandler *handler)
{
    int fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (fd < 0) {
        return -1;
    }
    int one = 1;
    setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
    struct sockaddr_in sa = {
        .sin_family = AF_INET,
        .sin_port = htons(port),
        .sin_addr.s_addr = htonl(INADDR_LOOPBACK),
    };
    if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
        L1_LOG("bind udp/%u : %s", port, strerror(errno));
        close(fd);
        return -1;
    }
    qemu_set_fd_handler(fd, handler, NULL, (void *)(intptr_t)fd);
    return fd;
}

static void poll_tch_cfg(void)
{
    static int fd = -2;
    static uint32_t last;
    if (fd == -2) {
        fd = open(SHM_TCH_CFG, O_CREAT | O_RDWR, 0644);
    }
    if (fd < 0) {
        return;
    }
    uint8_t b[16];
    if (pread(fd, b, sizeof(b), 0) != (ssize_t)sizeof(b)) {
        return;
    }
    uint32_t seq;
    memcpy(&seq, b, 4);
    if (seq == 0) {
        if (g.tch_cfg_valid) {
            g.tch_cfg_valid = false;
            g.facch_dl_valid = g.tsacch_dl_valid = false;
            L1_LOG("canal TCH libere");
        }
        last = 0;
        return;
    }
    if (seq == last) {
        return;
    }
    last = seq;
    g.tch_cfg_valid = true;
    g.facch_dl_valid = g.tsacch_dl_valid = false;
    L1_LOG("canal TCH : TN=%u TSC=%u ARFCN=%u", b[4], b[5], lduw_le_p(b + 6));
}

static void publish_kc(void)
{
    static int fd = -1;
    static uint32_t seq;
    static uint8_t last[KC_RECLEN];
    static bool have_last;
    static int tick;
    if (++tick < KC_PUBLISH_EVERY) {
        return;
    }
    tick = 0;

    uint16_t mode = *api_ndb(NDB_D_A5MODE);
    const uint16_t *kw = api_ndb(NDB_A_KC);
    uint8_t rec[KC_RECLEN] = {0};
    bool nul = true;
    for (int i = 0; i < 4; i++) {
        rec[6 + 6 - 2 * i] = (uint8_t)(kw[i] >> 8);
        rec[6 + 7 - 2 * i] = (uint8_t)(kw[i] & 0xFF);
    }
    for (int i = 6; i < 14; i++) {
        if (rec[i]) {
            nul = false;
        }
    }
    uint8_t algo = (mode >= 1 && mode <= 3 && !nul) ? (uint8_t)mode : 0;
    if (!algo) {
        memset(rec + 6, 0, 8);
    }
    rec[4] = algo;
    rec[5] = algo ? 8 : 0;
    rec[14] = 0xFF;
    if (have_last && !memcmp(last + 4, rec + 4, KC_RECLEN - 4)) {
        return;
    }
    if (fd < 0) {
        fd = open(SHM_KC, O_WRONLY | O_CREAT, 0666);
        if (fd < 0) {
            return;
        }
    }
    seq++;
    memcpy(rec, &seq, 4);
    if (pwrite(fd, rec, sizeof(rec), 0) != (ssize_t)sizeof(rec)) {
        close(fd);
        fd = -1;
        seq--;
        return;
    }
    memcpy(last, rec, sizeof(rec));
    have_last = true;
    if (algo) {
        L1_LOG("chiffrement A5/%u actif (Kc publie, seq=%u)", algo, seq);
    } else {
        L1_LOG("couche 1 en clair (seq=%u)", seq);
    }
}

void calypso_l1_frame_tick(void)
{
    publish_kc();
    tch_dl_poll();
    poll_tch_cfg();

    if (g.sb_valid) {
        dispatch_sb(0);
        dispatch_sb(1);
        bool imm_pending = g.agch_valid &&
            (g.agch_buf[2] == 0x3f || g.agch_buf[2] == 0x3a || g.agch_buf[2] == 0x3b);
        if (g.si_valid && !g.sdcch_valid && !dcch_si_guard() && !g.tch_cfg_valid &&
            !imm_pending) {
            if ((g.si_rot++ & 7u) == 0) {
                rotate_si();
            }
            ndb_block(NDB_A_CD, 0, g.si_set[g.si_rr]);
            tch_serv_demod();
        } else if (dcch_si_guard() && !tch_fresh(g.tsacch_dl_valid, g.tsacch_dl_tick) &&
                   !g.tch_cfg_valid) {
            dcch_sacch_present();
        }
    }

    if (!g.pending) {
        return;
    }
    g.tick++;

    uint8_t page = g.page;
    uint16_t md = g.d_task_md, td = g.d_task_d;
    if (md == PM_DSP_TASK) {
        dispatch_pm(page);
    }
    if (md == SB_DSP_TASK) {
        dispatch_sb(page);
    }
    if (td == ALLC_DSP_TASK) {
        dispatch_allc(page);
    }
    switch (td & 0x7FFF) {
    case TCHT_DSP_TASK:
        dispatch_tch_dl();
        break;
    case TCHA_DSP_TASK:
        dispatch_tch_sacch();
        break;
    default:
        break;
    }
    g.pending = false;
}

void calypso_l1_init(const char *firmware_elf)
{
    memset(&g, 0, sizeof(g));
    g.serving_arfcn = -1;
    if (firmware_elf) {
        g.l1s_addr = elf_symbol(firmware_elf, "l1s");
        g.last_rach_addr = elf_symbol(firmware_elf, "last_rach");
    }
    if (!g.l1s_addr || !g.last_rach_addr) {
        L1_LOG("symboles firmware introuvables (l1s=0x%x last_rach=0x%x) : "
               "fenetres CCCH et req-ref degradees", g.l1s_addr, g.last_rach_addr);
    }
    udp_listen(GSMTAP_PORT, gsmtap_readable);
    udp_listen(SCH_PORT, sch_readable);
    L1_LOG("backend gr-gsm : GSMTAP udp/%d, SCH udp/%d", GSMTAP_PORT, SCH_PORT);
}

hw/arm/calypso/calypso_mb.c

.c 125 lignes - 4,3KB

hw/arm/calypso/calypso_mb.c
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "hw/boards.h"
#include "hw/sysbus.h"
#include "hw/irq.h"
#include "hw/loader.h"
#include "hw/block/flash.h"
#include "sysemu/sysemu.h"
#include "sysemu/blockdev.h"
#include "sysemu/block-backend.h"
#include "qemu/error-report.h"
#include "exec/address-spaces.h"
#include "elf.h"
#include "target/arm/cpu.h"
#include "hw/arm/calypso/calypso_soc.h"
#include "hw/arm/calypso/calypso_l1.h"

#define CALYPSO_XRAM_BASE     0x01000000
#define CALYPSO_XRAM_SIZE     (8 * 1024 * 1024)
#define CALYPSO_FLASH_BASE    0x00000000
#define CALYPSO_FLASH_SIZE    (4 * 1024 * 1024)
#define CALYPSO_FLASH_SECTOR  (64 * 1024)
#define FLASH_MFR_INTEL       0x0089
#define FLASH_DEV_28F320J3    0x0018

typedef struct CalypsoMachineState {
    MachineState parent;
    ARMCPU *cpu;
    CalypsoSoCState soc;
    MemoryRegion xram;
    MemoryRegion bootrom;
} CalypsoMachineState;

#define TYPE_CALYPSO_MACHINE MACHINE_TYPE_NAME("calypso")
OBJECT_DECLARE_SIMPLE_TYPE(CalypsoMachineState, CALYPSO_MACHINE)

static void calypso_machine_init(MachineState *machine)
{
    CalypsoMachineState *s = CALYPSO_MACHINE(machine);
    MemoryRegion *sysmem = get_system_memory();
    Error *err = NULL;

    Object *cpuobj = object_new(machine->cpu_type);
    s->cpu = ARM_CPU(cpuobj);
    if (!qdev_realize(DEVICE(cpuobj), NULL, &err)) {
        error_report_err(err);
        exit(1);
    }

    object_initialize_child(OBJECT(machine), "soc", &s->soc, TYPE_CALYPSO_SOC);
    if (!sysbus_realize(SYS_BUS_DEVICE(&s->soc), &err)) {
        error_report_err(err);
        exit(1);
    }
    sysbus_connect_irq(SYS_BUS_DEVICE(&s->soc), 0,
                       qdev_get_gpio_in(DEVICE(&s->cpu->parent_obj), ARM_CPU_IRQ));
    sysbus_connect_irq(SYS_BUS_DEVICE(&s->soc), 1,
                       qdev_get_gpio_in(DEVICE(&s->cpu->parent_obj), ARM_CPU_FIQ));

    memory_region_init_ram(&s->xram, OBJECT(&s->soc.parent_obj), "calypso.xram",
                           CALYPSO_XRAM_SIZE, &error_fatal);
    memory_region_add_subregion(sysmem, CALYPSO_XRAM_BASE, &s->xram);

    DriveInfo *dinfo = drive_get(IF_PFLASH, 0, 0);
    pflash_cfi01_register(CALYPSO_FLASH_BASE, "calypso.flash", CALYPSO_FLASH_SIZE,
                          dinfo ? blk_by_legacy_dinfo(dinfo) : NULL,
                          CALYPSO_FLASH_SECTOR, 1, FLASH_MFR_INTEL, FLASH_DEV_28F320J3,
                          0, 0, 0);

    uint32_t vectors[16];
    for (int i = 0; i < 8; i++) {
        vectors[i] = 0xe59ff018;
    }
    vectors[8] = 0x00820000;
    for (int i = 9; i < 16; i++) {
        vectors[i] = 0x0080001C + 4 * (i - 9);
    }
    memory_region_init_ram(&s->bootrom, NULL, "calypso.bootrom", sizeof(vectors),
                           &error_fatal);
    memory_region_add_subregion_overlap(sysmem, 0x00000000, &s->bootrom, 1);
    memcpy(memory_region_get_ram_ptr(&s->bootrom), vectors, sizeof(vectors));

    if (machine->kernel_filename) {
        uint64_t entry;
        int ret = load_elf(machine->kernel_filename, NULL, NULL, NULL, &entry, NULL,
                           NULL, NULL, 0, EM_ARM, 1, 0);
        if (ret < 0) {
            ret = load_image_targphys(machine->kernel_filename, CALYPSO_XRAM_BASE,
                                      CALYPSO_XRAM_SIZE);
            if (ret < 0) {
                error_report("firmware illisible : '%s'", machine->kernel_filename);
                exit(1);
            }
            entry = CALYPSO_XRAM_BASE;
        }
        cpu_set_pc(CPU(s->cpu), entry);
    }

    calypso_l1_init(machine->kernel_filename);
}

static void calypso_machine_class_init(ObjectClass *oc, void *data)
{
    MachineClass *mc = MACHINE_CLASS(oc);
    mc->desc = "TI Calypso baseband (osmocom-bb firmware, couche 1 gr-gsm)";
    mc->init = calypso_machine_init;
    mc->max_cpus = 1;
    mc->default_cpu_type = ARM_CPU_TYPE_NAME("arm946");
    mc->default_ram_size = 0;
}

static const TypeInfo calypso_machine_info = {
    .name = TYPE_CALYPSO_MACHINE,
    .parent = TYPE_MACHINE,
    .instance_size = sizeof(CalypsoMachineState),
    .class_init = calypso_machine_class_init,
};

static void calypso_machine_register_types(void)
{
    type_register_static(&calypso_machine_info);
}

type_init(calypso_machine_register_types)

hw/arm/calypso/calypso_sim.c

.c 620 lignes - 18KB

hw/arm/calypso/calypso_sim.c
/* SPDX-License-Identifier: GPL-2.0-or-later */

#include "qemu/osdep.h"
#include "qemu/timer.h"
#include "qemu/main-loop.h"
#include "exec/cpu-common.h"
#include "hw/core/cpu.h"
#include "hw/arm/calypso/calypso_sim.h"

#define SIM_LOG(...) do { fprintf(stderr, "[sim] " __VA_ARGS__); fputc('\n', stderr); } while(0)

#define APDU_MAX_LEN     261
#define RX_FIFO_SIZE     512
#define ATR_DELAY_NS     1000000

static const uint8_t SIM_ATR[] = { 0x3B, 0x02, 0x14, 0x50 };

typedef enum {
    EF_TRANSPARENT = 0,
    EF_LINEAR_FIXED = 1,
    EF_CYCLIC = 2,
    EF_DF = 0xF0,
    EF_MF = 0xF1,
} EfStructure;

typedef struct SimFile {
    uint16_t fid;
    uint16_t parent;
    uint8_t  structure;
    uint16_t size;
    uint8_t  rec_len;
    uint8_t  data[64];
} SimFile;

static SimFile sim_files[] = {
    { 0x3F00, 0x0000, EF_MF, 0, 0, {0} },
    { 0x7F20, 0x3F00, EF_DF, 0, 0, {0} },
    { 0x7F10, 0x3F00, EF_DF, 0, 0, {0} },
    { 0x2FE2, 0x3F00, EF_TRANSPARENT, 10, 0,
      { 0x98, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0xF1 } },
    { 0x6F07, 0x7F20, EF_TRANSPARENT, 9, 0,
      { 0x08, 0x09, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0xF1 } },
    { 0x6F30, 0x7F20, EF_TRANSPARENT, 24, 0,
      { 0x00, 0xF1, 0x10,
        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } },
    { 0x6F38, 0x7F20, EF_TRANSPARENT, 14, 0,
      { 0xFF, 0x33, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00 } },
    { 0x6F78, 0x7F20, EF_TRANSPARENT, 2, 0,
      { 0x00, 0x01 } },
    { 0x6FAD, 0x7F20, EF_TRANSPARENT, 4, 0,
      { 0x00, 0x00, 0x00, 0x02 } },
    { 0x6F7E, 0x7F20, EF_TRANSPARENT, 11, 0,
      { 0xFF, 0xFF, 0xFF, 0xFF,
        0xFF, 0xFF, 0xFF,
        0x00, 0x00,
        0xFF, 0x00 } },
    { 0x6F74, 0x7F20, EF_TRANSPARENT, 16, 0,
      { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
        0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF } },
    { 0x6F46, 0x7F20, EF_TRANSPARENT, 17, 0,
      { 0x01, 'Q','E','M','U','-','S','I','M', ' ',' ',' ',' ',' ',' ',' ',' ' } },
};
#define SIM_FILE_COUNT (sizeof(sim_files) / sizeof(sim_files[0]))

static SimFile *find_file(uint16_t fid, uint16_t parent_pref)
{

    if (parent_pref) {
        for (size_t i = 0; i < SIM_FILE_COUNT; i++)
            if (sim_files[i].fid == fid && sim_files[i].parent == parent_pref)
                return &sim_files[i];
    }
    for (size_t i = 0; i < SIM_FILE_COUNT; i++)
        if (sim_files[i].fid == fid)
            return &sim_files[i];
    return NULL;
}

static int encode_imsi_bcd(const char *imsi_str, uint8_t out[9])
{
    int n = 0;
    while (imsi_str[n] >= '0' && imsi_str[n] <= '9') n++;
    if (n < 1 || n > 15) return -1;
    bool odd = (n & 1) != 0;
    int data_len = (n / 2) + 1;
    out[0] = (uint8_t)data_len;
    out[1] = (uint8_t)(((imsi_str[0] - '0') << 4) | (odd ? 0x9 : 0x1));
    int wpos = 2, ipos = 1;
    while (ipos < n) {
        uint8_t lo = imsi_str[ipos] - '0';
        uint8_t hi = (ipos + 1 < n) ? (imsi_str[ipos + 1] - '0') : 0xF;
        out[wpos++] = (uint8_t)((hi << 4) | lo);
        ipos += 2;
    }
    while (wpos < 9) out[wpos++] = 0xFF;
    return data_len + 1;
}

struct CalypsoSim {
    qemu_irq    irq;
    QEMUTimer  *atr_timer;
    QEMUTimer  *wt_timer;

    uint8_t     ki[16];
    bool        ki_valid;

    uint16_t    cmd, stat, conf1, conf2, maskit, it_cd;

    uint16_t    it;

    uint8_t     apdu[APDU_MAX_LEN];
    int         apdu_pos;
    int         apdu_expected;

    uint8_t     rx[RX_FIFO_SIZE];
    int         rx_head, rx_tail;

    uint16_t    selected_df;
    uint16_t    selected_ef;

    uint8_t     resp_buf[64];
    int         resp_len;

    bool        powered;
};

G_GNUC_UNUSED
static int rx_count(CalypsoSim *s)
{
    int c = s->rx_head - s->rx_tail;
    if (c < 0) c += RX_FIFO_SIZE;
    return c;
}

static void rx_push(CalypsoSim *s, uint8_t b)
{
    int next = (s->rx_head + 1) % RX_FIFO_SIZE;
    if (next == s->rx_tail) {
        SIM_LOG("RX FIFO overflow");
        return;
    }
    s->rx[s->rx_head] = b;
    s->rx_head = next;
}

G_GNUC_UNUSED
static int rx_pop(CalypsoSim *s, uint8_t *out)
{
    if (s->rx_head == s->rx_tail) return 0;
    *out = s->rx[s->rx_tail];
    s->rx_tail = (s->rx_tail + 1) % RX_FIFO_SIZE;
    return 1;
}

static void rx_push_n(CalypsoSim *s, const uint8_t *buf, int n)
{
    for (int i = 0; i < n; i++) rx_push(s, buf[i]);
}

static void update_irq(CalypsoSim *s);

#define WT_DELAY_NS  2000000

static void fire_wt(void *opaque)
{
    CalypsoSim *s = opaque;
    if (rx_count(s) > 0) return;
    s->it |= CALYPSO_SIM_IT_WT;
    SIM_LOG("WT timeout fired (RX FIFO empty)");
    update_irq(s);

}

static void schedule_wt(CalypsoSim *s)
{
    if (s->wt_timer)
        timer_mod_ns(s->wt_timer,
                     qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + WT_DELAY_NS);
}

static void refresh_it_rx(CalypsoSim *s)
{
    if (rx_count(s) > 0) s->it |= CALYPSO_SIM_IT_RX;
    else                 s->it &= ~CALYPSO_SIM_IT_RX;
}

static void update_irq(CalypsoSim *s)
{
    if (!s->irq) return;
    refresh_it_rx(s);
    bool active = (s->it & ~(uint16_t)s->maskit) != 0;
    static unsigned log_count;
    static bool last_active;
    if ((active != last_active && log_count < 30) || (log_count < 10)) {
        SIM_LOG("IRQ %s  IT=0x%04x MASKIT=0x%04x rx_count=%d",
                active ? "RAISE" : "lower",
                s->it, s->maskit, rx_count(s));
        log_count++;
        last_active = active;
    }
    if (active) qemu_irq_raise(s->irq);
    else        qemu_irq_lower(s->irq);
}

static void raise_rx_irq(CalypsoSim *s)
{
    update_irq(s);
}

static void deliver_atr(void *opaque)
{
    CalypsoSim *s = opaque;
    if (!s->powered) return;
    rx_push_n(s, SIM_ATR, sizeof(SIM_ATR));
    SIM_LOG("ATR queued (%zu bytes)", sizeof(SIM_ATR));
    raise_rx_irq(s);
}

G_GNUC_UNUSED
static void schedule_atr(CalypsoSim *s)
{
    timer_mod_ns(s->atr_timer,
                 qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + ATR_DELAY_NS);
}

static int build_select_response(CalypsoSim *s, SimFile *f, uint8_t *out)
{

    int p = 0;
    out[p++] = 0x00; out[p++] = 0x00;
    out[p++] = (f->size >> 8) & 0xFF;
    out[p++] =  f->size       & 0xFF;
    out[p++] = (f->fid >> 8) & 0xFF;
    out[p++] =  f->fid       & 0xFF;
    if (f->structure == EF_DF || f->structure == EF_MF) {
        out[p++] = (f->structure == EF_MF) ? 0x01 : 0x02;
        out[p++] = 0x00;
        out[p++] = 0x00; out[p++] = 0x00;
        out[p++] = 0x00; out[p++] = 0x00;
        out[p++] = 0x00; out[p++] = 0x00;
        out[p++] = 0x00;
        out[p++] = 0x09;
        out[p++] = 0x91;
        out[p++] = 0x00;
        out[p++] = 0x00;
        out[p++] = 0x04;
        out[p++] = 0x00;
        out[p++] = 0x83;
        out[p++] = 0x83;
        out[p++] = 0x83;
        out[p++] = 0x83;
    } else {
        out[p++] = 0x04;
        out[p++] = 0x00;
        out[p++] = 0x00; out[p++] = 0x00;
        out[p++] = 0x00;
        out[p++] = 0xAA;
        out[p++] = 0x00; out[p++] = 0x00;
        out[p++] = 0x00;
        out[p++] = 0x02;
        out[p++] = (f->structure == EF_TRANSPARENT) ? 0x00
                : (f->structure == EF_LINEAR_FIXED) ? 0x01 : 0x03;
        out[p++] = f->rec_len;
    }
    return p;
}

static void respond_sw(CalypsoSim *s, uint8_t sw1, uint8_t sw2)
{
    rx_push(s, sw1);
    rx_push(s, sw2);
}

static void respond_with_data_pending(CalypsoSim *s,
                                       const uint8_t *data, int len)
{
    if (len > (int)sizeof(s->resp_buf)) len = sizeof(s->resp_buf);
    memcpy(s->resp_buf, data, len);
    s->resp_len = len;

    respond_sw(s, 0x9F, (uint8_t)len);
}

static void cmd_select(CalypsoSim *s, uint8_t p1, uint8_t p2,
                       uint8_t lc, const uint8_t *data)
{
    if (lc != 2) { respond_sw(s, 0x67, 0x00); return; }
    uint16_t fid = (data[0] << 8) | data[1];

    SimFile *f = NULL;
    if (fid == 0x3F00) {
        f = find_file(0x3F00, 0);
        s->selected_df = 0x3F00;
    } else if ((fid & 0xFF00) == 0x7F00) {
        f = find_file(fid, 0);
        if (f) s->selected_df = fid;
    } else if ((fid & 0xFF00) == 0x2F00) {
        f = find_file(fid, 0x3F00);
    } else {
        f = find_file(fid, s->selected_df);
        if (!f) f = find_file(fid, 0);
    }

    if (!f) {
        SIM_LOG("SELECT 0x%04x → file not found", fid);
        respond_sw(s, 0x6A, 0x82);
        return;
    }
    s->selected_ef = f->fid;

    uint8_t resp[32];
    int n = build_select_response(s, f, resp);
    SIM_LOG("SELECT 0x%04x (%s) → %d bytes pending",
            fid,
            f->structure == EF_MF ? "MF" :
            f->structure == EF_DF ? "DF" : "EF",
            n);
    respond_with_data_pending(s, resp, n);
}

static void cmd_get_response(CalypsoSim *s, uint8_t le)
{
    if (s->resp_len == 0) {
        respond_sw(s, 0x6F, 0x00);
        return;
    }
    int n = (le == 0) ? s->resp_len : (le > s->resp_len ? s->resp_len : le);
    rx_push_n(s, s->resp_buf, n);
    respond_sw(s, 0x90, 0x00);
    s->resp_len = 0;
}

static void cmd_read_binary(CalypsoSim *s, uint8_t p1, uint8_t p2, uint8_t le)
{
    SimFile *f = find_file(s->selected_ef, 0);
    if (!f || f->structure != EF_TRANSPARENT) {
        respond_sw(s, 0x94, 0x00);
        return;
    }
    int offset = (p1 << 8) | p2;
    int n = (le == 0) ? 256 : le;
    if (offset + n > f->size) {
        respond_sw(s, 0x67, 0x00);
        return;
    }
    rx_push_n(s, f->data + offset, n);
    SIM_LOG("READ_BINARY EF=0x%04x off=%d len=%d", s->selected_ef, offset, n);
    respond_sw(s, 0x90, 0x00);
}

static void cmd_status(CalypsoSim *s, uint8_t le)
{
    SimFile *df = find_file(s->selected_df, 0);
    if (!df) { respond_sw(s, 0x6F, 0x00); return; }
    uint8_t resp[32];
    int n = build_select_response(s, df, resp);
    int outn = (le == 0 || le > n) ? n : le;
    rx_push_n(s, resp, outn);
    respond_sw(s, 0x90, 0x00);
}

static void cmd_verify_chv(CalypsoSim *s, uint8_t p2, uint8_t lc,
                           const uint8_t *data)
{

    (void)p2; (void)lc; (void)data;
    SIM_LOG("VERIFY_CHV → always OK");
    respond_sw(s, 0x90, 0x00);
}

static void cmd_run_gsm_algo(CalypsoSim *s, uint8_t lc, const uint8_t *data)
{

    if (lc != 16) { respond_sw(s, 0x67, 0x00); return; }

    uint8_t resp[12];
    for (int i = 0; i < 4; i++) resp[i]    = data[i] ^ 0xAA;
    for (int i = 0; i < 8; i++) resp[4+i] = 0x00;
    SIM_LOG("RUN_GSM_ALGORITHM (RAND[0]=0x%02x) → SRES[0]=0x%02x",
            data[0], resp[0]);
    respond_with_data_pending(s, resp, 12);
}

static void dispatch_apdu(CalypsoSim *s)
{
    uint8_t cla = s->apdu[0];
    uint8_t ins = s->apdu[1];
    uint8_t p1  = s->apdu[2];
    uint8_t p2  = s->apdu[3];
    uint8_t p3  = s->apdu[4];
    uint8_t *data = (s->apdu_pos > 5) ? s->apdu + 5 : NULL;
    int     dlen = s->apdu_pos - 5;

    SIM_LOG("APDU CLA=%02x INS=%02x P1=%02x P2=%02x P3=%02x dlen=%d",
            cla, ins, p1, p2, p3, dlen > 0 ? dlen : 0);

    switch (ins) {
    case 0xA4: cmd_select(s, p1, p2, p3, data ? data : (const uint8_t *)""); break;
    case 0xB0: cmd_read_binary(s, p1, p2, p3); break;
    case 0xC0: cmd_get_response(s, p3); break;
    case 0xF2: cmd_status(s, p3); break;
    case 0x20: cmd_verify_chv(s, p2, p3, data ? data : (const uint8_t *)""); break;
    case 0x88: cmd_run_gsm_algo(s, p3, data ? data : (const uint8_t *)""); break;
    default:
        SIM_LOG("INS=0x%02x not supported → SW=6D00", ins);
        respond_sw(s, 0x6D, 0x00);
        break;
    }

    raise_rx_irq(s);
}

G_GNUC_UNUSED
static void apdu_tx_byte(CalypsoSim *s, uint8_t b)
{
    if (s->apdu_pos < APDU_MAX_LEN) {
        s->apdu[s->apdu_pos++] = b;
    }
    if (s->apdu_pos == 5) {

        uint8_t ins = s->apdu[1];
        bool is_outgoing_data =
            (ins == 0xA4) || (ins == 0xD6) || (ins == 0xDC) ||
            (ins == 0x20) || (ins == 0x24) || (ins == 0x88);
        if (is_outgoing_data && s->apdu[4] != 0) {
            s->apdu_expected = 5 + s->apdu[4];
        } else {
            s->apdu_expected = 5;
        }

        if (is_outgoing_data && s->apdu[4] != 0) {
            rx_push(s, ins);
            raise_rx_irq(s);
        }
    }
    if (s->apdu_pos == s->apdu_expected) {
        dispatch_apdu(s);
        s->apdu_pos = 0;
        s->apdu_expected = 0;
    }
}

uint16_t calypso_sim_reg_read(CalypsoSim *s, hwaddr off)
{
    switch (off) {
    case CALYPSO_SIM_REG_CMD:
        return s->cmd;
    case CALYPSO_SIM_REG_STAT: {
        uint16_t v = 0;
        if (s->powered) v |= CALYPSO_SIM_STAT_NOCARD;
        v |= CALYPSO_SIM_STAT_TXPAR;
        if (rx_count(s) == 0) v |= CALYPSO_SIM_STAT_FIFOEMPTY;
        if (rx_count(s) >= RX_FIFO_SIZE - 1) v |= CALYPSO_SIM_STAT_FIFOFULL;
        return v;
    }
    case CALYPSO_SIM_REG_CONF1:  return s->conf1;
    case CALYPSO_SIM_REG_CONF2:  return s->conf2;
    case CALYPSO_SIM_REG_IT: {
        refresh_it_rx(s);
        uint16_t v = s->it;

        uint16_t edge_seen = v & (CALYPSO_SIM_IT_NATR |
                                  CALYPSO_SIM_IT_WT   |
                                  CALYPSO_SIM_IT_OV);

        s->it &= ~edge_seen;
        update_irq(s);

        static unsigned itrd;
        if (itrd++ < 40) {
            uintptr_t io_pc = current_cpu ? current_cpu->mem_io_pc : 0;
            fprintf(stderr,
                    "[sim] SIM_IT read=0x%04x rx_count=%d edge_cleared=0x%04x "
                    "post_it=0x%04x mem_io_pc=0x%lx\n",
                    v, rx_count(s), edge_seen, s->it,
                    (unsigned long)io_pc);
        }
        return v;
    }
    case CALYPSO_SIM_REG_DRX: {
        uint8_t b = 0;
        rx_pop(s, &b);
        update_irq(s);

        return b | (1 << 8);
    }
    case CALYPSO_SIM_REG_DTX:    return 0;
    case CALYPSO_SIM_REG_MASKIT: return s->maskit;
    case CALYPSO_SIM_REG_IT_CD:  return s->it_cd;
    default: return 0;
    }
}

void calypso_sim_reg_write(CalypsoSim *s, hwaddr off, uint16_t val)
{
    switch (off) {
    case CALYPSO_SIM_REG_CMD:
        s->cmd = val;
        if (val & CALYPSO_SIM_CMD_START) {
            s->powered = true;
            SIM_LOG("CMDSTART → ATR delivered (synchronous)");

            deliver_atr(s);
        }
        if (val & CALYPSO_SIM_CMD_STOP) {
            s->powered = false;
            SIM_LOG("CMDSTOP");
        }
        if (val & (CALYPSO_SIM_CMD_CARDRST | CALYPSO_SIM_CMD_IFRST)) {
            SIM_LOG("RESET → ATR delivered (synchronous)");
            s->apdu_pos = 0;
            s->apdu_expected = 0;
            s->resp_len = 0;
            s->rx_head = s->rx_tail = 0;
            s->selected_df = 0x3F00;
            s->selected_ef = 0x3F00;

            if (s->powered) deliver_atr(s);
        }
        break;
    case CALYPSO_SIM_REG_STAT:    s->stat   = val; break;
    case CALYPSO_SIM_REG_CONF1:   s->conf1  = val; break;
    case CALYPSO_SIM_REG_CONF2:   s->conf2  = val; break;
    case CALYPSO_SIM_REG_IT:           break;
    case CALYPSO_SIM_REG_DRX:            break;
    case CALYPSO_SIM_REG_DTX:
        apdu_tx_byte(s, (uint8_t)(val & 0xFF));

        s->it &= ~CALYPSO_SIM_IT_TX;
        update_irq(s);
        break;
    case CALYPSO_SIM_REG_MASKIT: {

        s->maskit = val;
        update_irq(s);
        bool wt_unmasked = (val & CALYPSO_SIM_IT_WT) == 0;
        bool wt_not_pending = (s->it & CALYPSO_SIM_IT_WT) == 0;
        if (wt_unmasked && wt_not_pending && rx_count(s) == 0) {
            schedule_wt(s);
        }
        break;
    }
    case CALYPSO_SIM_REG_IT_CD:   s->it_cd  = val; break;
    default: break;
    }
}

static void load_config_from_file(CalypsoSim *s, const char *path)
{
    FILE *fp = fopen(path, "r");
    if (!fp) {
        SIM_LOG("config %s not found — keeping default file system", path);
        return;
    }
    char line[256];
    bool in_test_sim = false;
    while (fgets(line, sizeof(line), fp)) {
        char *p = line;
        while (*p == ' ' || *p == '\t') p++;
        if (strncmp(p, "test-sim", 8) == 0) {
            in_test_sim = true;
            continue;
        }
        if (!in_test_sim) continue;

        if (line[0] != ' ' && line[0] != '\t' && line[0] != '\n') {
            in_test_sim = false;
            continue;
        }
        if (strncmp(p, "imsi ", 5) == 0) {
            const char *imsi = p + 5;
            uint8_t bcd[9];
            if (encode_imsi_bcd(imsi, bcd) > 0) {
                SimFile *ef = find_file(0x6F07, 0x7F20);
                if (ef) {
                    memcpy(ef->data, bcd, 9);
                    ef->size = 9;
                    SIM_LOG("EF_IMSI loaded from %s: %.15s", path, imsi);
                }
            }
        } else if (strncmp(p, "ki comp128 ", 11) == 0) {
            const char *hex = p + 11;
            int n = 0;
            while (n < 16 && *hex) {
                while (*hex == ' ') hex++;
                if (!*hex) break;
                unsigned v;
                if (sscanf(hex, "%2x", &v) != 1) break;
                s->ki[n++] = (uint8_t)v;
                hex += 2;
            }
            if (n == 16) {
                s->ki_valid = true;
                SIM_LOG("Ki loaded from %s (16 bytes)", path);
            }
        }
    }
    fclose(fp);
}

CalypsoSim *calypso_sim_new(qemu_irq sim_irq)
{
    CalypsoSim *s = g_new0(CalypsoSim, 1);
    s->irq = sim_irq;
    s->atr_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, deliver_atr, s);
    s->wt_timer  = timer_new_ns(QEMU_CLOCK_VIRTUAL, fire_wt,     s);

    s->selected_df = 0x3F00;
    s->selected_ef = 0x3F00;

    const char *cfg_path = getenv("CALYPSO_SIM_CFG");
    if (cfg_path && *cfg_path) {
        load_config_from_file(s, cfg_path);
    } else {
        SIM_LOG("CALYPSO_SIM_CFG unset — keeping default IMSI/Ki");
    }
    return s;
}

hw/arm/calypso/calypso_soc.c

.c 239 lignes - 8,2KB

hw/arm/calypso/calypso_soc.c
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "qemu/osdep.h"
#include "qapi/error.h"
#include "hw/sysbus.h"
#include "hw/irq.h"
#include "hw/qdev-properties.h"
#include "hw/qdev-properties-system.h"
#include "hw/char/serial.h"
#include "chardev/char.h"
#include "exec/address-spaces.h"
#include "sysemu/sysemu.h"
#include "hw/arm/calypso/calypso_soc.h"
#include "hw/arm/calypso/calypso_trx.h"
#include "hw/arm/calypso/calypso_uart.h"

CalypsoUARTState *g_uart_modem;
CalypsoUARTState *g_uart_irda;

#define CALYPSO_IRAM_BASE     0x00800000
#define CALYPSO_IRAM_SIZE     (256 * 1024)
#define CALYPSO_INTH_BASE     0xFFFFFA00
#define CALYPSO_TIMER1_BASE   0xFFFE3800
#define CALYPSO_TIMER2_BASE   0xFFFE3C00
#define CALYPSO_SPI_BASE      0xFFFE3000
#define CALYPSO_I2C_BASE      0xFFFE1800
#define CALYPSO_KEYPAD_BASE   0xFFFE4800
#define CALYPSO_UART_IRDA     0xFFFF5000
#define CALYPSO_UART_MODEM    0xFFFF5800
#define CALYPSO_CNTL_BASE     0xFFFFFD00
#define CALYPSO_RHEA_DMA_BASE 0xFFFFFC00

static uint64_t stub_read(void *o, hwaddr a, unsigned s)
{
    return 0;
}

static void stub_write(void *o, hwaddr a, uint64_t v, unsigned s)
{
}

static const MemoryRegionOps stub8_ops = {
    .read = stub_read,
    .write = stub_write,
    .endianness = DEVICE_NATIVE_ENDIAN,
    .impl = { .min_access_size = 1, .max_access_size = 1 },
};

static const MemoryRegionOps stub16_ops = {
    .read = stub_read,
    .write = stub_write,
    .endianness = DEVICE_NATIVE_ENDIAN,
    .impl = { .min_access_size = 2, .max_access_size = 2 },
};

static uint64_t keypad_read(void *o, hwaddr a, unsigned s)
{
    return 0xFF;
}

static const MemoryRegionOps keypad_ops = {
    .read = keypad_read,
    .write = stub_write,
    .endianness = DEVICE_NATIVE_ENDIAN,
};

static uint64_t cntl_read(void *opaque, hwaddr offset, unsigned size)
{
    CalypsoSoCState *s = CALYPSO_SOC(opaque);
    return offset == 0 ? s->extra_conf : 0;
}

static void cntl_write(void *opaque, hwaddr offset, uint64_t value, unsigned size)
{
    CalypsoSoCState *s = CALYPSO_SOC(opaque);
    if (offset != 0) {
        return;
    }
    s->extra_conf = (uint16_t)value;
    bool bootrom_enabled = (value >> 8) & 3;
    MemoryRegion *sysmem = get_system_memory();
    if (!bootrom_enabled && !s->iram_at_zero) {
        memory_region_init_alias(&s->iram_alias, OBJECT(s), "calypso.iram_at_zero",
                                 &s->iram, 0, CALYPSO_IRAM_SIZE);
        memory_region_add_subregion_overlap(sysmem, 0x00000000, &s->iram_alias, 1);
        s->iram_at_zero = true;
    } else if (bootrom_enabled && s->iram_at_zero) {
        memory_region_del_subregion(sysmem, &s->iram_alias);
        object_unparent(OBJECT(&s->iram_alias));
        s->iram_at_zero = false;
    }
}

static const MemoryRegionOps cntl_ops = {
    .read = cntl_read,
    .write = cntl_write,
    .endianness = DEVICE_NATIVE_ENDIAN,
    .impl = { .min_access_size = 2, .max_access_size = 2 },
};

static void add_stub(MemoryRegion *sys, const char *name, hwaddr base,
                     const MemoryRegionOps *ops, uint64_t size)
{
    MemoryRegion *mr = g_new(MemoryRegion, 1);
    memory_region_init_io(mr, NULL, ops, NULL, name, size);
    memory_region_add_subregion(sys, base, mr);
}

static void init_uart(CalypsoSoCState *s, CalypsoUARTState *uart, const char *label,
                      int serial_index, hwaddr base, qemu_irq irq, Error **errp)
{
    Chardev *chr = qemu_chr_find(label);
    if (!chr) {
        chr = serial_hd(serial_index);
    }
    object_initialize_child(OBJECT(s), label, uart, TYPE_CALYPSO_UART);
    qdev_prop_set_string(DEVICE(uart), "label", label);
    if (chr) {
        qdev_prop_set_chr(DEVICE(uart), "chardev", chr);
    }
    if (!sysbus_realize(SYS_BUS_DEVICE(uart), errp)) {
        return;
    }
    sysbus_mmio_map(SYS_BUS_DEVICE(uart), 0, base);
    sysbus_connect_irq(SYS_BUS_DEVICE(uart), 0, irq);
}

static void calypso_soc_realize(DeviceState *dev, Error **errp)
{
    CalypsoSoCState *s = CALYPSO_SOC(dev);
    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
    MemoryRegion *sysmem = get_system_memory();
    Error *err = NULL;

    memory_region_init_ram(&s->iram, OBJECT(dev), "calypso.iram", CALYPSO_IRAM_SIZE,
                           &error_fatal);
    memory_region_add_subregion(sysmem, CALYPSO_IRAM_BASE, &s->iram);

    object_initialize_child(OBJECT(dev), "inth", &s->inth, TYPE_CALYPSO_INTH);
    if (!sysbus_realize(SYS_BUS_DEVICE(&s->inth), &err)) {
        error_propagate(errp, err);
        return;
    }
    sysbus_mmio_map(SYS_BUS_DEVICE(&s->inth), 0, CALYPSO_INTH_BASE);
    sysbus_pass_irq(sbd, SYS_BUS_DEVICE(&s->inth));
#define INTH_IRQ(n) qdev_get_gpio_in(DEVICE(&s->inth), (n))

    object_initialize_child(OBJECT(dev), "timer1", &s->timer1, TYPE_CALYPSO_TIMER);
    if (!sysbus_realize(SYS_BUS_DEVICE(&s->timer1), &err)) {
        error_propagate(errp, err);
        return;
    }
    sysbus_mmio_map(SYS_BUS_DEVICE(&s->timer1), 0, CALYPSO_TIMER1_BASE);
    sysbus_connect_irq(SYS_BUS_DEVICE(&s->timer1), 0, INTH_IRQ(CALYPSO_IRQ_TIMER1));
    calypso_timer_register_lost(DEVICE(&s->timer1));

    object_initialize_child(OBJECT(dev), "timer2", &s->timer2, TYPE_CALYPSO_TIMER);
    if (!sysbus_realize(SYS_BUS_DEVICE(&s->timer2), &err)) {
        error_propagate(errp, err);
        return;
    }
    sysbus_mmio_map(SYS_BUS_DEVICE(&s->timer2), 0, CALYPSO_TIMER2_BASE);
    sysbus_connect_irq(SYS_BUS_DEVICE(&s->timer2), 0, INTH_IRQ(CALYPSO_IRQ_TIMER2));

    DeviceState *i2c = qdev_new("calypso-i2c");
    sysbus_realize_and_unref(SYS_BUS_DEVICE(i2c), &error_fatal);
    sysbus_mmio_map(SYS_BUS_DEVICE(i2c), 0, CALYPSO_I2C_BASE);

    object_initialize_child(OBJECT(dev), "spi", &s->spi, TYPE_CALYPSO_SPI);
    if (!sysbus_realize(SYS_BUS_DEVICE(&s->spi), &err)) {
        error_propagate(errp, err);
        return;
    }
    sysbus_mmio_map(SYS_BUS_DEVICE(&s->spi), 0, CALYPSO_SPI_BASE);
    sysbus_connect_irq(SYS_BUS_DEVICE(&s->spi), 0, INTH_IRQ(CALYPSO_IRQ_SPI));

    init_uart(s, &s->uart_modem, "modem", 0, CALYPSO_UART_MODEM,
              INTH_IRQ(CALYPSO_IRQ_UART_MODEM), &err);
    if (err) {
        error_propagate(errp, err);
        return;
    }
    g_uart_modem = &s->uart_modem;

    init_uart(s, &s->uart_irda, "irda", 1, CALYPSO_UART_IRDA,
              INTH_IRQ(CALYPSO_IRQ_UART_IRDA), &err);
    if (err) {
        error_propagate(errp, err);
        return;
    }
    g_uart_irda = &s->uart_irda;

    qemu_irq *irqs = g_new0(qemu_irq, CALYPSO_NUM_IRQS);
    for (int i = 0; i < CALYPSO_NUM_IRQS; i++) {
        irqs[i] = INTH_IRQ(i);
    }
    calypso_trx_init(sysmem, irqs);
#undef INTH_IRQ

    add_stub(sysmem, "calypso.keypad", CALYPSO_KEYPAD_BASE, &keypad_ops, 0x100);
    add_stub(sysmem, "calypso.tmr6800", 0xFFFE6800, &stub8_ops, 0x100);
    add_stub(sysmem, "calypso.mmio_80xx", 0xFFFE8000, &stub8_ops, 0x100);
    add_stub(sysmem, "calypso.conf", 0xFFFEF000, &stub16_ops, 0x100);
    add_stub(sysmem, "calypso.dpll", 0xFFFF9800, &stub16_ops, 0x100);
    add_stub(sysmem, "calypso.mmio_f0xx", 0xFFFFF000, &stub16_ops, 0x100);
    add_stub(sysmem, "calypso.rhea", 0xFFFFF900, &stub16_ops, 0x100);
    add_stub(sysmem, "calypso.clkm", 0xFFFFFB00, &stub16_ops, 0x100);
    add_stub(sysmem, "calypso.rhea_dma", CALYPSO_RHEA_DMA_BASE, &stub16_ops, 0x100);
    add_stub(sysmem, "calypso.dio", 0xFFFFFF00, &stub8_ops, 0x100);

    memory_region_init_io(&s->cntl_iomem, OBJECT(dev), &cntl_ops, s, "calypso.cntl", 0x100);
    memory_region_add_subregion(sysmem, CALYPSO_CNTL_BASE, &s->cntl_iomem);
    s->extra_conf = 0x0300;
    s->iram_at_zero = false;

    MemoryRegion *catchall = g_new(MemoryRegion, 1);
    memory_region_init_io(catchall, NULL, &stub8_ops, NULL, "calypso.catchall", 0x100000);
    memory_region_add_subregion_overlap(sysmem, 0xFFF00000, catchall, -1);
}

static void calypso_soc_class_init(ObjectClass *oc, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(oc);
    dc->realize = calypso_soc_realize;
    dc->user_creatable = false;
}

static const TypeInfo calypso_soc_type_info = {
    .name = TYPE_CALYPSO_SOC,
    .parent = TYPE_SYS_BUS_DEVICE,
    .instance_size = sizeof(CalypsoSoCState),
    .class_init = calypso_soc_class_init,
};

static void calypso_soc_register_types(void)
{
    type_register_static(&calypso_soc_type_info);
}

type_init(calypso_soc_register_types)

hw/arm/calypso/calypso_tpu.c

.c 106 lignes - 2,8KB

hw/arm/calypso/calypso_tpu.c
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "qemu/osdep.h"
#include "hw/arm/calypso/calypso_trx.h"
#include "hw/arm/calypso/calypso_tsp.h"

#define TPU_OP_SLEEP     0
#define TPU_OP_AT        1
#define TPU_OP_OFFSET    2
#define TPU_OP_SYNCHRO   3
#define TPU_OP_MOVE      4
#define TPU_OP_WAIT      5

#define QBITS_PER_TDMA   5000

static struct {
    uint16_t insns[CALYPSO_TPU_RAM_SIZE / 2];
    int len;
    int cursor;
    int qbit;
    int wait_frames;
    bool active;
    uint16_t *tpu_regs;
} seq;

static void seq_run(uint32_t fn)
{
    while (seq.active && seq.cursor < seq.len) {
        uint16_t insn = seq.insns[seq.cursor];
        if (insn == 0x0000) {
            int next = seq.cursor + 1;
            bool had_any = seq.cursor > 0;
            if (had_any && (next >= seq.len || seq.insns[next] == 0x0000)) {
                seq.active = false;
                return;
            }
            seq.cursor++;
            continue;
        }
        uint8_t opcode = (insn >> 13) & 0x7;
        uint16_t payload = insn & 0x1FFF;

        if (opcode == TPU_OP_AT) {
            seq.cursor++;
            if (payload > seq.qbit) {
                seq.qbit = payload;
                continue;
            }
            seq.qbit = payload;
            seq.wait_frames = 1;
            return;
        }
        if (opcode == TPU_OP_WAIT) {
            seq.cursor++;
            int target = seq.qbit + (int)payload;
            if (target < QBITS_PER_TDMA) {
                seq.qbit = target;
                continue;
            }
            seq.wait_frames = target / QBITS_PER_TDMA;
            seq.qbit = target % QBITS_PER_TDMA;
            return;
        }
        if (opcode == TPU_OP_SYNCHRO || opcode == TPU_OP_OFFSET) {
            if (seq.tpu_regs) {
                seq.tpu_regs[(opcode == TPU_OP_SYNCHRO ? TPU_SYNCHRO : TPU_OFFSET) / 2] = payload;
            }
            seq.cursor++;
            continue;
        }
        if (opcode == TPU_OP_MOVE) {
            uint8_t addr = insn & 0x1F;
            uint8_t data = (insn >> 5) & 0xFF;
            if (calypso_tsp_owns_addr(addr)) {
                calypso_tsp_move(addr, data, fn);
            }
            seq.cursor++;
            continue;
        }
        seq.cursor++;
    }
    seq.active = false;
}

void calypso_tpu_run_scenario(uint16_t *tpu_ram, uint32_t fn, uint16_t *tpu_regs)
{
    memcpy(seq.insns, tpu_ram, sizeof(seq.insns));
    seq.len = CALYPSO_TPU_RAM_SIZE / 2;
    seq.cursor = 0;
    seq.qbit = 0;
    seq.wait_frames = 0;
    seq.tpu_regs = tpu_regs;
    seq.active = true;
    seq_run(fn);
}

void calypso_tpu_sequencer_tick(uint32_t fn)
{
    if (!seq.active || seq.wait_frames <= 0) {
        return;
    }
    seq.wait_frames--;
    if (seq.wait_frames > 0) {
        return;
    }
    seq_run(fn);
}

hw/arm/calypso/calypso_trf6151.c

.c 108 lignes - 2,6KB

hw/arm/calypso/calypso_trf6151.c
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "qemu/osdep.h"
#include "hw/arm/calypso/calypso_trf6151.h"

#define SYSTEM_INHERENT_GAIN    71
#define TRF6151_FE_GAIN_LOW      7
#define TRF6151_FE_GAIN_HIGH    27
#define TRF6151_VGA_GAIN_MIN    14
#define RX_VGA_GAIN_SHIFT       11

#define TRF6151_REG_RX           0
#define TRF6151_REG_PLL          1
#define TRF6151_REG_PWR          2
#define TRF6151_REG_ADDR_MASK    0x7
#define TRF6151_BAND_1800        2

#define TRF6151_REG_RX_RESET    0x9E00
#define TRF6151_TSP_DEV          1

static uint16_t g_reg_rx = TRF6151_REG_RX_RESET;
static uint16_t g_reg_pll;
static uint16_t g_reg_pwr;

static int trf6151_gain_from_reg(uint16_t reg_rx)
{
    int gain = 0;
    unsigned vga;

    switch ((reg_rx >> 9) & 3) {
    case 0: gain += TRF6151_FE_GAIN_LOW;  break;
    case 3: gain += TRF6151_FE_GAIN_HIGH; break;
    default:   break;
    }

    vga = (reg_rx >> RX_VGA_GAIN_SHIFT) & 0x1f;
    if (vga < 6) {
        vga = 6;
    }
    gain += TRF6151_VGA_GAIN_MIN + (int)(vga - 6) * 2;

    return gain;
}

void calypso_trf6151_tsp_write(uint8_t dev_idx, uint32_t word)
{
    if (dev_idx != TRF6151_TSP_DEV) {
        return;
    }
    switch (word & TRF6151_REG_ADDR_MASK) {
    case TRF6151_REG_RX:
        g_reg_rx = (uint16_t)word;
        break;
    case TRF6151_REG_PLL:
        g_reg_pll = (uint16_t)word;
        break;
    case TRF6151_REG_PWR:
        g_reg_pwr = (uint16_t)word;
        break;
    default:
        break;
    }
}

int calypso_trf6151_arfcn(void)
{
    unsigned a = (g_reg_pll >> 3) & 0x3f;
    unsigned b = ((g_reg_pll >> 9) & 0x7f) + 64;
    unsigned n = b * 64 + a;
    bool high_band = ((g_reg_pwr >> 6) & 3) == TRF6151_BAND_1800;
    unsigned freq10 = high_band ? n * 2 : n;
    if (!g_reg_pll) {
        return -1;
    }
    if (freq10 >= 9350 && freq10 <= 9598) {
        return (int)((freq10 - 9350) / 2);
    }
    if (freq10 >= 9252 && freq10 < 9350) {
        return 1024 - (int)(9350 - freq10) / 2;
    }
    if (freq10 >= 18052 && freq10 <= 18798) {
        return (int)(512 + (freq10 - 18052) / 2);
    }
    if (freq10 >= 19302 && freq10 <= 19898) {
        return (int)(512 + (freq10 - 19302) / 2);
    }
    return -1;
}

int calypso_trf6151_total_gain_db(void)
{
    return SYSTEM_INHERENT_GAIN + trf6151_gain_from_reg(g_reg_rx);
}

uint16_t calypso_trf6151_apm_for_rf(int target_rf_dbm)
{
    int total_gain = calypso_trf6151_total_gain_db();
    int bb_dbm = target_rf_dbm + total_gain;
    int apm;

    if (bb_dbm < 0) {
        bb_dbm = 0;
    }
    apm = bb_dbm * 64;
    if (apm > 0xFFFF) {
        apm = 0xFFFF;
    }
    return (uint16_t)apm;
}

hw/arm/calypso/calypso_trx.c

.c 478 lignes - 14KB

hw/arm/calypso/calypso_trx.c
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "qemu/osdep.h"
#include "qemu/timer.h"
#include "qemu/main-loop.h"
#include "sysemu/runstate.h"
#include "exec/address-spaces.h"
#include "exec/cpu-common.h"
#include "hw/core/cpu.h"
#include "hw/irq.h"
#include "hw/arm/calypso/calypso_api.h"
#include "hw/arm/calypso/calypso_l1.h"
#include "hw/arm/calypso/calypso_trx.h"
#include "hw/arm/calypso/calypso_uart.h"
#include "hw/arm/calypso/calypso_timer.h"
#include "hw/arm/calypso/calypso_sim.h"
#include <pthread.h>
#include <time.h>

extern CalypsoUARTState *g_uart_modem;
extern CalypsoUARTState *g_uart_irda;

#define FRAME_IRQ_PULSE_NS     1000000
#define CPU_KICK_NS            5000000
#define IDLE_PC_LO             0x00823000u
#define IDLE_PC_HI             0x00826000u
#define INTH_MASK_ADDR         0xFFFFFA08u
#define BOOT_POLLS_BEFORE_BOOT 3
#define FN_SYNC_TOLERANCE      2

typedef struct CalypsoTRX {
    qemu_irq *irqs;
    MemoryRegion api_iomem;
    uint16_t api_ram[CALYPSO_API_WORDS];
    uint8_t dsp_page;
    bool bl_booted;
    unsigned bl_polls;
    MemoryRegion tpu_iomem;
    MemoryRegion tpu_ram_iomem;
    uint16_t tpu_regs[CALYPSO_TPU_SIZE / 2];
    uint16_t tpu_ram[CALYPSO_TPU_RAM_SIZE / 2];
    MemoryRegion tsp_iomem;
    uint16_t tsp_regs[CALYPSO_TSP_SIZE / 2];
    MemoryRegion ulpd_iomem;
    uint16_t ulpd_regs[CALYPSO_ULPD_SIZE / 2];
    uint32_t ulpd_counter;
    MemoryRegion sim_iomem;
    CalypsoSim *sim;
    QEMUTimer *tdma_timer;
    QEMUTimer *frame_irq_timer;
    QEMUTimer *kick_timer;
    uint32_t fn;
    int64_t fn_offset;
    bool fn_synced;
    bool tdma_running;
    uint8_t burst_ring[8];
    unsigned burst_w, burst_r;
    uint16_t burst_cur;
    uint32_t burst_last_fn;
} CalypsoTRX;

static CalypsoTRX *g_trx;
static volatile uint32_t g_wall_fn;
static volatile bool g_wall_running;
static pthread_t g_wall_thread;

uint16_t *calypso_api_ram(void)
{
    return g_trx->api_ram;
}

uint32_t calypso_trx_get_fn(void)
{
    return g_trx ? (uint32_t)((int64_t)g_trx->fn + g_trx->fn_offset) : 0;
}

void calypso_trx_autosync_fn(uint32_t sch_fn)
{
    if (!g_trx) {
        return;
    }
    int64_t offset = (int64_t)sch_fn - (int64_t)g_trx->fn;
    int64_t drift = offset - g_trx->fn_offset;
    if (drift < 0) {
        drift = -drift;
    }
    if (g_trx->fn_synced && drift <= FN_SYNC_TOLERANCE) {
        return;
    }
    g_trx->fn_offset = offset;
    g_trx->fn_synced = true;
    fprintf(stderr, "[trx] horloge FN calee sur le SCH gr-gsm : fn=%u offset=%lld\n",
            sch_fn, (long long)offset);
}

static uint64_t api_read(void *opaque, hwaddr off, unsigned size)
{
    CalypsoTRX *s = opaque;
    if (off >= CALYPSO_API_SIZE) {
        return 0;
    }
    const uint16_t *src = &s->api_ram[off / 2];
    uint64_t val = (size == 2) ? src[0] :
                   (size == 4) ? ((uint32_t)src[0] | ((uint32_t)src[1] << 16)) :
                   ((const uint8_t *)src)[off & 1];
    if (size != 2) {
        return val;
    }

    uint16_t rv;
    if (calypso_l1_read_override((uint32_t)off, &rv)) {
        val = rv;
    }
    if (off == API_R_PAGE(0) + RP_D_TASK_D || off == API_R_PAGE(1) + RP_D_TASK_D) {
        s->burst_cur = s->burst_ring[s->burst_r++ & 7u];
        if (val == 0 && calypso_l1_si_valid()) {
            val = ALLC_DSP_TASK;
        }
    }
    if (off == API_R_PAGE(0) + RP_D_BURST_D || off == API_R_PAGE(1) + RP_D_BURST_D) {
        val = (uint16_t)((s->burst_cur + 3) & 3);
    }
    if (off == API_BL_STATUS && !s->bl_booted && ++s->bl_polls > BOOT_POLLS_BEFORE_BOOT) {
        s->api_ram[API_BL_STATUS / 2] = BL_STATUS_BOOT;
        s->api_ram[API_VERSION / 2] = API_VERSION_VALUE;
        s->api_ram[API_VERSION2 / 2] = 0;
        s->bl_booted = true;
        val = BL_STATUS_BOOT;
    }
    return val;
}

static void api_write(void *opaque, hwaddr off, uint64_t value, unsigned size)
{
    CalypsoTRX *s = opaque;
    if (off >= CALYPSO_API_SIZE) {
        return;
    }
    if (size == 2) {
        s->api_ram[off / 2] = (uint16_t)value;
    } else if (size == 4) {
        s->api_ram[off / 2] = (uint16_t)value;
        s->api_ram[off / 2 + 1] = (uint16_t)(value >> 16);
    } else {
        ((uint8_t *)s->api_ram)[off] = (uint8_t)value;
    }

    if (size == 2 && (off == API_W_PAGE(0) + WP_D_BURST_D ||
                      off == API_W_PAGE(1) + WP_D_BURST_D)) {
        uint32_t wfn = calypso_l1s_fn();
        if (wfn != s->burst_last_fn + 1) {
            s->burst_w = s->burst_r = 0;
        }
        s->burst_last_fn = wfn;
        s->burst_ring[s->burst_w++ & 7u] = (uint8_t)(value & 3);
        calypso_l1_burst_written((uint16_t)value);
    }
    if (off == API_NDB + NDB_D_RACH && value != 0 && (size == 2 || size == 4)) {
        calypso_l1_rach_written((uint16_t)value, s->fn);
    }
    if (off == API_NDB + NDB_D_DSP_PAGE && size == 2) {
        s->dsp_page = value & 1;
        calypso_l1_page_written((uint16_t)value);
    }
    if (off == API_BL_STATUS) {
        if (value == 0) {
            s->bl_booted = false;
            s->bl_polls = 0;
        } else if (value == BL_STATUS_READY) {
            s->api_ram[API_VERSION / 2] = API_VERSION_VALUE;
            s->api_ram[API_VERSION2 / 2] = 0;
            uint16_t mask;
            cpu_physical_memory_read(INTH_MASK_ADDR, &mask, 2);
            mask &= ~(1 << CALYPSO_IRQ_API);
            cpu_physical_memory_write(INTH_MASK_ADDR, &mask, 2);
            s->api_ram[API_NDB / 2] = 0;
        }
    }
}

static const MemoryRegionOps api_ops = {
    .read = api_read,
    .write = api_write,
    .endianness = DEVICE_LITTLE_ENDIAN,
    .valid = { .min_access_size = 1, .max_access_size = 4 },
    .impl = { .min_access_size = 1, .max_access_size = 4 },
};

static void tpu_done(CalypsoTRX *s)
{
    s->tpu_regs[TPU_CTRL / 2] &= ~TPU_CTRL_EN;
    calypso_tpu_run_scenario(s->tpu_ram, s->fn, s->tpu_regs);
    qemu_irq_raise(s->irqs[CALYPSO_IRQ_API]);
}

static void tdma_start(CalypsoTRX *s);

static uint64_t tpu_read(void *o, hwaddr off, unsigned sz)
{
    CalypsoTRX *s = o;
    if (off == TPU_IT_DSP_PG) {
        return s->dsp_page;
    }
    return (off / 2 < CALYPSO_TPU_SIZE / 2) ? s->tpu_regs[off / 2] : 0;
}

static void tpu_write(void *o, hwaddr off, uint64_t val, unsigned sz)
{
    CalypsoTRX *s = o;
    if (off / 2 < CALYPSO_TPU_SIZE / 2) {
        s->tpu_regs[off / 2] = val;
    }
    if (off == TPU_CTRL && (val & TPU_CTRL_EN)) {
        s->tpu_regs[TPU_CTRL / 2] &= ~(TPU_CTRL_EN | TPU_CTRL_IDLE);
        tpu_done(s);
    }
    if (off == TPU_INT_CTRL && !(val & ICTRL_MCU_FRAME) && !s->tdma_running) {
        tdma_start(s);
    }
    if (off == TPU_IT_DSP_PG) {
        s->dsp_page = val & 1;
    }
}

static const MemoryRegionOps tpu_ops = {
    .read = tpu_read,
    .write = tpu_write,
    .endianness = DEVICE_LITTLE_ENDIAN,
    .valid = { .min_access_size = 1, .max_access_size = 4 },
    .impl = { .min_access_size = 1, .max_access_size = 4 },
};

static uint64_t tpu_ram_read(void *o, hwaddr off, unsigned sz)
{
    CalypsoTRX *s = o;
    return (off / 2 < CALYPSO_TPU_RAM_SIZE / 2) ? s->tpu_ram[off / 2] : 0;
}

static void tpu_ram_write(void *o, hwaddr off, uint64_t v, unsigned sz)
{
    CalypsoTRX *s = o;
    if (off / 2 < CALYPSO_TPU_RAM_SIZE / 2) {
        s->tpu_ram[off / 2] = v;
    }
}

static const MemoryRegionOps tpu_ram_ops = {
    .read = tpu_ram_read,
    .write = tpu_ram_write,
    .endianness = DEVICE_LITTLE_ENDIAN,
    .valid = { .min_access_size = 1, .max_access_size = 4 },
    .impl = { .min_access_size = 1, .max_access_size = 4 },
};

static uint64_t tsp_read(void *o, hwaddr off, unsigned sz)
{
    CalypsoTRX *s = o;
    if (off == TSP_RX_REG) {
        return 0xFFFF;
    }
    return (off / 2 < CALYPSO_TSP_SIZE / 2) ? s->tsp_regs[off / 2] : 0;
}

static void tsp_write(void *o, hwaddr off, uint64_t v, unsigned sz)
{
    CalypsoTRX *s = o;
    if (off / 2 < CALYPSO_TSP_SIZE / 2) {
        s->tsp_regs[off / 2] = v;
    }
}

static const MemoryRegionOps tsp_ops = {
    .read = tsp_read,
    .write = tsp_write,
    .endianness = DEVICE_LITTLE_ENDIAN,
    .valid = { .min_access_size = 1, .max_access_size = 4 },
    .impl = { .min_access_size = 1, .max_access_size = 4 },
};

static uint64_t ulpd_read(void *o, hwaddr off, unsigned sz)
{
    CalypsoTRX *s = o;
    if (off >= 0x20 && off <= 0x40) {
        return 0;
    }
    switch (off) {
    case ULPD_SETUP_CLK13:
        return 0x2003;
    case ULPD_COUNTER_HI:
        s->ulpd_counter += 100;
        return (s->ulpd_counter >> 16) & 0xFFFF;
    case ULPD_COUNTER_LO:
        return s->ulpd_counter & 0xFFFF;
    case ULPD_GAUGING_CTRL:
        return 1;
    case ULPD_GSM_TIMER:
        return s->fn & 0xFFFF;
    default:
        return (off / 2 < CALYPSO_ULPD_SIZE / 2) ? s->ulpd_regs[off / 2] : 0;
    }
}

static void ulpd_write(void *o, hwaddr off, uint64_t v, unsigned sz)
{
    CalypsoTRX *s = o;
    if (off >= 0x20 && off <= 0x40) {
        return;
    }
    if (off / 2 < CALYPSO_ULPD_SIZE / 2) {
        s->ulpd_regs[off / 2] = v;
    }
}

static const MemoryRegionOps ulpd_ops = {
    .read = ulpd_read,
    .write = ulpd_write,
    .endianness = DEVICE_LITTLE_ENDIAN,
    .valid = { .min_access_size = 1, .max_access_size = 2 },
    .impl = { .min_access_size = 1, .max_access_size = 2 },
};

static uint64_t sim_read(void *o, hwaddr off, unsigned sz)
{
    CalypsoTRX *s = o;
    return calypso_sim_reg_read(s->sim, off);
}

static void sim_write(void *o, hwaddr off, uint64_t v, unsigned sz)
{
    CalypsoTRX *s = o;
    calypso_sim_reg_write(s->sim, off, (uint16_t)v);
}

static const MemoryRegionOps sim_ops = {
    .read = sim_read,
    .write = sim_write,
    .endianness = DEVICE_LITTLE_ENDIAN,
    .valid = { .min_access_size = 1, .max_access_size = 4 },
    .impl = { .min_access_size = 1, .max_access_size = 4 },
};

static void cpu_idle_park(void)
{
    CPUState *cs = first_cpu;
    if (!cs) {
        return;
    }
    uint64_t pc = (cs->cc && cs->cc->get_pc) ? cs->cc->get_pc(cs) : 0;
    if (pc < IDLE_PC_LO || pc >= IDLE_PC_HI) {
        return;
    }
    cs->halted = 1;
    cpu_exit(cs);
}

static void frame_irq_lower(void *o)
{
    CalypsoTRX *s = o;
    qemu_irq_lower(s->irqs[CALYPSO_IRQ_TPU_FRAME]);
    calypso_l1_frame_tick();
    cpu_idle_park();
}

static void *wall_clock_loop(void *arg)
{
    struct timespec next;
    clock_gettime(CLOCK_MONOTONIC, &next);
    while (g_wall_running) {
        next.tv_nsec += GSM_TDMA_NS;
        while (next.tv_nsec >= 1000000000L) {
            next.tv_nsec -= 1000000000L;
            next.tv_sec += 1;
        }
        int rc = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next, NULL);
        if (rc != 0 && rc != EINTR) {
            break;
        }
        __atomic_add_fetch(&g_wall_fn, 1, __ATOMIC_RELEASE);
    }
    return NULL;
}

static void tdma_tick(void *opaque)
{
    CalypsoTRX *s = opaque;
    if (!runstate_is_running()) {
        timer_mod_ns(s->tdma_timer, qemu_clock_get_ns(QEMU_CLOCK_REALTIME) + GSM_TDMA_NS);
        return;
    }
    uint32_t wfn = __atomic_load_n(&g_wall_fn, __ATOMIC_ACQUIRE);
    s->fn = (wfn ? wfn : s->fn + 1) % GSM_HYPERFRAME;

    calypso_tpu_sequencer_tick(s->fn);
    if (g_uart_modem) {
        calypso_uart_poll_backend(g_uart_modem);
        calypso_uart_kick_rx(g_uart_modem);
    }
    if (g_uart_irda) {
        calypso_uart_poll_backend(g_uart_irda);
        calypso_uart_kick_rx(g_uart_irda);
    }

    *api_wp(s->dsp_page, WP_D_TASK_RA) = 0;
    *api_wp(s->dsp_page, WP_D_TASK_U) = 0;

    calypso_timer_lost_frame_tick(s->fn);
    qemu_irq_raise(s->irqs[CALYPSO_IRQ_TPU_FRAME]);
    timer_mod_ns(s->frame_irq_timer,
                 qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + FRAME_IRQ_PULSE_NS);

    static int64_t target;
    int64_t now = qemu_clock_get_ns(QEMU_CLOCK_REALTIME);
    if (target == 0) {
        target = now;
    }
    target += GSM_TDMA_NS;
    while (target <= now) {
        target += GSM_TDMA_NS;
    }
    timer_mod_ns(s->tdma_timer, target);
}

static void tdma_start(CalypsoTRX *s)
{
    s->tdma_running = true;
    s->fn = 0;
    timer_mod_ns(s->tdma_timer, qemu_clock_get_ns(QEMU_CLOCK_REALTIME) + GSM_TDMA_NS);
}

static void cpu_kick(void *o)
{
    CalypsoTRX *s = o;
    if (first_cpu) {
        cpu_exit(first_cpu);
    }
    qemu_notify_event();
    timer_mod_ns(s->kick_timer, qemu_clock_get_ns(QEMU_CLOCK_REALTIME) + CPU_KICK_NS);
}

static void map_io(MemoryRegion *sysmem, MemoryRegion *mr, const MemoryRegionOps *ops,
                   void *opaque, const char *name, hwaddr base, uint64_t size)
{
    memory_region_init_io(mr, NULL, ops, opaque, name, size);
    memory_region_add_subregion(sysmem, base, mr);
}

void calypso_trx_init(MemoryRegion *sysmem, qemu_irq *irqs)
{
    CalypsoTRX *s = g_new0(CalypsoTRX, 1);
    g_trx = s;
    s->irqs = irqs;

    map_io(sysmem, &s->api_iomem, &api_ops, s, "calypso.dsp_api", CALYPSO_API_BASE,
           CALYPSO_API_SIZE);
    s->api_ram[API_BL_STATUS / 2] = BL_STATUS_READY;
    s->api_ram[API_VERSION / 2] = API_VERSION_VALUE;
    s->bl_booted = true;

    map_io(sysmem, &s->tpu_iomem, &tpu_ops, s, "calypso.tpu", CALYPSO_TPU_BASE,
           CALYPSO_TPU_SIZE);
    map_io(sysmem, &s->tpu_ram_iomem, &tpu_ram_ops, s, "calypso.tpu_ram",
           CALYPSO_TPU_RAM_BASE, CALYPSO_TPU_RAM_SIZE);
    map_io(sysmem, &s->tsp_iomem, &tsp_ops, s, "calypso.tsp", CALYPSO_TSP_BASE,
           CALYPSO_TSP_SIZE);
    map_io(sysmem, &s->ulpd_iomem, &ulpd_ops, s, "calypso.ulpd", CALYPSO_ULPD_BASE,
           CALYPSO_ULPD_SIZE);
    s->sim = calypso_sim_new(s->irqs[CALYPSO_IRQ_SIM]);
    map_io(sysmem, &s->sim_iomem, &sim_ops, s, "calypso.sim", CALYPSO_SIM_BASE,
           CALYPSO_SIM_SIZE);

    s->tdma_timer = timer_new_ns(QEMU_CLOCK_REALTIME, tdma_tick, s);
    s->frame_irq_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, frame_irq_lower, s);
    s->kick_timer = timer_new_ns(QEMU_CLOCK_REALTIME, cpu_kick, s);
    timer_mod_ns(s->kick_timer, qemu_clock_get_ns(QEMU_CLOCK_REALTIME) + CPU_KICK_NS);

    g_wall_running = true;
    pthread_create(&g_wall_thread, NULL, wall_clock_loop, NULL);
    pthread_setname_np(g_wall_thread, "cal-tdma-clock");
}

hw/arm/calypso/calypso_tsp.c

.c 55 lignes - 1,7KB

hw/arm/calypso/calypso_tsp.c
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "qemu/osdep.h"
#include "hw/arm/calypso/calypso_trf6151.h"
#include "hw/arm/calypso/calypso_tsp.h"

static struct {
    uint8_t tx[4];
    uint8_t ctrl1;
} tsp;

bool calypso_tsp_owns_addr(uint8_t addr)
{
    switch (addr) {
    case TPUI_TSP_CTRL1: case TPUI_TSP_CTRL2:
    case TPUI_TX_1: case TPUI_TX_2: case TPUI_TX_3: case TPUI_TX_4:
    case TPUI_TSP_ACT_L: case TPUI_TSP_ACT_U:
    case TPUI_TSP_SET1: case TPUI_TSP_SET2: case TPUI_TSP_SET3:
        return true;
    default:
        return false;
    }
}

void calypso_tsp_move(uint8_t addr, uint8_t data, uint32_t fn)
{
    switch (addr) {
    case TPUI_TX_1: tsp.tx[0] = data; break;
    case TPUI_TX_2: tsp.tx[1] = data; break;
    case TPUI_TX_3: tsp.tx[2] = data; break;
    case TPUI_TX_4: tsp.tx[3] = data; break;
    case TPUI_TSP_CTRL1:
        tsp.ctrl1 = data;
        break;
    case TPUI_TSP_CTRL2:
        if (data & TPUI_CTRL2_WR) {
            uint8_t dev_idx = (tsp.ctrl1 >> 5) & 0x07;
            uint8_t bitlen = (tsp.ctrl1 & 0x1F) + 1;
            uint32_t dout;
            if (bitlen <= 8) {
                dout = tsp.tx[0];
            } else if (bitlen <= 16) {
                dout = ((uint32_t)tsp.tx[0] << 8) | tsp.tx[1];
            } else if (bitlen <= 24) {
                dout = ((uint32_t)tsp.tx[0] << 16) | ((uint32_t)tsp.tx[1] << 8) | tsp.tx[2];
            } else {
                dout = ((uint32_t)tsp.tx[0] << 24) | ((uint32_t)tsp.tx[1] << 16) |
                       ((uint32_t)tsp.tx[2] << 8) | tsp.tx[3];
            }
            calypso_trf6151_tsp_write(dev_idx, dout);
        }
        break;
    default:
        break;
    }
}

hw/char/calypso_uart.c

.c 622 lignes - 14KB

hw/char/calypso_uart.c
/* SPDX-License-Identifier: GPL-2.0-or-later */

#include "qemu/osdep.h"
#include "hw/sysbus.h"
#include "hw/irq.h"
#include "chardev/char-fe.h"
#include "qemu/log.h"
#include "qemu/timer.h"
#include "qemu/main-loop.h"
#include "hw/core/cpu.h"
#include "hw/qdev-properties.h"
#include "hw/qdev-properties-system.h"
#include "hw/arm/calypso/calypso_uart.h"
#include "hw/arm/calypso/calypso_trx.h"
#include "hw/arm/calypso/calypso_l1ctl_tap.h"

#define REG_RBR_THR   0x00
#define REG_IER       0x01
#define REG_IIR_FCR   0x02
#define REG_LCR       0x03
#define REG_MCR       0x04
#define REG_LSR       0x05
#define REG_MSR       0x06
#define REG_SPR       0x07
#define REG_MDR1      0x08
#define REG_SCR       0x10
#define REG_SSR       0x11

#define IER_RX_DATA   (1 << 0)
#define IER_TX_EMPTY  (1 << 1)
#define IER_RX_LINE   (1 << 2)

#define IIR_NO_INT    0x01
#define IIR_RX_LINE   0x06
#define IIR_RX_DATA   0x04
#define IIR_TX_EMPTY  0x02

#define LCR_DLAB      (1 << 7)
#define LCR_CONF_BF   0xBF

#define LSR_DR        (1 << 0)
#define LSR_OE        (1 << 1)
#define LSR_THRE      (1 << 5)
#define LSR_TEMT      (1 << 6)

#define MSR_CTS       (1 << 4)
#define MSR_DSR       (1 << 5)
#define MSR_DCD       (1 << 7)

#define FCR_FIFO_EN   (1 << 0)
#define FCR_RX_RESET  (1 << 1)
#define FCR_TX_RESET  (1 << 2)

#define SSR_TX_FIFO_FULL  (1 << 0)

static void fifo_reset(CalypsoUARTState *s)
{
    s->rx_head = 0;
    s->rx_tail = 0;
    s->rx_count = 0;
}

static void fifo_push(CalypsoUARTState *s, uint8_t data)
{
    if (s->rx_count >= CALYPSO_UART_RX_FIFO_SIZE) {
        s->lsr |= LSR_OE;
        fprintf(stderr,
                "[UART:%s] RX FIFO OVERFLOW drop=0x%02x count=%u size=%u\n",
                s->label ? s->label : "?",
                data,
                (unsigned)s->rx_count,
                (unsigned)CALYPSO_UART_RX_FIFO_SIZE);
        return;
    }

    s->rx_fifo[s->rx_head] = data;
    s->rx_head = (s->rx_head + 1) % CALYPSO_UART_RX_FIFO_SIZE;
    s->rx_count++;
}

static uint8_t fifo_pop(CalypsoUARTState *s)
{
    uint8_t data = 0;

    if (s->rx_count == 0) {
        return 0;
    }

    data = s->rx_fifo[s->rx_tail];
    s->rx_tail = (s->rx_tail + 1) % CALYPSO_UART_RX_FIFO_SIZE;
    s->rx_count--;

    return data;
}

static void calypso_uart_update_irq(CalypsoUARTState *s)
{
    uint8_t iir = IIR_NO_INT;
    bool want = false;

    if ((s->ier & IER_RX_LINE) && (s->lsr & LSR_OE)) {
        iir = IIR_RX_LINE;
        want = true;
    } else if ((s->ier & IER_RX_DATA) && (s->lsr & LSR_DR)) {
        iir = IIR_RX_DATA;
        want = true;
    } else if ((s->ier & IER_TX_EMPTY) && s->thr_empty_pending) {
        iir = IIR_TX_EMPTY;
        want = true;
    }

    s->iir = iir;

    qemu_irq_lower(s->irq);
    if (want) {
        qemu_irq_raise(s->irq);
    }
}

void calypso_uart_kick_rx(CalypsoUARTState *s)
{
    if (s->rx_count > 0 && (s->lsr & LSR_DR)) {

        qemu_irq_lower(s->irq);
        calypso_uart_update_irq(s);
    }
}

void calypso_uart_poll_backend(CalypsoUARTState *s)
{
    qemu_chr_fe_accept_input(&s->chr);
}

void calypso_uart_kick_tx(CalypsoUARTState *s)
{

    calypso_uart_update_irq(s);
}

void calypso_uart_inject_raw(CalypsoUARTState *s, const uint8_t *buf, int len)
{
    if (!s) return;
    for (int i = 0; i < len; i++) {
        fifo_push(s, buf[i]);
    }
    if (s->rx_count > 0) {
        s->lsr |= LSR_DR;
        calypso_uart_update_irq(s);
    }
}

void calypso_uart_force_init(CalypsoUARTState *s)
{

    if (s->mdr1 != 0x00) {
        s->mdr1 = 0x00;
        s->scr = 0x01;
    }
    s->ier = 0x03;
    calypso_uart_update_irq(s);
}

#define UART_RX_POLL_NS  (10 * 1000 * 1000)

static void calypso_uart_rx_poll(void *opaque)
{
    CalypsoUARTState *s = (CalypsoUARTState *)opaque;

    qemu_chr_fe_accept_input(&s->chr);

    timer_mod(s->rx_poll_timer,
              qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 5);
}

typedef enum {
    ROM_IDLE,
    ROM_AFTER_3C,
    ROM_BLOCK_DATA,
    ROM_CHK_DATA,
    ROM_BR_DATA,
    ROM_PASSTHROUGH,
} RomloadState;

static struct {
    RomloadState state;
    int          needed;
    uint16_t     payload_size;
} romload = {
    .state = ROM_IDLE,
    .payload_size = 1024,
};

static bool romload_stub_eat(CalypsoUARTState *s, uint8_t b)
{
    if (romload.state == ROM_PASSTHROUGH) {
        return false;
    }

    switch (romload.state) {
    case ROM_IDLE:
        if (b == 0x3c) {
            romload.state = ROM_AFTER_3C;
        }

        return true;

    case ROM_AFTER_3C: {
        if (b == 0x69) {

            uint8_t ack[6] = {
                0x3e, 0x69,
                0x3e, 0x70,
                (uint8_t)((romload.payload_size + 10) & 0xFF),
                (uint8_t)(((romload.payload_size + 10) >> 8) & 0xFF),
            };
            qemu_chr_fe_write_all(&s->chr, ack, sizeof(ack));
            romload.state = ROM_IDLE;
        } else if (b == 0x77) {

            romload.state  = ROM_BLOCK_DATA;
            romload.needed = 8 + romload.payload_size;
        } else if (b == 0x63) {
            romload.state  = ROM_CHK_DATA;
            romload.needed = 1;
        } else if (b == 0x62) {
            romload.state  = ROM_BR_DATA;
            romload.needed = 4;
        } else {

            romload.state = ROM_IDLE;
        }
        return true;
    }

    case ROM_BLOCK_DATA:
        if (--romload.needed == 0) {
            uint8_t ack[2] = { 0x3e, 0x77 };
            qemu_chr_fe_write_all(&s->chr, ack, sizeof(ack));
            romload.state = ROM_IDLE;
        }
        return true;

    case ROM_CHK_DATA:
        if (--romload.needed == 0) {

            uint8_t ack[3] = { 0x3e, 0x63, b };
            qemu_chr_fe_write_all(&s->chr, ack, sizeof(ack));
            romload.state = ROM_IDLE;
        }
        return true;

    case ROM_BR_DATA:
        if (--romload.needed == 0) {
            uint8_t ack[2] = { 0x3e, 0x62 };
            qemu_chr_fe_write_all(&s->chr, ack, sizeof(ack));
            romload.state = ROM_PASSTHROUGH;
        }
        return true;

    case ROM_PASSTHROUGH:
        return false;
    }
    return false;
}

int calypso_uart_can_receive(void *opaque)
{
    CalypsoUARTState *s = (CalypsoUARTState *)opaque;
    return CALYPSO_UART_RX_FIFO_SIZE - s->rx_count;
}

void calypso_uart_receive(void *opaque, const uint8_t *buf, int size)
{
    CalypsoUARTState *s = (CalypsoUARTState *)opaque;

    if (s->label && !strcmp(s->label, "irda")) {
        for (int i = 0; i < size; i++)
            fifo_push(s, buf[i]);
        if (s->rx_count > 0)
            s->lsr |= LSR_DR;
        calypso_uart_update_irq(s);
        return;
    }

    if (s->label && !strcmp(s->label, "modem")) {
        uint8_t passthrough[CALYPSO_UART_RX_FIFO_SIZE];
        int     pt_len = 0;
        for (int i = 0; i < size; i++) {
            if (!romload_stub_eat(s, buf[i])) {
                passthrough[pt_len++] = buf[i];
            }
        }
        if (pt_len > 0) {
            calypso_uart_inject_raw(s, passthrough, pt_len);
        }

        qemu_chr_fe_accept_input(&s->chr);
        return;
    }

    calypso_uart_inject_raw(s, buf, size);

    if (s->rx_count > 0) {
        s->lsr |= LSR_DR;
    }

    calypso_uart_update_irq(s);
}

static uint64_t calypso_uart_read(void *opaque, hwaddr offset, unsigned size)
{
    CalypsoUARTState *s = CALYPSO_UART(opaque);
    uint64_t val = 0;

    switch (offset) {
    case REG_RBR_THR:
        if (s->lcr & LCR_DLAB) {
            val = s->dll;
        } else {

            val = fifo_pop(s);

            if (s->rx_count > 0) {
                s->lsr |= LSR_DR;
            } else {
                s->lsr &= ~LSR_DR;
            }

            calypso_uart_update_irq(s);
        }
        break;

    case REG_IER:
        if (s->lcr & LCR_DLAB) {
            val = s->dlh;
        } else {
            val = s->ier;
        }
        break;

    case REG_IIR_FCR:
        if (s->lcr == LCR_CONF_BF) {
            val = s->efr;
        } else {
            val = s->iir;
            if ((s->iir & 0x0F) == IIR_TX_EMPTY) {

                s->tx_empty_reads++;
                if (s->tx_empty_reads >= 2) {
                    s->thr_empty_pending = false;
                    s->tx_empty_reads = 0;
                    calypso_uart_update_irq(s);
                }
            }
        }
        break;

    case REG_LCR:
        val = s->lcr;
        break;

    case REG_MCR:
        if (s->lcr == LCR_CONF_BF) {
            val = s->xon1;
        } else {
            val = s->mcr;
        }
        break;

    case REG_LSR:
        if (s->lcr == LCR_CONF_BF) {
            val = s->xon2;
        } else {
            val = s->lsr;
            s->lsr &= ~LSR_OE;
        }
        break;

    case REG_MSR:
        if (s->lcr == LCR_CONF_BF) {
            val = s->xoff1;
        } else {
            val = MSR_CTS | MSR_DSR | MSR_DCD;
        }
        break;

    case REG_SPR:
        if (s->lcr == LCR_CONF_BF) {
            val = s->xoff2;
        } else {
            val = s->spr;
        }
        break;

    case REG_MDR1:
        val = s->mdr1;
        break;

    case REG_SCR:
        val = s->scr;
        break;

    case REG_SSR:
        val = s->ssr & ~SSR_TX_FIFO_FULL;
        break;

    default:
        break;
    }

    return val;
}

static void calypso_uart_write(void *opaque, hwaddr offset,
                               uint64_t value, unsigned size)
{
    CalypsoUARTState *s = CALYPSO_UART(opaque);

    switch (offset) {
    case REG_RBR_THR:
        if (s->lcr & LCR_DLAB) {
            s->dll = value;
        } else {
            uint8_t ch = (uint8_t)value;



            (void)qemu_chr_fe_write(&s->chr, &ch, 1);

            if (s->label && !strcmp(s->label, "modem")) {
                calypso_l1ctl_tap_tx_byte(ch);
            }

            s->lsr |= LSR_THRE | LSR_TEMT;
            s->thr_empty_pending = true;
            s->tx_empty_reads = 0;
            calypso_uart_update_irq(s);
        }
        break;

    case REG_IER:
        if (s->lcr & LCR_DLAB) {
            s->dlh = value;
        } else {
            uint8_t old = s->ier;
            s->ier = value & 0x0F;


            if (!(old & IER_TX_EMPTY) &&
                (s->ier & IER_TX_EMPTY) &&
                (s->lsr & LSR_THRE)) {
                s->thr_empty_pending = true;
            }

            calypso_uart_update_irq(s);
        }
        break;

    case REG_IIR_FCR:
        if (s->lcr == LCR_CONF_BF) {
            s->efr = value;
        } else {
            s->fcr = value;

            if (value & FCR_RX_RESET) {
                fifo_reset(s);
                s->lsr &= ~LSR_DR;
            }

            if (value & FCR_TX_RESET) {
                s->thr_empty_pending = false;
                s->lsr |= LSR_THRE | LSR_TEMT;
            }

            calypso_uart_update_irq(s);
        }
        break;

    case REG_LCR:
        s->lcr = value;
        break;

    case REG_MCR:
        if (s->lcr == LCR_CONF_BF) {
            s->xon1 = value;
        } else {
            s->mcr = value;
        }
        break;

    case REG_LSR:
        if (s->lcr == LCR_CONF_BF) {
            s->xon2 = value;
        }
        break;

    case REG_MSR:
        if (s->lcr == LCR_CONF_BF) {
            s->xoff1 = value;
        }
        break;

    case REG_SPR:
        if (s->lcr == LCR_CONF_BF) {
            s->xoff2 = value;
        } else {
            s->spr = value;
        }
        break;

    case REG_MDR1:
        s->mdr1 = value;
        break;

    case REG_SCR:
        s->scr = value;
        break;

    case REG_SSR:
        s->ssr = value;
        break;

    default:
        break;
    }
}

static const MemoryRegionOps calypso_uart_ops = {
    .read = calypso_uart_read,
    .write = calypso_uart_write,
    .endianness = DEVICE_NATIVE_ENDIAN,
    .impl = { .min_access_size = 1, .max_access_size = 1 },
    .valid = { .min_access_size = 1, .max_access_size = 1 },
};

static void calypso_uart_realize(DeviceState *dev, Error **errp)
{
    CalypsoUARTState *s = CALYPSO_UART(dev);
    bool connected;

    memory_region_init_io(&s->iomem, OBJECT(dev), &calypso_uart_ops, s,
                          "calypso-uart", 0x100);
    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);
    sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq);

    connected = qemu_chr_fe_backend_connected(&s->chr);


    if (connected) {
        qemu_chr_fe_set_handlers(&s->chr,
                                 calypso_uart_can_receive,
                                 calypso_uart_receive,
                                 NULL, NULL,
                                 s,
                                 NULL, true);

        s->rx_poll_timer = timer_new_ms(QEMU_CLOCK_REALTIME,
                                        calypso_uart_rx_poll, s);
        timer_mod(s->rx_poll_timer,
                  qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + 10);
    }

}

static void calypso_uart_reset_state(DeviceState *dev)
{
    CalypsoUARTState *s = CALYPSO_UART(dev);

    s->ier = 0;
    s->iir = IIR_NO_INT;
    s->fcr = 0;
    s->lcr = 0;
    s->mcr = 0;
    s->lsr = LSR_THRE | LSR_TEMT;
    s->msr = MSR_CTS | MSR_DSR | MSR_DCD;
    s->spr = 0;
    s->dll = 0;
    s->dlh = 0;
    s->mdr1 = 0;

    s->efr = 0;
    s->xon1 = 0;
    s->xon2 = 0;
    s->xoff1 = 0;
    s->xoff2 = 0;
    s->scr = 0;
    s->ssr = 0;

    s->thr_empty_pending = false;

    fifo_reset(s);
}

static Property calypso_uart_properties[] = {
    DEFINE_PROP_CHR("chardev", CalypsoUARTState, chr),
    DEFINE_PROP_STRING("label", CalypsoUARTState, label),
    DEFINE_PROP_END_OF_LIST(),
};

static void calypso_uart_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);

    dc->realize = calypso_uart_realize;
    device_class_set_legacy_reset(dc, calypso_uart_reset_state);
    dc->desc = "Calypso UART";
    device_class_set_props(dc, calypso_uart_properties);
}

static const TypeInfo calypso_uart_info = {
    .name          = TYPE_CALYPSO_UART,
    .parent        = TYPE_SYS_BUS_DEVICE,
    .instance_size = sizeof(CalypsoUARTState),
    .class_init    = calypso_uart_class_init,
};

static void calypso_uart_register_types(void)
{
    type_register_static(&calypso_uart_info);
}

type_init(calypso_uart_register_types)

hw/intc/calypso_inth.c

.c 218 lignes - 5,4KB

hw/intc/calypso_inth.c
/* SPDX-License-Identifier: GPL-2.0-or-later */

#include "qemu/osdep.h"
#include "hw/irq.h"
#include "hw/sysbus.h"
#include "hw/arm/calypso/calypso_inth.h"

static void calypso_inth_update(CalypsoINTHState *s)
{
    uint32_t active = s->levels & ~s->mask;
    int best_irq = -1, best_irq_prio = 0x7F;
    int best_fiq = -1, best_fiq_prio = 0x7F;

    for (int j = 0; j < CALYPSO_INTH_NUM_IRQS; j++) {
        int i = (s->rr_start + j) % CALYPSO_INTH_NUM_IRQS;
        if (!(active & (1u << i))) continue;
        int prio = s->ilr[i] & 0x1F;
        int is_fiq = (s->ilr[i] >> 8) & 1;
        if (is_fiq) {
            if (prio < best_fiq_prio) { best_fiq_prio = prio; best_fiq = i; }
        } else {
            if (prio < best_irq_prio) { best_irq_prio = prio; best_irq = i; }
        }
    }

    if (best_irq >= 0) {
        s->ith_v = best_irq;
        qemu_irq_raise(s->parent_irq);
    } else {
        if (best_fiq < 0) s->ith_v = 0;
        qemu_irq_lower(s->parent_irq);
    }

    if (best_fiq >= 0) {
        s->fiq_v = best_fiq;
        qemu_irq_raise(s->parent_fiq);
    } else {
        qemu_irq_lower(s->parent_fiq);
    }
}

static void calypso_inth_set_irq(void *opaque, int irq, int level)
{
    CalypsoINTHState *s = CALYPSO_INTH(opaque);

    if (level) {
        s->levels |= (1u << irq);
    } else {
        s->levels &= ~(1u << irq);
    }

    calypso_inth_update(s);
}

static uint64_t calypso_inth_read(void *opaque, hwaddr offset, unsigned size)
{
    CalypsoINTHState *s = CALYPSO_INTH(opaque);

    switch (offset) {
    case 0x00:
        return s->levels & 0xFFFF;
    case 0x02:
        return (s->levels >> 16) & 0xFFFF;
    case 0x08:
        return s->mask & 0xFFFF;
    case 0x0a:
        return (s->mask >> 16) & 0xFFFF;
    case 0x10:
    case 0x80:
    {
        uint16_t num = s->ith_v;

        if (num == 4 || num == 5 || num == 15) {
            s->levels &= ~(1u << num);
        }

        calypso_inth_update(s);
        return num;
    }
    case 0x12:
    case 0x82:
    {

        uint16_t num = s->fiq_v;
        if (num == 4 || num == 5 || num == 15) {
            s->levels &= ~(1u << num);
        }
        calypso_inth_update(s);
        return num;
    }
    case 0x14:
    case 0x84:
        return 0;
    default:
        if (offset >= 0x20 && offset < 0x60) {
            int idx = (offset - 0x20) / 2;
            return s->ilr[idx];
        }
        return 0;
    }
}

static void calypso_inth_write(void *opaque, hwaddr offset, uint64_t value,
                                unsigned size)
{
    CalypsoINTHState *s = CALYPSO_INTH(opaque);

    switch (offset) {

    case 0x00:
    {
        s->levels &= (0xFFFF0000u | (uint32_t)(value & 0xFFFF));
        calypso_inth_update(s);
        break;
    }
    case 0x02:
    {
        s->levels &= (0x0000FFFFu | ((uint32_t)(value & 0xFFFF) << 16));
        calypso_inth_update(s);
        break;
    }
    case 0x08:
    {
        s->mask = (s->mask & 0xFFFF0000) | (value & 0xFFFF);
        calypso_inth_update(s);
        break;
    }
    case 0x0a:
    {
        s->mask = (s->mask & 0x0000FFFF) | ((value & 0xFFFF) << 16);
        calypso_inth_update(s);
        break;
    }
    case 0x14:
    case 0x84:
    {

        uint16_t svc = s->ith_v;
        if (svc > 0 || (s->levels & 1)) {

            s->rr_start = (svc + 1) % CALYPSO_INTH_NUM_IRQS;
        }
        calypso_inth_update(s);
        break;
    }
    default:
        if (offset >= 0x20 && offset < 0x60) {
            int idx = (offset - 0x20) / 2;
            s->ilr[idx] = value & 0x1FFF;

            if (idx == 7) {
                s->ilr[7] = (s->ilr[7] & ~0x1F) | (s->ilr[4] & 0x1F);
            }

            if (idx == 18) {
                s->ilr[18] = (s->ilr[18] & ~0x1F) | (s->ilr[4] & 0x1F);
            }
        }
        break;
    }
}

static const MemoryRegionOps calypso_inth_ops = {
    .read = calypso_inth_read,
    .write = calypso_inth_write,
    .endianness = DEVICE_NATIVE_ENDIAN,
    .valid = { .min_access_size = 1, .max_access_size = 2 },
    .impl  = { .min_access_size = 1, .max_access_size = 2 },
};

static void calypso_inth_realize(DeviceState *dev, Error **errp)
{
    CalypsoINTHState *s = CALYPSO_INTH(dev);

    memory_region_init_io(&s->iomem, OBJECT(dev), &calypso_inth_ops, s,
                          "calypso-inth", 0x100);
    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);

    sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->parent_irq);
    sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->parent_fiq);

    qdev_init_gpio_in(dev, calypso_inth_set_irq, CALYPSO_INTH_NUM_IRQS);
}

static void calypso_inth_reset(DeviceState *dev)
{
    CalypsoINTHState *s = CALYPSO_INTH(dev);

    s->levels = 0;
    s->mask = 0x00000000;
    s->ith_v = 0;
    s->fiq_v = 0;
    s->rr_start = 0;
    memset(s->ilr, 0, sizeof(s->ilr));
}

static void calypso_inth_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);

    dc->realize = calypso_inth_realize;
    device_class_set_legacy_reset(dc, calypso_inth_reset);
    dc->desc = "Calypso INTH interrupt controller";
}

static const TypeInfo calypso_inth_info = {
    .name          = TYPE_CALYPSO_INTH,
    .parent        = TYPE_SYS_BUS_DEVICE,
    .instance_size = sizeof(CalypsoINTHState),
    .class_init    = calypso_inth_class_init,
};

static void calypso_inth_register_types(void)
{
    type_register_static(&calypso_inth_info);
}

type_init(calypso_inth_register_types)

hw/ssi/calypso_i2c.c

.c 66 lignes - 1,7KB

hw/ssi/calypso_i2c.c
/* SPDX-License-Identifier: GPL-2.0-or-later */

#include "qemu/osdep.h"
#include "hw/sysbus.h"
#include "qemu/log.h"

#define TYPE_CALYPSO_I2C "calypso-i2c"
OBJECT_DECLARE_SIMPLE_TYPE(CalypsoI2CState, CALYPSO_I2C)

struct CalypsoI2CState {
    SysBusDevice parent_obj;
    MemoryRegion iomem;
};

static uint64_t calypso_i2c_read(void *opaque, hwaddr offset, unsigned size)
{
    switch (offset) {
    case 0x04:
        return 0x04;
    default:
        return 0;
    }
}

static void calypso_i2c_write(void *opaque, hwaddr offset, uint64_t value,
                              unsigned size)
{

}

static const MemoryRegionOps calypso_i2c_ops = {
    .read = calypso_i2c_read,
    .write = calypso_i2c_write,
    .endianness = DEVICE_NATIVE_ENDIAN,
    .impl = { .min_access_size = 2, .max_access_size = 2 },
};

static void calypso_i2c_realize(DeviceState *dev, Error **errp)
{
    CalypsoI2CState *s = CALYPSO_I2C(dev);

    memory_region_init_io(&s->iomem, OBJECT(dev), &calypso_i2c_ops, s,
                          "calypso-i2c", 0x100);
    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);
}

static void calypso_i2c_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);
    dc->realize = calypso_i2c_realize;
    dc->desc = "Calypso I2C stub";
}

static const TypeInfo calypso_i2c_info = {
    .name          = TYPE_CALYPSO_I2C,
    .parent        = TYPE_SYS_BUS_DEVICE,
    .instance_size = sizeof(CalypsoI2CState),
    .class_init    = calypso_i2c_class_init,
};

static void calypso_i2c_register_types(void)
{
    type_register_static(&calypso_i2c_info);
}

type_init(calypso_i2c_register_types)

hw/ssi/calypso_spi.c

.c 167 lignes - 4,3KB

hw/ssi/calypso_spi.c
/* SPDX-License-Identifier: GPL-2.0-or-later */

#include "qemu/osdep.h"
#include "hw/sysbus.h"
#include "hw/irq.h"
#include "qemu/log.h"
#include "hw/arm/calypso/calypso_spi.h"

#define SPI_REG_SET1     0x00
#define SPI_REG_SET2     0x02
#define SPI_REG_CTRL     0x04
#define SPI_REG_STATUS   0x06
#define SPI_REG_TX_LSB   0x08
#define SPI_REG_TX_MSB   0x0A
#define SPI_REG_RX_LSB   0x0C
#define SPI_REG_RX_MSB   0x0E

#define SPI_CTRL_START   (1 << 0)

static uint16_t twl3025_spi_xfer(CalypsoSPIState *s, uint16_t tx)
{
    int read  = (tx >> 15) & 1;
    int addr  = (tx >> 6) & 0x1FF;
    int wdata = tx & 0x3F;

    if (addr >= 256) {
        addr = 0;
    }

    if (read) {
        return s->abb_regs[addr];
    } else {
        if (addr == ABB_TOGBR1 && (wdata & 0x01)) {
            return 0;
        }
        s->abb_regs[addr] = wdata;
        if (addr == ABB_TOGBR2) {
            return 0;
        }

        if (addr == ABB_VRPCDEV) {
            s->abb_regs[ABB_VRPCSTS] = 0x1F;
        }
        return 0;
    }
}

static uint64_t calypso_spi_read(void *opaque, hwaddr offset, unsigned size)
{
    CalypsoSPIState *s = CALYPSO_SPI(opaque);

    switch (offset) {
    case SPI_REG_SET1:
        return s->set1;
    case SPI_REG_SET2:
        return s->set2;
    case SPI_REG_CTRL:
        return s->ctrl;
    case SPI_REG_STATUS:
        return SPI_STATUS_RE;
    case SPI_REG_TX_LSB:
        return s->tx_data & 0xFF;
    case SPI_REG_TX_MSB:
        return (s->tx_data >> 8) & 0xFF;
    case SPI_REG_RX_LSB:
        return s->rx_data & 0xFF;
    case SPI_REG_RX_MSB:
        return (s->rx_data >> 8) & 0xFF;
    default:
        qemu_log_mask(LOG_UNIMP, "calypso-spi: read at 0x%02x\n",
                       (unsigned)offset);
        return 0;
    }
}

static void calypso_spi_write(void *opaque, hwaddr offset, uint64_t value,
                               unsigned size)
{
    CalypsoSPIState *s = CALYPSO_SPI(opaque);

    switch (offset) {
    case SPI_REG_SET1:
        s->set1 = value & 0xFFFF;
        break;
    case SPI_REG_SET2:
        s->set2 = value & 0xFFFF;
        break;
    case SPI_REG_CTRL:
        s->ctrl = value & 0xFFFF;
        if (value & SPI_CTRL_START) {
            s->rx_data = twl3025_spi_xfer(s, s->tx_data);
            qemu_irq_pulse(s->irq);
        }
        break;
    case SPI_REG_STATUS:
        break;
    case SPI_REG_TX_LSB:
        s->tx_data = (s->tx_data & 0xFF00) | (value & 0xFF);
        break;
    case SPI_REG_TX_MSB:
        s->tx_data = (s->tx_data & 0x00FF) | ((value & 0xFF) << 8);
        break;
    case SPI_REG_RX_LSB:
    case SPI_REG_RX_MSB:
        break;
    default:
        qemu_log_mask(LOG_UNIMP, "calypso-spi: write 0x%04x at 0x%02x\n",
                       (unsigned)value, (unsigned)offset);
        break;
    }
}

static const MemoryRegionOps calypso_spi_ops = {
    .read = calypso_spi_read,
    .write = calypso_spi_write,
    .endianness = DEVICE_NATIVE_ENDIAN,
    .impl = { .min_access_size = 2, .max_access_size = 2 },
};

static void calypso_spi_realize(DeviceState *dev, Error **errp)
{
    CalypsoSPIState *s = CALYPSO_SPI(dev);

    memory_region_init_io(&s->iomem, OBJECT(dev), &calypso_spi_ops, s,
                          "calypso-spi", 0x100);
    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);
    sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq);
}

static void calypso_spi_reset(DeviceState *dev)
{
    CalypsoSPIState *s = CALYPSO_SPI(dev);

    s->set1 = 0;
    s->set2 = 0;
    s->ctrl = 0;
    s->status = SPI_STATUS_RE;
    s->tx_data = 0;
    s->rx_data = 0;
    memset(s->abb_regs, 0, sizeof(s->abb_regs));

    s->abb_regs[ABB_VRPCSTS] = 0x1F;
    s->abb_regs[ABB_ITSTATREG] = 0x00;
}

static void calypso_spi_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);

    dc->realize = calypso_spi_realize;
    device_class_set_legacy_reset(dc, calypso_spi_reset);
    dc->desc = "Calypso SPI controller + TWL3025 ABB";
}

static const TypeInfo calypso_spi_info = {
    .name          = TYPE_CALYPSO_SPI,
    .parent        = TYPE_SYS_BUS_DEVICE,
    .instance_size = sizeof(CalypsoSPIState),
    .class_init    = calypso_spi_class_init,
};

static void calypso_spi_register_types(void)
{
    type_register_static(&calypso_spi_info);
}

type_init(calypso_spi_register_types)

hw/timer/calypso_timer.c

.c 226 lignes - 6,3KB

hw/timer/calypso_timer.c
/* SPDX-License-Identifier: GPL-2.0-or-later */

#include "qemu/osdep.h"
#include "hw/sysbus.h"
#include "hw/irq.h"
#include "qemu/timer.h"
#include "hw/arm/calypso/calypso_timer.h"

#define TIMER_CTRL_START         (1 << 0)
#define TIMER_CTRL_RELOAD        (1 << 1)
#define TIMER_CTRL_PRESCALER_SH  2
#define TIMER_CTRL_PRESCALER_MSK (0x7 << 2)
#define TIMER_CTRL_CLOCK_ENABLE  (1 << 5)

#define CALYPSO_BASE_CLK   13000000LL

static QEMUClockType calypso_timer_clock(void)
{
    return QEMU_CLOCK_REALTIME;
}

static bool calypso_timer_should_run(CalypsoTimerState *s)
{
    return (s->ctrl & TIMER_CTRL_START) && (s->ctrl & TIMER_CTRL_CLOCK_ENABLE);
}

static void calypso_timer_recompute_tick(CalypsoTimerState *s)
{
    int prescaler = (s->ctrl & TIMER_CTRL_PRESCALER_MSK) >> TIMER_CTRL_PRESCALER_SH;

    int64_t divider = 32LL << prescaler;
    int64_t freq = CALYPSO_BASE_CLK / divider;
    if (freq <= 0) freq = 1;
    s->tick_ns = NANOSECONDS_PER_SECOND / freq;
}

static uint16_t calypso_timer_current_count(CalypsoTimerState *s)
{
    if (!s->running) return s->count;
    int64_t now = qemu_clock_get_ns(calypso_timer_clock());
    int64_t elapsed = now - s->epoch_ns;
    if (elapsed < 0) elapsed = 0;
    int64_t ticks = elapsed / s->tick_ns;
    int64_t period = (int64_t)s->load + 1;
    if (s->ctrl & TIMER_CTRL_RELOAD) {
        ticks %= period;
    } else if (ticks > s->load) {
        return 0;
    }
    return (uint16_t)(s->load - ticks);
}

static void calypso_timer_schedule_wrap(CalypsoTimerState *s)
{

    int64_t now = qemu_clock_get_ns(calypso_timer_clock());
    uint16_t cur = calypso_timer_current_count(s);
    int64_t ns_to_wrap = (int64_t)(cur + 1) * s->tick_ns;
    timer_mod(s->timer, now + ns_to_wrap);
}

static void calypso_timer_tick(void *opaque)
{
    CalypsoTimerState *s = CALYPSO_TIMER(opaque);

    if (!s->running) return;

    qemu_irq_raise(s->irq);

    if (s->ctrl & TIMER_CTRL_RELOAD) {

        s->epoch_ns = qemu_clock_get_ns(calypso_timer_clock());
        s->count = s->load;
        timer_mod(s->timer, s->epoch_ns + (int64_t)(s->load + 1) * s->tick_ns);
    } else {
        s->running = false;
        s->count = 0;
    }
}

static void calypso_timer_start(CalypsoTimerState *s)
{
    if (s->load == 0) return;
    calypso_timer_recompute_tick(s);
    s->count = s->load;
    s->running = true;
    s->epoch_ns = qemu_clock_get_ns(calypso_timer_clock());
    timer_mod(s->timer, s->epoch_ns + (int64_t)(s->load + 1) * s->tick_ns);
}

#define LOST_TICKS_PER_TDMA 1875

static CalypsoTimerState *g_lost_timer;

void calypso_timer_register_lost(DeviceState *d)
{
    g_lost_timer = CALYPSO_TIMER(d);
}

void calypso_timer_lost_frame_tick(uint32_t fn)
{
    CalypsoTimerState *s = g_lost_timer;
    if (!s || !s->running) {
        return;
    }
    uint32_t period = (uint32_t)s->load + 1;
    uint32_t step = ((uint64_t)fn * LOST_TICKS_PER_TDMA) % period;
    s->lost_latch_count = (uint16_t)(s->load - step);
    s->lost_latch_active = true;
}

static uint64_t calypso_timer_read(void *opaque, hwaddr offset, unsigned size)
{
    CalypsoTimerState *s = CALYPSO_TIMER(opaque);


    switch (offset) {
    case 0x00: return s->ctrl;
    case 0x02: return s->load;
    case 0x04:
        if (s->lost_latch_active) {
            if (s == g_lost_timer) {

                uint32_t period = (uint32_t)s->load + 1;
                s->lost_read_phase = (s->lost_read_phase + LOST_TICKS_PER_TDMA) % period;
                return (uint16_t)(s->load - s->lost_read_phase);
            }
            return s->lost_latch_count;
        }
        return calypso_timer_current_count(s);
    default:   return 0;
    }
}

static void calypso_timer_write(void *opaque, hwaddr offset, uint64_t value,
                                 unsigned size)
{
    CalypsoTimerState *s = CALYPSO_TIMER(opaque);

    switch (offset) {
    case 0x00: {
        bool was_running = s->running;
        uint16_t old_ctrl = s->ctrl;
        s->ctrl = value & 0xFF;
        if (calypso_timer_should_run(s)) {
            if (!was_running) {
                calypso_timer_start(s);
            } else if ((old_ctrl & TIMER_CTRL_PRESCALER_MSK) !=
                       (s->ctrl & TIMER_CTRL_PRESCALER_MSK)) {

                s->count = calypso_timer_current_count(s);
                calypso_timer_recompute_tick(s);
                s->epoch_ns = qemu_clock_get_ns(calypso_timer_clock()) -
                              (int64_t)(s->load - s->count) * s->tick_ns;
                calypso_timer_schedule_wrap(s);
            }
        } else {
            s->count = calypso_timer_current_count(s);
            s->running = false;
            timer_del(s->timer);
        }
        break;
    }
    case 0x02:
        s->load = value;
        break;
    }
}

static const MemoryRegionOps calypso_timer_ops = {
    .read = calypso_timer_read,
    .write = calypso_timer_write,
    .endianness = DEVICE_NATIVE_ENDIAN,
    .valid = { .min_access_size = 1, .max_access_size = 2 },
    .impl  = { .min_access_size = 1, .max_access_size = 2 },
};

static void calypso_timer_realize(DeviceState *dev, Error **errp)
{
    CalypsoTimerState *s = CALYPSO_TIMER(dev);

    memory_region_init_io(&s->iomem, OBJECT(dev), &calypso_timer_ops, s,
                          "calypso-timer", 0x100);
    sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);
    sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq);

    s->timer = timer_new_ns(calypso_timer_clock(), calypso_timer_tick, s);
}

static void calypso_timer_reset(DeviceState *dev)
{
    CalypsoTimerState *s = CALYPSO_TIMER(dev);

    s->load = 0;
    s->count = 0;
    s->ctrl = 0;
    s->prescaler = 0;
    s->running = false;
    s->lost_latch_active = false;
    s->lost_latch_count = 0;
    s->lost_read_phase = 0;
    timer_del(s->timer);
}

static void calypso_timer_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);

    dc->realize = calypso_timer_realize;
    device_class_set_legacy_reset(dc, calypso_timer_reset);
    dc->desc = "Calypso GP/Watchdog timer";
}

static const TypeInfo calypso_timer_info = {
    .name          = TYPE_CALYPSO_TIMER,
    .parent        = TYPE_SYS_BUS_DEVICE,
    .instance_size = sizeof(CalypsoTimerState),
    .class_init    = calypso_timer_class_init,
};

static void calypso_timer_register_types(void)
{
    type_register_static(&calypso_timer_info);
}

type_init(calypso_timer_register_types)