1
0
mirror of https://github.com/torvalds/linux.git synced 2025-04-11 04:53:02 +00:00
Alexis Lothoré 1bd2aad57d serial: mctrl_gpio: split disable_ms into sync and no_sync APIs
The following splat has been observed on a SAMA5D27 platform using
atmel_serial:

BUG: sleeping function called from invalid context at kernel/irq/manage.c:738
in_atomic(): 1, irqs_disabled(): 128, non_block: 0, pid: 27, name: kworker/u5:0
preempt_count: 1, expected: 0
INFO: lockdep is turned off.
irq event stamp: 0
hardirqs last  enabled at (0): [<00000000>] 0x0
hardirqs last disabled at (0): [<c01588f0>] copy_process+0x1c4c/0x7bec
softirqs last  enabled at (0): [<c0158944>] copy_process+0x1ca0/0x7bec
softirqs last disabled at (0): [<00000000>] 0x0
CPU: 0 UID: 0 PID: 27 Comm: kworker/u5:0 Not tainted 6.13.0-rc7+ 
Hardware name: Atmel SAMA5
Workqueue: hci0 hci_power_on [bluetooth]
Call trace:
  unwind_backtrace from show_stack+0x18/0x1c
  show_stack from dump_stack_lvl+0x44/0x70
  dump_stack_lvl from __might_resched+0x38c/0x598
  __might_resched from disable_irq+0x1c/0x48
  disable_irq from mctrl_gpio_disable_ms+0x74/0xc0
  mctrl_gpio_disable_ms from atmel_disable_ms.part.0+0x80/0x1f4
  atmel_disable_ms.part.0 from atmel_set_termios+0x764/0x11e8
  atmel_set_termios from uart_change_line_settings+0x15c/0x994
  uart_change_line_settings from uart_set_termios+0x2b0/0x668
  uart_set_termios from tty_set_termios+0x600/0x8ec
  tty_set_termios from ttyport_set_flow_control+0x188/0x1e0
  ttyport_set_flow_control from wilc_setup+0xd0/0x524 [hci_wilc]
  wilc_setup [hci_wilc] from hci_dev_open_sync+0x330/0x203c [bluetooth]
  hci_dev_open_sync [bluetooth] from hci_dev_do_open+0x40/0xb0 [bluetooth]
  hci_dev_do_open [bluetooth] from hci_power_on+0x12c/0x664 [bluetooth]
  hci_power_on [bluetooth] from process_one_work+0x998/0x1a38
  process_one_work from worker_thread+0x6e0/0xfb4
  worker_thread from kthread+0x3d4/0x484
  kthread from ret_from_fork+0x14/0x28

This warning is emitted when trying to toggle, at the highest level,
some flow control (with serdev_device_set_flow_control) in a device
driver. At the lowest level, the atmel_serial driver is using
serial_mctrl_gpio lib to enable/disable the corresponding IRQs
accordingly.  The warning emitted by CONFIG_DEBUG_ATOMIC_SLEEP is due to
disable_irq (called in mctrl_gpio_disable_ms) being possibly called in
some atomic context (some tty drivers perform modem lines configuration
in regions protected by port lock).

Split mctrl_gpio_disable_ms into two differents APIs, a non-blocking one
and a blocking one. Replace mctrl_gpio_disable_ms calls with the
relevant version depending on whether the call is protected by some port
lock.

Suggested-by: Jiri Slaby <jirislaby@kernel.org>
Signed-off-by: Alexis Lothoré <alexis.lothore@bootlin.com>
Acked-by: Richard Genoud <richard.genoud@bootlin.com>
Link: https://lore.kernel.org/r/20250217-atomic_sleep_mctrl_serial_gpio-v3-1-59324b313eef@bootlin.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2025-02-19 15:08:36 +01:00

384 lines
8.7 KiB
C

// SPDX-License-Identifier: GPL-2.0+
/*
* Helpers for controlling modem lines via GPIO
*
* Copyright (C) 2014 Paratronic S.A.
*/
#include <linux/err.h>
#include <linux/device.h>
#include <linux/irq.h>
#include <linux/gpio/consumer.h>
#include <linux/termios.h>
#include <linux/serial_core.h>
#include <linux/module.h>
#include <linux/property.h>
#include "serial_mctrl_gpio.h"
struct mctrl_gpios {
struct uart_port *port;
struct gpio_desc *gpio[UART_GPIO_MAX];
int irq[UART_GPIO_MAX];
unsigned int mctrl_prev;
bool mctrl_on;
};
static const struct {
const char *name;
unsigned int mctrl;
enum gpiod_flags flags;
} mctrl_gpios_desc[UART_GPIO_MAX] = {
{ "cts", TIOCM_CTS, GPIOD_IN, },
{ "dsr", TIOCM_DSR, GPIOD_IN, },
{ "dcd", TIOCM_CD, GPIOD_IN, },
{ "rng", TIOCM_RNG, GPIOD_IN, },
{ "rts", TIOCM_RTS, GPIOD_OUT_LOW, },
{ "dtr", TIOCM_DTR, GPIOD_OUT_LOW, },
};
static bool mctrl_gpio_flags_is_dir_out(unsigned int idx)
{
return mctrl_gpios_desc[idx].flags & GPIOD_FLAGS_BIT_DIR_OUT;
}
/**
* mctrl_gpio_set - set gpios according to mctrl state
* @gpios: gpios to set
* @mctrl: state to set
*
* Set the gpios according to the mctrl state.
*/
void mctrl_gpio_set(struct mctrl_gpios *gpios, unsigned int mctrl)
{
enum mctrl_gpio_idx i;
struct gpio_desc *desc_array[UART_GPIO_MAX];
DECLARE_BITMAP(values, UART_GPIO_MAX);
unsigned int count = 0;
if (gpios == NULL)
return;
for (i = 0; i < UART_GPIO_MAX; i++)
if (gpios->gpio[i] && mctrl_gpio_flags_is_dir_out(i)) {
desc_array[count] = gpios->gpio[i];
__assign_bit(count, values,
mctrl & mctrl_gpios_desc[i].mctrl);
count++;
}
gpiod_set_array_value(count, desc_array, NULL, values);
}
EXPORT_SYMBOL_GPL(mctrl_gpio_set);
/**
* mctrl_gpio_to_gpiod - obtain gpio_desc of modem line index
* @gpios: gpios to look into
* @gidx: index of the modem line
* Returns: the gpio_desc structure associated to the modem line index
*/
struct gpio_desc *mctrl_gpio_to_gpiod(struct mctrl_gpios *gpios,
enum mctrl_gpio_idx gidx)
{
if (gpios == NULL)
return NULL;
return gpios->gpio[gidx];
}
EXPORT_SYMBOL_GPL(mctrl_gpio_to_gpiod);
/**
* mctrl_gpio_get - update mctrl with the gpios values.
* @gpios: gpios to get the info from
* @mctrl: mctrl to set
* Returns: modified mctrl (the same value as in @mctrl)
*
* Update mctrl with the gpios values.
*/
unsigned int mctrl_gpio_get(struct mctrl_gpios *gpios, unsigned int *mctrl)
{
enum mctrl_gpio_idx i;
if (gpios == NULL)
return *mctrl;
for (i = 0; i < UART_GPIO_MAX; i++) {
if (gpios->gpio[i] && !mctrl_gpio_flags_is_dir_out(i)) {
if (gpiod_get_value(gpios->gpio[i]))
*mctrl |= mctrl_gpios_desc[i].mctrl;
else
*mctrl &= ~mctrl_gpios_desc[i].mctrl;
}
}
return *mctrl;
}
EXPORT_SYMBOL_GPL(mctrl_gpio_get);
unsigned int
mctrl_gpio_get_outputs(struct mctrl_gpios *gpios, unsigned int *mctrl)
{
enum mctrl_gpio_idx i;
if (gpios == NULL)
return *mctrl;
for (i = 0; i < UART_GPIO_MAX; i++) {
if (gpios->gpio[i] && mctrl_gpio_flags_is_dir_out(i)) {
if (gpiod_get_value(gpios->gpio[i]))
*mctrl |= mctrl_gpios_desc[i].mctrl;
else
*mctrl &= ~mctrl_gpios_desc[i].mctrl;
}
}
return *mctrl;
}
EXPORT_SYMBOL_GPL(mctrl_gpio_get_outputs);
struct mctrl_gpios *mctrl_gpio_init_noauto(struct device *dev, unsigned int idx)
{
struct mctrl_gpios *gpios;
enum mctrl_gpio_idx i;
gpios = devm_kzalloc(dev, sizeof(*gpios), GFP_KERNEL);
if (!gpios)
return ERR_PTR(-ENOMEM);
for (i = 0; i < UART_GPIO_MAX; i++) {
char *gpio_str;
bool present;
/* Check if GPIO property exists and continue if not */
gpio_str = kasprintf(GFP_KERNEL, "%s-gpios",
mctrl_gpios_desc[i].name);
if (!gpio_str)
continue;
present = device_property_present(dev, gpio_str);
kfree(gpio_str);
if (!present)
continue;
gpios->gpio[i] =
devm_gpiod_get_index_optional(dev,
mctrl_gpios_desc[i].name,
idx,
mctrl_gpios_desc[i].flags);
if (IS_ERR(gpios->gpio[i]))
return ERR_CAST(gpios->gpio[i]);
}
return gpios;
}
EXPORT_SYMBOL_GPL(mctrl_gpio_init_noauto);
#define MCTRL_ANY_DELTA (TIOCM_RI | TIOCM_DSR | TIOCM_CD | TIOCM_CTS)
static irqreturn_t mctrl_gpio_irq_handle(int irq, void *context)
{
struct mctrl_gpios *gpios = context;
struct uart_port *port = gpios->port;
u32 mctrl = gpios->mctrl_prev;
u32 mctrl_diff;
unsigned long flags;
mctrl_gpio_get(gpios, &mctrl);
uart_port_lock_irqsave(port, &flags);
mctrl_diff = mctrl ^ gpios->mctrl_prev;
gpios->mctrl_prev = mctrl;
if (mctrl_diff & MCTRL_ANY_DELTA && port->state != NULL) {
if ((mctrl_diff & mctrl) & TIOCM_RI)
port->icount.rng++;
if ((mctrl_diff & mctrl) & TIOCM_DSR)
port->icount.dsr++;
if (mctrl_diff & TIOCM_CD)
uart_handle_dcd_change(port, mctrl & TIOCM_CD);
if (mctrl_diff & TIOCM_CTS)
uart_handle_cts_change(port, mctrl & TIOCM_CTS);
wake_up_interruptible(&port->state->port.delta_msr_wait);
}
uart_port_unlock_irqrestore(port, flags);
return IRQ_HANDLED;
}
/**
* mctrl_gpio_init - initialize uart gpios
* @port: port to initialize gpios for
* @idx: index of the gpio in the @port's device
*
* This will get the {cts,rts,...}-gpios from device tree if they are present
* and request them, set direction etc, and return an allocated structure.
* `devm_*` functions are used, so there's no need to explicitly free.
* As this sets up the irq handling, make sure to not handle changes to the
* gpio input lines in your driver, too.
*/
struct mctrl_gpios *mctrl_gpio_init(struct uart_port *port, unsigned int idx)
{
struct mctrl_gpios *gpios;
enum mctrl_gpio_idx i;
gpios = mctrl_gpio_init_noauto(port->dev, idx);
if (IS_ERR(gpios))
return gpios;
gpios->port = port;
for (i = 0; i < UART_GPIO_MAX; ++i) {
int ret;
if (!gpios->gpio[i] || mctrl_gpio_flags_is_dir_out(i))
continue;
ret = gpiod_to_irq(gpios->gpio[i]);
if (ret < 0) {
dev_err(port->dev,
"failed to find corresponding irq for %s (idx=%d, err=%d)\n",
mctrl_gpios_desc[i].name, idx, ret);
return ERR_PTR(ret);
}
gpios->irq[i] = ret;
/* irqs should only be enabled in .enable_ms */
irq_set_status_flags(gpios->irq[i], IRQ_NOAUTOEN);
ret = devm_request_irq(port->dev, gpios->irq[i],
mctrl_gpio_irq_handle,
IRQ_TYPE_EDGE_BOTH, dev_name(port->dev),
gpios);
if (ret) {
/* alternatively implement polling */
dev_err(port->dev,
"failed to request irq for %s (idx=%d, err=%d)\n",
mctrl_gpios_desc[i].name, idx, ret);
return ERR_PTR(ret);
}
}
return gpios;
}
EXPORT_SYMBOL_GPL(mctrl_gpio_init);
/**
* mctrl_gpio_enable_ms - enable irqs and handling of changes to the ms lines
* @gpios: gpios to enable
*/
void mctrl_gpio_enable_ms(struct mctrl_gpios *gpios)
{
enum mctrl_gpio_idx i;
if (gpios == NULL)
return;
/* .enable_ms may be called multiple times */
if (gpios->mctrl_on)
return;
gpios->mctrl_on = true;
/* get initial status of modem lines GPIOs */
mctrl_gpio_get(gpios, &gpios->mctrl_prev);
for (i = 0; i < UART_GPIO_MAX; ++i) {
if (!gpios->irq[i])
continue;
enable_irq(gpios->irq[i]);
}
}
EXPORT_SYMBOL_GPL(mctrl_gpio_enable_ms);
static void mctrl_gpio_disable_ms(struct mctrl_gpios *gpios, bool sync)
{
enum mctrl_gpio_idx i;
if (gpios == NULL)
return;
if (!gpios->mctrl_on)
return;
gpios->mctrl_on = false;
for (i = 0; i < UART_GPIO_MAX; ++i) {
if (!gpios->irq[i])
continue;
if (sync)
disable_irq(gpios->irq[i]);
else
disable_irq_nosync(gpios->irq[i]);
}
}
/**
* mctrl_gpio_disable_ms_sync - disable irqs and handling of changes to the ms
* lines, and wait for any pending IRQ to be processed
* @gpios: gpios to disable
*/
void mctrl_gpio_disable_ms_sync(struct mctrl_gpios *gpios)
{
mctrl_gpio_disable_ms(gpios, true);
}
EXPORT_SYMBOL_GPL(mctrl_gpio_disable_ms_sync);
/**
* mctrl_gpio_disable_ms_no_sync - disable irqs and handling of changes to the
* ms lines, and return immediately
* @gpios: gpios to disable
*/
void mctrl_gpio_disable_ms_no_sync(struct mctrl_gpios *gpios)
{
mctrl_gpio_disable_ms(gpios, false);
}
EXPORT_SYMBOL_GPL(mctrl_gpio_disable_ms_no_sync);
void mctrl_gpio_enable_irq_wake(struct mctrl_gpios *gpios)
{
enum mctrl_gpio_idx i;
if (!gpios)
return;
if (!gpios->mctrl_on)
return;
for (i = 0; i < UART_GPIO_MAX; ++i) {
if (!gpios->irq[i])
continue;
enable_irq_wake(gpios->irq[i]);
}
}
EXPORT_SYMBOL_GPL(mctrl_gpio_enable_irq_wake);
void mctrl_gpio_disable_irq_wake(struct mctrl_gpios *gpios)
{
enum mctrl_gpio_idx i;
if (!gpios)
return;
if (!gpios->mctrl_on)
return;
for (i = 0; i < UART_GPIO_MAX; ++i) {
if (!gpios->irq[i])
continue;
disable_irq_wake(gpios->irq[i]);
}
}
EXPORT_SYMBOL_GPL(mctrl_gpio_disable_irq_wake);
MODULE_DESCRIPTION("Helpers for controlling modem lines via GPIO");
MODULE_LICENSE("GPL");