Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix gpio-fsm race condition #5596

Merged
merged 2 commits into from
Sep 8, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
305 changes: 154 additions & 151 deletions drivers/gpio/gpio-fsm.c
Original file line number Diff line number Diff line change
Expand Up @@ -193,131 +193,11 @@ static void free_symbols(struct symtab_entry **symtab)
}
}

static int gpio_fsm_get_direction(struct gpio_chip *gc, unsigned int off)
{
struct gpio_fsm *gf = gpiochip_get_data(gc);
struct soft_gpio *sg;

if (off >= gf->num_soft_gpios)
return -EINVAL;
sg = &gf->soft_gpios[off];

return sg->dir;
}

static int gpio_fsm_get(struct gpio_chip *gc, unsigned int off)
{
struct gpio_fsm *gf = gpiochip_get_data(gc);
struct soft_gpio *sg;

if (off >= gf->num_soft_gpios)
return -EINVAL;
sg = &gf->soft_gpios[off];

return sg->value;
}

static void gpio_fsm_go_to_state(struct gpio_fsm *gf,
struct fsm_state *new_state)
{
struct input_gpio_state *inp_state;
struct gpio_event *gp_ev;
struct fsm_state *state;
int i;

dev_dbg(gf->dev, "go_to_state(%s)\n",
new_state ? new_state->name : "<unset>");

spin_lock(&gf->spinlock);

if (gf->next_state) {
/* Something else has already requested a transition */
spin_unlock(&gf->spinlock);
return;
}

gf->next_state = new_state;
state = gf->current_state;
gf->delay_target_state = NULL;

if (state) {
/* Disarm any GPIO IRQs */
for (i = 0; i < state->num_gpio_events; i++) {
gp_ev = &state->gpio_events[i];
inp_state = &gf->input_gpio_states[gp_ev->index];
inp_state->target = NULL;
}
}

spin_unlock(&gf->spinlock);

if (new_state)
schedule_work(&gf->work);
}

static void gpio_fsm_set_soft(struct gpio_fsm *gf,
unsigned int off, int val)
{
struct soft_gpio *sg = &gf->soft_gpios[off];
struct gpio_event *gp_ev;
struct fsm_state *state;
int i;

dev_dbg(gf->dev, "set(%d,%d)\n", off, val);
state = gf->current_state;
sg->value = val;
for (i = 0; i < state->num_soft_events; i++) {
gp_ev = &state->soft_events[i];
if (gp_ev->index == off && gp_ev->value == val) {
if (gf->debug)
dev_info(gf->dev,
"GF_SOFT %d->%d -> %s\n", gp_ev->index,
gp_ev->value, gp_ev->target->name);
gpio_fsm_go_to_state(gf, gp_ev->target);
break;
}
}
}

static int gpio_fsm_direction_input(struct gpio_chip *gc, unsigned int off)
{
struct gpio_fsm *gf = gpiochip_get_data(gc);
struct soft_gpio *sg;

if (off >= gf->num_soft_gpios)
return -EINVAL;
sg = &gf->soft_gpios[off];
sg->dir = GPIOF_DIR_IN;

return 0;
}

static int gpio_fsm_direction_output(struct gpio_chip *gc, unsigned int off,
int value)
{
struct gpio_fsm *gf = gpiochip_get_data(gc);
struct soft_gpio *sg;

if (off >= gf->num_soft_gpios)
return -EINVAL;
sg = &gf->soft_gpios[off];
sg->dir = GPIOF_DIR_OUT;
gpio_fsm_set_soft(gf, off, value);

return 0;
}

static void gpio_fsm_set(struct gpio_chip *gc, unsigned int off, int val)
{
struct gpio_fsm *gf;

gf = gpiochip_get_data(gc);
if (off < gf->num_soft_gpios)
gpio_fsm_set_soft(gf, off, val);
}
unsigned int off, int val);

static void gpio_fsm_enter_state(struct gpio_fsm *gf,
struct fsm_state *state)
struct fsm_state *state)
{
struct input_gpio_state *inp_state;
struct output_signal *signal;
Expand All @@ -330,6 +210,7 @@ static void gpio_fsm_enter_state(struct gpio_fsm *gf,
dev_dbg(gf->dev, "enter_state(%s)\n", state->name);

gf->current_state = state;
gf->delay_target_state = NULL;

// 1. Apply any listed signals
for (i = 0; i < state->num_signals; i++) {
Expand Down Expand Up @@ -388,7 +269,7 @@ static void gpio_fsm_enter_state(struct gpio_fsm *gf,
dev_info(gf->dev,
"GF_SOFT %d=%d -> %s\n", event->index,
event->value, event->target->name);
gpio_fsm_go_to_state(gf, event->target);
gpio_fsm_enter_state(gf, event->target);
return;
}
}
Expand All @@ -401,7 +282,7 @@ static void gpio_fsm_enter_state(struct gpio_fsm *gf,
inp_state->value = event->value;
inp_state->enabled = true;

value = gpiod_get_value(gf->input_gpios->desc[event->index]);
value = gpiod_get_value_cansleep(gf->input_gpios->desc[event->index]);

// Clear stale event state
disable_irq(inp_state->irq);
Expand All @@ -416,7 +297,7 @@ static void gpio_fsm_enter_state(struct gpio_fsm *gf,
dev_info(gf->dev,
"GF_IN %d=%d -> %s\n", event->index,
event->value, event->target->name);
gpio_fsm_go_to_state(gf, event->target);
gpio_fsm_enter_state(gf, event->target);
return;
}
}
Expand All @@ -431,40 +312,79 @@ static void gpio_fsm_enter_state(struct gpio_fsm *gf,
}
}

static void gpio_fsm_work(struct work_struct *work)
static void gpio_fsm_go_to_state(struct gpio_fsm *gf,
struct fsm_state *new_state)
{
struct input_gpio_state *inp_state;
struct fsm_state *new_state;
struct gpio_event *gp_ev;
struct fsm_state *state;
int i;

dev_dbg(gf->dev, "go_to_state(%s)\n",
new_state ? new_state->name : "<unset>");

state = gf->current_state;

/* Disable any enabled GPIO IRQs */
for (i = 0; i < state->num_gpio_events; i++) {
gp_ev = &state->gpio_events[i];
inp_state = &gf->input_gpio_states[gp_ev->index];
if (inp_state->enabled) {
inp_state->enabled = false;
irq_set_irq_type(inp_state->irq,
IRQF_TRIGGER_NONE);
}
}

gpio_fsm_enter_state(gf, new_state);
}

static void gpio_fsm_go_to_state_deferred(struct gpio_fsm *gf,
struct fsm_state *new_state)
{
struct input_gpio_state *inp_state;
struct gpio_event *gp_ev;
struct gpio_fsm *gf;
struct fsm_state *state;
int i;

gf = container_of(work, struct gpio_fsm, work);
dev_dbg(gf->dev, "go_to_state_deferred(%s)\n",
new_state ? new_state->name : "<unset>");

spin_lock(&gf->spinlock);

if (gf->next_state) {
/* Something else has already requested a transition */
spin_unlock(&gf->spinlock);
return;
}

gf->next_state = new_state;
state = gf->current_state;

/* Disarm any GPIO IRQs */
for (i = 0; i < state->num_gpio_events; i++) {
gp_ev = &state->gpio_events[i];
inp_state = &gf->input_gpio_states[gp_ev->index];
inp_state->target = NULL;
}

spin_unlock(&gf->spinlock);

schedule_work(&gf->work);
}

static void gpio_fsm_work(struct work_struct *work)
{
struct fsm_state *new_state;
struct gpio_fsm *gf;

gf = container_of(work, struct gpio_fsm, work);
spin_lock(&gf->spinlock);
new_state = gf->next_state;
if (!new_state)
new_state = gf->delay_target_state;
gf->next_state = NULL;
gf->delay_target_state = NULL;
spin_unlock(&gf->spinlock);

if (state) {
/* Disable any enabled GPIO IRQs */
for (i = 0; i < state->num_gpio_events; i++) {
gp_ev = &state->gpio_events[i];
inp_state = &gf->input_gpio_states[gp_ev->index];
if (inp_state->enabled) {
inp_state->enabled = false;
irq_set_irq_type(inp_state->irq,
IRQF_TRIGGER_NONE);
}
}
}

if (new_state)
gpio_fsm_enter_state(gf, new_state);
gpio_fsm_go_to_state(gf, new_state);
}

static irqreturn_t gpio_fsm_gpio_irq_handler(int irq, void *dev_id)
Expand All @@ -483,7 +403,7 @@ static irqreturn_t gpio_fsm_gpio_irq_handler(int irq, void *dev_id)
if (gf->debug)
dev_info(gf->dev, "GF_IN %d->%d -> %s\n",
inp_state->index, inp_state->value, target->name);
gpio_fsm_go_to_state(gf, target);
gpio_fsm_go_to_state_deferred(gf, target);
return IRQ_HANDLED;
}

Expand All @@ -495,12 +415,11 @@ static void gpio_fsm_timer(struct timer_list *timer)
target = gf->delay_target_state;
if (!target)
return;

if (gf->debug)
dev_info(gf->dev, "GF_DELAY %d -> %s\n", gf->delay_ms,
target->name);

gpio_fsm_go_to_state(gf, target);
gpio_fsm_go_to_state_deferred(gf, target);
}

int gpio_fsm_parse_signals(struct gpio_fsm *gf, struct fsm_state *state,
Expand Down Expand Up @@ -851,6 +770,90 @@ static int resolve_sym_to_state(struct gpio_fsm *gf, struct fsm_state **pstate)
return 0;
}

static void gpio_fsm_set_soft(struct gpio_fsm *gf,
unsigned int off, int val)
{
struct soft_gpio *sg = &gf->soft_gpios[off];
struct gpio_event *gp_ev;
struct fsm_state *state;
int i;

dev_dbg(gf->dev, "set(%d,%d)\n", off, val);
state = gf->current_state;
sg->value = val;
for (i = 0; i < state->num_soft_events; i++) {
gp_ev = &state->soft_events[i];
if (gp_ev->index == off && gp_ev->value == val) {
if (gf->debug)
dev_info(gf->dev,
"GF_SOFT %d->%d -> %s\n", gp_ev->index,
gp_ev->value, gp_ev->target->name);
gpio_fsm_go_to_state(gf, gp_ev->target);
break;
}
}
}

static int gpio_fsm_get(struct gpio_chip *gc, unsigned int off)
{
struct gpio_fsm *gf = gpiochip_get_data(gc);
struct soft_gpio *sg;

if (off >= gf->num_soft_gpios)
return -EINVAL;
sg = &gf->soft_gpios[off];

return sg->value;
}

static void gpio_fsm_set(struct gpio_chip *gc, unsigned int off, int val)
{
struct gpio_fsm *gf;

gf = gpiochip_get_data(gc);
if (off < gf->num_soft_gpios)
gpio_fsm_set_soft(gf, off, val);
}

static int gpio_fsm_get_direction(struct gpio_chip *gc, unsigned int off)
{
struct gpio_fsm *gf = gpiochip_get_data(gc);
struct soft_gpio *sg;

if (off >= gf->num_soft_gpios)
return -EINVAL;
sg = &gf->soft_gpios[off];

return sg->dir;
}

static int gpio_fsm_direction_input(struct gpio_chip *gc, unsigned int off)
{
struct gpio_fsm *gf = gpiochip_get_data(gc);
struct soft_gpio *sg;

if (off >= gf->num_soft_gpios)
return -EINVAL;
sg = &gf->soft_gpios[off];
sg->dir = GPIOF_DIR_IN;

return 0;
}

static int gpio_fsm_direction_output(struct gpio_chip *gc, unsigned int off,
int value)
{
struct gpio_fsm *gf = gpiochip_get_data(gc);
struct soft_gpio *sg;

if (off >= gf->num_soft_gpios)
return -EINVAL;
sg = &gf->soft_gpios[off];
sg->dir = GPIOF_DIR_OUT;
gpio_fsm_set_soft(gf, off, value);

return 0;
}

/*
* /sys/class/gpio-fsm/<fsm-name>/
Expand Down Expand Up @@ -1114,7 +1117,7 @@ static int gpio_fsm_probe(struct platform_device *pdev)
if (gf->debug)
dev_info(gf->dev, "Start -> %s\n", gf->start_state->name);

gpio_fsm_go_to_state(gf, gf->start_state);
gpio_fsm_enter_state(gf, gf->start_state);

return devm_gpiochip_add_data(dev, &gf->gc, gf);
}
Expand Down