211 lines
4.7 KiB
C
211 lines
4.7 KiB
C
|
// SPDX-License-Identifier: GPL-2.0
|
||
|
/*
|
||
|
* A generic FSM based on fsm used in isdn4linux
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
#include "fsm.h"
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/slab.h>
|
||
|
#include <linux/timer.h>
|
||
|
|
||
|
MODULE_AUTHOR("(C) 2000 IBM Corp. by Fritz Elfert (felfert@millenux.com)");
|
||
|
MODULE_DESCRIPTION("Finite state machine helper functions");
|
||
|
MODULE_LICENSE("GPL");
|
||
|
|
||
|
fsm_instance *
|
||
|
init_fsm(char *name, const char **state_names, const char **event_names, int nr_states,
|
||
|
int nr_events, const fsm_node *tmpl, int tmpl_len, gfp_t order)
|
||
|
{
|
||
|
int i;
|
||
|
fsm_instance *this;
|
||
|
fsm_function_t *m;
|
||
|
fsm *f;
|
||
|
|
||
|
this = kzalloc(sizeof(fsm_instance), order);
|
||
|
if (this == NULL) {
|
||
|
printk(KERN_WARNING
|
||
|
"fsm(%s): init_fsm: Couldn't alloc instance\n", name);
|
||
|
return NULL;
|
||
|
}
|
||
|
strscpy(this->name, name, sizeof(this->name));
|
||
|
init_waitqueue_head(&this->wait_q);
|
||
|
|
||
|
f = kzalloc(sizeof(fsm), order);
|
||
|
if (f == NULL) {
|
||
|
printk(KERN_WARNING
|
||
|
"fsm(%s): init_fsm: Couldn't alloc fsm\n", name);
|
||
|
kfree_fsm(this);
|
||
|
return NULL;
|
||
|
}
|
||
|
f->nr_events = nr_events;
|
||
|
f->nr_states = nr_states;
|
||
|
f->event_names = event_names;
|
||
|
f->state_names = state_names;
|
||
|
this->f = f;
|
||
|
|
||
|
m = kcalloc(nr_states*nr_events, sizeof(fsm_function_t), order);
|
||
|
if (m == NULL) {
|
||
|
printk(KERN_WARNING
|
||
|
"fsm(%s): init_fsm: Couldn't alloc jumptable\n", name);
|
||
|
kfree_fsm(this);
|
||
|
return NULL;
|
||
|
}
|
||
|
f->jumpmatrix = m;
|
||
|
|
||
|
for (i = 0; i < tmpl_len; i++) {
|
||
|
if ((tmpl[i].cond_state >= nr_states) ||
|
||
|
(tmpl[i].cond_event >= nr_events) ) {
|
||
|
printk(KERN_ERR
|
||
|
"fsm(%s): init_fsm: Bad template l=%d st(%ld/%ld) ev(%ld/%ld)\n",
|
||
|
name, i, (long)tmpl[i].cond_state, (long)f->nr_states,
|
||
|
(long)tmpl[i].cond_event, (long)f->nr_events);
|
||
|
kfree_fsm(this);
|
||
|
return NULL;
|
||
|
} else
|
||
|
m[nr_states * tmpl[i].cond_event + tmpl[i].cond_state] =
|
||
|
tmpl[i].function;
|
||
|
}
|
||
|
return this;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
kfree_fsm(fsm_instance *this)
|
||
|
{
|
||
|
if (this) {
|
||
|
if (this->f) {
|
||
|
kfree(this->f->jumpmatrix);
|
||
|
kfree(this->f);
|
||
|
}
|
||
|
kfree(this);
|
||
|
} else
|
||
|
printk(KERN_WARNING
|
||
|
"fsm: kfree_fsm called with NULL argument\n");
|
||
|
}
|
||
|
|
||
|
#if FSM_DEBUG_HISTORY
|
||
|
void
|
||
|
fsm_print_history(fsm_instance *fi)
|
||
|
{
|
||
|
int idx = 0;
|
||
|
int i;
|
||
|
|
||
|
if (fi->history_size >= FSM_HISTORY_SIZE)
|
||
|
idx = fi->history_index;
|
||
|
|
||
|
printk(KERN_DEBUG "fsm(%s): History:\n", fi->name);
|
||
|
for (i = 0; i < fi->history_size; i++) {
|
||
|
int e = fi->history[idx].event;
|
||
|
int s = fi->history[idx++].state;
|
||
|
idx %= FSM_HISTORY_SIZE;
|
||
|
if (e == -1)
|
||
|
printk(KERN_DEBUG " S=%s\n",
|
||
|
fi->f->state_names[s]);
|
||
|
else
|
||
|
printk(KERN_DEBUG " S=%s E=%s\n",
|
||
|
fi->f->state_names[s],
|
||
|
fi->f->event_names[e]);
|
||
|
}
|
||
|
fi->history_size = fi->history_index = 0;
|
||
|
}
|
||
|
|
||
|
void
|
||
|
fsm_record_history(fsm_instance *fi, int state, int event)
|
||
|
{
|
||
|
fi->history[fi->history_index].state = state;
|
||
|
fi->history[fi->history_index++].event = event;
|
||
|
fi->history_index %= FSM_HISTORY_SIZE;
|
||
|
if (fi->history_size < FSM_HISTORY_SIZE)
|
||
|
fi->history_size++;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
const char *
|
||
|
fsm_getstate_str(fsm_instance *fi)
|
||
|
{
|
||
|
int st = atomic_read(&fi->state);
|
||
|
if (st >= fi->f->nr_states)
|
||
|
return "Invalid";
|
||
|
return fi->f->state_names[st];
|
||
|
}
|
||
|
|
||
|
static void
|
||
|
fsm_expire_timer(struct timer_list *t)
|
||
|
{
|
||
|
fsm_timer *this = from_timer(this, t, tl);
|
||
|
#if FSM_TIMER_DEBUG
|
||
|
printk(KERN_DEBUG "fsm(%s): Timer %p expired\n",
|
||
|
this->fi->name, this);
|
||
|
#endif
|
||
|
fsm_event(this->fi, this->expire_event, this->event_arg);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
fsm_settimer(fsm_instance *fi, fsm_timer *this)
|
||
|
{
|
||
|
this->fi = fi;
|
||
|
#if FSM_TIMER_DEBUG
|
||
|
printk(KERN_DEBUG "fsm(%s): Create timer %p\n", fi->name,
|
||
|
this);
|
||
|
#endif
|
||
|
timer_setup(&this->tl, fsm_expire_timer, 0);
|
||
|
}
|
||
|
|
||
|
void
|
||
|
fsm_deltimer(fsm_timer *this)
|
||
|
{
|
||
|
#if FSM_TIMER_DEBUG
|
||
|
printk(KERN_DEBUG "fsm(%s): Delete timer %p\n", this->fi->name,
|
||
|
this);
|
||
|
#endif
|
||
|
del_timer(&this->tl);
|
||
|
}
|
||
|
|
||
|
int
|
||
|
fsm_addtimer(fsm_timer *this, int millisec, int event, void *arg)
|
||
|
{
|
||
|
|
||
|
#if FSM_TIMER_DEBUG
|
||
|
printk(KERN_DEBUG "fsm(%s): Add timer %p %dms\n",
|
||
|
this->fi->name, this, millisec);
|
||
|
#endif
|
||
|
|
||
|
timer_setup(&this->tl, fsm_expire_timer, 0);
|
||
|
this->expire_event = event;
|
||
|
this->event_arg = arg;
|
||
|
this->tl.expires = jiffies + (millisec * HZ) / 1000;
|
||
|
add_timer(&this->tl);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
/* FIXME: this function is never used, why */
|
||
|
void
|
||
|
fsm_modtimer(fsm_timer *this, int millisec, int event, void *arg)
|
||
|
{
|
||
|
|
||
|
#if FSM_TIMER_DEBUG
|
||
|
printk(KERN_DEBUG "fsm(%s): Restart timer %p %dms\n",
|
||
|
this->fi->name, this, millisec);
|
||
|
#endif
|
||
|
|
||
|
del_timer(&this->tl);
|
||
|
timer_setup(&this->tl, fsm_expire_timer, 0);
|
||
|
this->expire_event = event;
|
||
|
this->event_arg = arg;
|
||
|
this->tl.expires = jiffies + (millisec * HZ) / 1000;
|
||
|
add_timer(&this->tl);
|
||
|
}
|
||
|
|
||
|
EXPORT_SYMBOL(init_fsm);
|
||
|
EXPORT_SYMBOL(kfree_fsm);
|
||
|
EXPORT_SYMBOL(fsm_settimer);
|
||
|
EXPORT_SYMBOL(fsm_deltimer);
|
||
|
EXPORT_SYMBOL(fsm_addtimer);
|
||
|
EXPORT_SYMBOL(fsm_modtimer);
|
||
|
EXPORT_SYMBOL(fsm_getstate_str);
|
||
|
|
||
|
#if FSM_DEBUG_HISTORY
|
||
|
EXPORT_SYMBOL(fsm_print_history);
|
||
|
EXPORT_SYMBOL(fsm_record_history);
|
||
|
#endif
|