4 - Sources
hw/arm/calypso/calypso_l1ctl_tap.c
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
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
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
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
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
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
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
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
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
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
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
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
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
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)