#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: midictl.c,v 1.10 2022/10/31 20:35:02 andvar Exp $");
#include <sys/systm.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/kthread.h>
#include <sys/kmem.h>
#include "midictl.h"
typedef enum { CTL1, CTL7, CTL14, RPN, NRPN } class;
static void reset_all_controllers(midictl *mc, uint_fast8_t chan);
static void enter14(midictl *mc, uint_fast8_t chan, class c,
uint_fast16_t key, _Bool islsb, uint8_t val);
static uint_fast16_t read14(midictl *mc, uint_fast8_t chan, class c,
uint_fast16_t key, uint_fast16_t dflt);
static class classify(uint_fast16_t *key, _Bool *islsb);
static midictl_notify notify_no_one;
static _Bool store_locate(midictl_store *s, class c,
uint_fast8_t chan, uint_fast16_t key);
static uint16_t store_extract(midictl_store *s, class c,
uint_fast8_t chan, uint_fast16_t key);
static void store_update(midictl_store *s, class c,
uint_fast8_t chan, uint_fast16_t key, uint16_t value);
#define PN_SET 0x8000
#define C14MSET 0x8000
#define C14LSET 0x4000
#define C7_SET 0x80
#define C1_SET 2
struct midictl_store {
uint64_t *table;
uint64_t key;
uint32_t idx;
uint32_t lgcapacity;
uint32_t used;
kcondvar_t cv;
kmutex_t *lock;
bool destroy;
};
#define INITIALLGCAPACITY 6
#define IS_USED 1<<15
#define IS_CTL7 1<<14
#define CTL1SHIFT(chan) (23+((chan)<<1))
#define CTL7SHIFT(chan) (16+((chan)<<3))
#define CTLESHIFT(chan) (23+((chan)<<4))
#define NEED_REHASH(s) ((s)->used * 2 >= 1 << (s)->lgcapacity)
static uint_fast8_t const packing[] = {
[CTL1 ] = 16,
[CTL7 ] = 6,
[CTL14] = 2,
[RPN ] = 2,
[NRPN ] = 2
};
static uint32_t store_idx(uint32_t lgcapacity,
uint64_t *table,
uint64_t key, uint64_t mask);
static void store_rehash(midictl_store *s);
static void store_thread(void *);
int
midictl_open(midictl *mc)
{
midictl_store *s;
int error;
if (mc->lock == NULL)
panic("midictl_open: no lock");
if (NULL == mc->notify)
mc->notify = notify_no_one;
s = kmem_zalloc(sizeof(*s), KM_SLEEP);
s->lgcapacity = INITIALLGCAPACITY;
s->table = kmem_zalloc(sizeof(*s->table)<<s->lgcapacity, KM_SLEEP);
s->lock = mc->lock;
cv_init(&s->cv, "midictlv");
error = kthread_create(PRI_NONE, KTHREAD_MPSAFE, NULL, store_thread,
s, NULL, "midictlt");
if (error != 0) {
printf("midictl: cannot create kthread, error = %d\n", error);
cv_destroy(&s->cv);
kmem_free(s->table, sizeof(*s->table)<<s->lgcapacity);
kmem_free(s, sizeof(*s));
return error;
}
mc->store = s;
return 0;
}
void
midictl_close(midictl *mc)
{
midictl_store *s;
kmutex_t *lock;
s = mc->store;
lock = s->lock;
mutex_enter(lock);
s->destroy = true;
cv_broadcast(&s->cv);
mutex_exit(lock);
}
void
midictl_change(midictl *mc, uint_fast8_t chan, uint8_t *ctlval)
{
class c;
uint_fast16_t key, val;
_Bool islsb, present;
KASSERT(mutex_owned(mc->lock));
KASSERT(!mc->store->destroy);
switch ( ctlval[0] ) {
case MIDI_CTRL_OMNI_OFF:
case MIDI_CTRL_OMNI_ON:
case MIDI_CTRL_POLY_OFF:
case MIDI_CTRL_POLY_ON:
if ( chan != mc->base_channel )
return;
else
return;
case MIDI_CTRL_NOTES_OFF:
mc->notify(mc->cookie, MIDICTL_NOTES_OFF, chan, 0);
return;
case MIDI_CTRL_LOCAL:
mc->notify(mc->cookie, MIDICTL_LOCAL, chan, ctlval[1]);
return;
case MIDI_CTRL_SOUND_OFF:
mc->notify(mc->cookie, MIDICTL_SOUND_OFF, chan, 0);
return;
case MIDI_CTRL_RESET:
reset_all_controllers(mc, chan);
return;
case MIDI_CTRL_RPN_LSB:
mc-> rpn &= ~0x7f;
mc-> rpn |= PN_SET | (0x7f & ctlval[1]);
mc->nrpn &= ~PN_SET;
return;
case MIDI_CTRL_RPN_MSB:
mc-> rpn &= ~0x7fU<<7;
mc-> rpn |= PN_SET | (0x7f & ctlval[1])<<7;
mc->nrpn &= ~PN_SET;
return;
case MIDI_CTRL_NRPN_LSB:
mc->nrpn &= ~0x7f;
mc->nrpn |= PN_SET | (0x7f & ctlval[1]);
mc-> rpn &= ~PN_SET;
return;
case MIDI_CTRL_NRPN_MSB:
mc->nrpn &= ~0x7fU<<7;
mc->nrpn |= PN_SET | (0x7f & ctlval[1])<<7;
mc-> rpn &= ~PN_SET;
return;
case MIDI_CTRL_DATA_ENTRY_LSB:
islsb = 1;
goto whichparm;
case MIDI_CTRL_DATA_ENTRY_MSB:
islsb = 0;
whichparm:
if ( 0 == ( (mc->rpn ^ mc->nrpn) & PN_SET ) )
return;
if ( mc->rpn & PN_SET ) {
key = mc->rpn;
c = RPN;
} else {
key = mc->nrpn;
c = NRPN;
}
key &= 0x3fff;
if ( 0x3fff == key )
return;
enter14(mc, chan, c, key, islsb, ctlval[1]);
return;
case MIDI_CTRL_RPN_INCREMENT:
case MIDI_CTRL_RPN_DECREMENT:
return;
}
key = ctlval[0];
c = classify(&key, &islsb);
switch ( c ) {
case CTL14:
enter14(mc, chan, c, key, islsb, ctlval[1]);
return;
case CTL7:
present = store_locate(mc->store, c, chan, key);
if ( !mc->accept_any_ctl_rpn ) {
if ( !present )
break;
val = store_extract(mc->store, c, chan, key);
if ( !(val&C7_SET) )
break;
}
store_update(mc->store, c, chan, key,
C7_SET | (0x7f & ctlval[1]));
mc->notify(mc->cookie, MIDICTL_CTLR, chan, key);
return;
case CTL1:
present = store_locate(mc->store, c, chan, key);
if ( !mc->accept_any_ctl_rpn ) {
if ( !present )
break;
val = store_extract(mc->store, c, chan, key);
if ( !(val&C1_SET) )
break;
}
store_update(mc->store, c, chan, key,
C1_SET | (ctlval[1]>63));
mc->notify(mc->cookie, MIDICTL_CTLR, chan, key);
return;
case RPN:
case NRPN:
return;
}
}
uint_fast16_t
midictl_read(midictl *mc, uint_fast8_t chan, uint_fast8_t ctlr,
uint_fast16_t dflt)
{
uint_fast16_t key, val;
class c;
_Bool islsb, present;
KASSERT(mutex_owned(mc->lock));
KASSERT(!mc->store->destroy);
key = ctlr;
c = classify(&key, &islsb);
switch ( c ) {
case CTL1:
present = store_locate(mc->store, c, chan, key);
if ( !present ||
!(C1_SET&(val = store_extract(mc->store, c, chan, key))) ) {
val = C1_SET | (dflt > 63);
store_update(mc->store, c, chan, key, val);
}
return (val & 1) ? 127 : 0;
case CTL7:
present = store_locate(mc->store, c, chan, key);
if ( !present ||
!(C7_SET&(val = store_extract(mc->store, c, chan, key))) ) {
val = C7_SET | (dflt & 0x7f);
store_update(mc->store, c, chan, key, val);
}
return val & 0x7f;
case CTL14:
KASSERT(!islsb);
return read14(mc, chan, c, key, dflt);
case RPN:
case NRPN:
break;
}
return 0;
}
uint_fast16_t
midictl_rpn_read(midictl *mc, uint_fast8_t chan, uint_fast16_t ctlr,
uint_fast16_t dflt)
{
KASSERT(mutex_owned(mc->lock));
KASSERT(!mc->store->destroy);
return read14(mc, chan, RPN, ctlr, dflt);
}
uint_fast16_t
midictl_nrpn_read(midictl *mc, uint_fast8_t chan, uint_fast16_t ctlr,
uint_fast16_t dflt)
{
KASSERT(mutex_owned(mc->lock));
KASSERT(!mc->store->destroy);
return read14(mc, chan, NRPN, ctlr, dflt);
}
static void
reset_all_controllers(midictl *mc, uint_fast8_t chan)
{
uint_fast16_t ctlr, key;
class c;
_Bool islsb, present;
KASSERT(mutex_owned(mc->lock));
for ( ctlr = 0 ; ; ++ ctlr ) {
switch ( ctlr ) {
case MIDI_CTRL_BANK_SELECT_MSB:
case MIDI_CTRL_CHANNEL_VOLUME_MSB:
case MIDI_CTRL_PAN_MSB:
continue;
case MIDI_CTRL_BANK_SELECT_LSB:
ctlr += 31;
continue;
case MIDI_CTRL_SOUND_VARIATION:
ctlr += 9;
continue;
case MIDI_CTRL_EFFECT_DEPTH_1:
goto loop_exit;
case MIDI_CTRL_DATA_ENTRY_MSB:
continue;
}
key = ctlr;
c = classify(&key, &islsb);
present = store_locate(mc->store, c, chan, key);
if ( !present )
continue;
store_update(mc->store, c, chan, key, 0);
}
loop_exit:
mc->notify(mc->cookie, MIDICTL_RESET, chan, 0);
}
static void
enter14(midictl *mc, uint_fast8_t chan, class c, uint_fast16_t key,
_Bool islsb, uint8_t val)
{
uint16_t stval;
_Bool present;
KASSERT(mutex_owned(mc->lock));
present = store_locate(mc->store, c, chan, key);
stval = (present) ? store_extract(mc->store, c, chan, key) : 0;
if ( !( stval & (C14MSET|C14LSET) ) ) {
if ( !((NRPN==c)? mc->accept_any_nrpn: mc->accept_any_ctl_rpn) )
return;
}
if ( islsb )
stval = C14LSET | val | ( stval & ~0x7f );
else
stval = C14MSET | ( val << 7 ) | ( stval & ~0x3f80 );
store_update(mc->store, c, chan, key, stval);
mc->notify(mc->cookie, CTL14 == c ? MIDICTL_CTLR
: RPN == c ? MIDICTL_RPN
: MIDICTL_NRPN, chan, key);
}
static uint_fast16_t
read14(midictl *mc, uint_fast8_t chan, class c, uint_fast16_t key,
uint_fast16_t dflt)
{
uint16_t val;
_Bool present;
KASSERT(mutex_owned(mc->lock));
present = store_locate(mc->store, c, chan, key);
if ( !present )
goto neitherset;
val = store_extract(mc->store, c, chan, key);
switch ( val & (C14MSET|C14LSET) ) {
case C14MSET|C14LSET:
return val & 0x3fff;
case C14MSET:
val = C14LSET | (val & ~0x7f) | (dflt & 0x7f);
break;
case C14LSET:
val = C14MSET | (val & ~0x3f8) | (dflt & 0x3f8);
break;
neitherset:
case 0:
val = C14MSET|C14LSET | (dflt & 0x3fff);
}
store_update(mc->store, c, chan, key, val);
return val & 0x3fff;
}
static class
classify(uint_fast16_t *key, _Bool *islsb) {
if ( *key < 32 ) {
*islsb = 0;
return CTL14;
} else if ( *key < 64 ) {
*islsb = 1;
*key -= 32;
return CTL14;
} else if ( *key < 70 ) {
return CTL1;
}
return CTL7;
}
static void
notify_no_one(void *cookie, midictl_evt evt,
uint_fast8_t chan, uint_fast16_t k)
{
}
#undef PN_SET
#undef C14MSET
#undef C14LSET
#undef C7_SET
#undef C1_SET
static void
store_thread(void *arg)
{
midictl_store *s;
s = arg;
mutex_enter(s->lock);
for (;;) {
if (s->destroy) {
mutex_exit(s->lock);
cv_destroy(&s->cv);
kmem_free(s->table, sizeof(*s->table)<<s->lgcapacity);
kmem_free(s, sizeof(*s));
kthread_exit(0);
} else if (NEED_REHASH(s)) {
store_rehash(s);
} else {
cv_wait(&s->cv, s->lock);
}
}
}
static _Bool
store_locate(midictl_store *s, class c, uint_fast8_t chan, uint_fast16_t key)
{
uint64_t mask;
KASSERT(mutex_owned(s->lock));
if ( s->used >= 1 << s->lgcapacity )
panic("%s: repeated attempts to expand table failed", __func__);
chan = packing[c] * (chan/packing[c]);
if ( CTL7 == c ) {
s->key = IS_USED | IS_CTL7 | (chan << 7) | key;
mask = 0xffff;
} else {
s->key = (c << 20) | (chan << 16) | IS_USED | key;
mask = 0x7fffff;
}
s->idx = store_idx(s->lgcapacity, s->table, s->key, mask);
if ( !(s->table[s->idx] & IS_USED) )
return 0;
return 1;
}
static uint16_t
store_extract(midictl_store *s, class c, uint_fast8_t chan,
uint_fast16_t key)
{
KASSERT(mutex_owned(s->lock));
chan %= packing[c];
switch ( c ) {
case CTL1:
return 3 & (s->table[s->idx]>>CTL1SHIFT(chan));
case CTL7:
return 0xff & (s->table[s->idx]>>CTL7SHIFT(chan));
case CTL14:
case RPN:
case NRPN:
break;
}
return 0xffff & (s->table[s->idx]>>CTLESHIFT(chan));
}
static void
store_update(midictl_store *s, class c, uint_fast8_t chan,
uint_fast16_t key, uint16_t value)
{
uint64_t orig;
KASSERT(mutex_owned(s->lock));
orig = s->table[s->idx];
if ( !(orig & IS_USED) ) {
orig = s->key;
++ s->used;
}
chan %= packing[c];
switch ( c ) {
case CTL1:
orig &= ~(((uint64_t)3)<<CTL1SHIFT(chan));
orig |= ((uint64_t)(3 & value)) << CTL1SHIFT(chan);
break;
case CTL7:
orig &= ~(((uint64_t)0xff)<<CTL7SHIFT(chan));
orig |= ((uint64_t)(0xff & value)) << CTL7SHIFT(chan);
break;
case CTL14:
case RPN:
case NRPN:
orig &= ~(((uint64_t)0xffff)<<CTLESHIFT(chan));
orig |= ((uint64_t)value) << CTLESHIFT(chan);
break;
}
s->table[s->idx] = orig;
if (NEED_REHASH(s))
cv_broadcast(&s->cv);
}
static uint32_t
store_idx(uint32_t lgcapacity, uint64_t *table,
uint64_t key, uint64_t mask)
{
uint32_t val;
uint32_t k, h1, h2;
int32_t idx;
k = key;
h1 = ((k * 0x61c88646) >> (32-lgcapacity)) & ((1<<lgcapacity) - 1);
h2 = ((k * 0x9e3779b9) >> (32-lgcapacity)) & ((1<<lgcapacity) - 1);
h2 |= 1;
for ( idx = h1 ;; idx -= h2 ) {
if ( idx < 0 )
idx += 1<<lgcapacity;
val = (uint32_t)(table[idx] & mask);
if ( val == k )
break;
if ( !(val & IS_USED) )
break;
}
return idx;
}
static void
store_rehash(midictl_store *s)
{
uint64_t *newtbl, *oldtbl, mask;
uint32_t oldlgcap, newlgcap, oidx, nidx;
KASSERT(mutex_owned(s->lock));
oldlgcap = s->lgcapacity;
newlgcap = oldlgcap + s->lgcapacity;
mutex_exit(s->lock);
newtbl = kmem_zalloc(sizeof(*newtbl) << newlgcap, KM_SLEEP);
mutex_enter(s->lock);
if (oldlgcap != s->lgcapacity) {
KASSERT(FALSE);
mutex_exit(s->lock);
kmem_free(newtbl, sizeof(*newtbl) << newlgcap);
mutex_enter(s->lock);
return;
}
for (oidx = 1 << s->lgcapacity ; oidx-- > 0 ; ) {
if (!(s->table[oidx] & IS_USED))
continue;
if (s->table[oidx] & IS_CTL7)
mask = 0xffff;
else
mask = 0x3fffff;
nidx = store_idx(newlgcap, newtbl,
s->table[oidx] & mask, mask);
newtbl[nidx] = s->table[oidx];
}
oldtbl = s->table;
s->table = newtbl;
s->lgcapacity = newlgcap;
mutex_exit(s->lock);
kmem_free(oldtbl, sizeof(*oldtbl) << oldlgcap);
mutex_enter(s->lock);
}