USB: serial: f81232: fix interrupt worker not stop
Subsystems (2)
PATCH v9 1/4 USB: serial: f81232: fix interrupt worker not stop
The F81232 will use interrupt worker to handle MSR change. This patch will fix the issue that interrupt work should stop in close() and suspend().
---
v9:
1: change the sequence from patch index from 2 to 1. 2: using tty_port_initialized() to determinate port open state.
v8:
1: no change
v7:
1: first introduce to this series patch.
drivers/usb/serial/f81232.c | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+)
drivers/usb/serial/f81232.c +39 -0
@@ -556,9 +556,12 @@ static int f81232_open(struct tty_struct *tty, struct usb_serial_port *port)static void f81232_close (struct usb_serial_port *port) { +struct f81232_private *port_priv =usb_get_serial_port_data (port); +f81232_port_disable (port);usb_serial_generic_close (port);usb_kill_urb (port->interrupt_in_urb); +flush_work (&port_priv->interrupt_work); }static void f81232_dtr_rts (struct usb_serial_port *port,int on)
@@ -632,6 +635,40 @@ static int f81232_port_remove(struct usb_serial_port *port)return 0 ; } +static int f81232_suspend (struct usb_serial *serial,pm_message_t message) +{ +struct usb_serial_port *port = serial->port[0 ]; +struct f81232_private *port_priv =usb_get_serial_port_data (port); +int i; + +for (i =0 ; i <ARRAY_SIZE (port->read_urbs); ++i) +usb_kill_urb (port->read_urbs[i]); + +usb_kill_urb (port->interrupt_in_urb); + +if (port_priv) +flush_work (&port_priv->interrupt_work); + +return 0 ; +} + +static int f81232_resume (struct usb_serial *serial) +{ +struct usb_serial_port *port = serial->port[0 ]; +int result; + +if (tty_port_initialized (&port->port)) { + result =usb_submit_urb (port->interrupt_in_urb,GFP_NOIO ); +if (result) { +dev_err (&port->dev,"submit interrupt urb failed: %d" , + result); +return result; + } + } + +return usb_serial_generic_resume (serial); +} +static struct usb_serial_driver f81232_device = { .driver = { .owner =THIS_MODULE ,
@@ -655,6 +692,8 @@ static struct usb_serial_driver f81232_device = { .read_int_callback = f81232_read_int_callback, .port_probe = f81232_port_probe, .port_remove = f81232_port_remove, + .suspend = f81232_suspend, + .resume = f81232_resume, };static struct usb_serial_driver *const serial_drivers[] = {
PATCH v9 2/4 USB: serial: f81232: clear overrun flag
The F81232 will report data and LSR with bulk like following format:
bulk-in data: [LSR(1Byte)+DATA(1Byte)][LSR(1Byte)+DATA(1Byte)]...
LSR will auto clear frame/parity/break error flag when reading by H/W, but overrrun will only cleared when reading LSR. So this patch add a worker to read LSR when overrun and flush the worker on close() & suspend().
---
v9:
1: change the patch index from 1 to 2.
v8:
1: Fix usb_submit_urb() in f81232_resume() with GFP_NOIO, not GFP_KERNEL.
v7:
1: Remove serial->suspending check
2: Add port priv is_port_open to save port open state. We'll
register interrupt-in urb in f81232_resume() when port is opened.
3: Using usb_kill_urb for read/interrupt urb to ensure urb
stopped.
v6:
1: Add deferred_lsr_work_needed to re-trigger when f81232_resume()
v5:
1: Source code base revert to v3 and remove all v4 changes. 2: Add serial->suspending check in f81232_process_read_urb() before schedule_work(&priv->lsr_work) to avoid race condition.
v4:
1: Add serial->suspending check in f81232_lsr_worker() to avoid re-trigger 2: Add port_priv-lsr_work_resched to re-trigger LSR worker
v3:
1: Add flush_work(&port_priv->lsr_work) in f81232_suspend().
v2:
1: Add flush_work(&port_priv->lsr_work) in f81232_close().
drivers/usb/serial/f81232.c | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-)
drivers/usb/serial/f81232.c +24 -1
@@ -41,12 +41,14 @@ MODULE_DEVICE_TABLE(usb, id_table);#define FIFO_CONTROL_REGISTER (0x02 + SERIAL_BASE_ADDRESS)#define LINE_CONTROL_REGISTER (0x03 + SERIAL_BASE_ADDRESS)#define MODEM_CONTROL_REGISTER (0x04 + SERIAL_BASE_ADDRESS) +#define LINE_STATUS_REGISTER (0x05 + SERIAL_BASE_ADDRESS)#define MODEM_STATUS_REGISTER (0x06 + SERIAL_BASE_ADDRESS)struct f81232_private {struct mutex lock;u8 modem_control;u8 modem_status; +struct work_struct lsr_work;struct work_struct interrupt_work;struct usb_serial_port *port; };
@@ -282,6 +284,7 @@ static void f81232_read_int_callback(struct urb *urb)static void f81232_process_read_urb (struct urb *urb) {struct usb_serial_port *port = urb->context; +struct f81232_private *priv =usb_get_serial_port_data (port);unsigned char *data = urb->transfer_buffer;char tty_flag;unsigned int i;
@@ -315,6 +318,7 @@ static void f81232_process_read_urb(struct urb *urb)if (lsr &UART_LSR_OE ) { port->icount.overrun++; +schedule_work (&priv->lsr_work);tty_insert_flip_char (&port->port,0 ,TTY_OVERRUN ); }
@@ -562,6 +566,7 @@ static void f81232_close(struct usb_serial_port *port)usb_serial_generic_close (port);usb_kill_urb (port->interrupt_in_urb);flush_work (&port_priv->interrupt_work); +flush_work (&port_priv->lsr_work); }static void f81232_dtr_rts (struct usb_serial_port *port,int on)
@@ -606,6 +611,21 @@ static void f81232_interrupt_work(struct work_struct *work)f81232_read_msr (priv->port); } +static void f81232_lsr_worker (struct work_struct *work) +{ +struct f81232_private *priv; +struct usb_serial_port *port; +int status; +u8 tmp; + + priv = container_of(work, struct f81232_private, lsr_work); + port = priv->port; + + status =f81232_get_register (port,LINE_STATUS_REGISTER , &tmp); +if (status) +dev_warn (&port->dev,"read LSR failed: %d\n" , status); +} +static int f81232_port_probe (struct usb_serial_port *port) {struct f81232_private *priv;
@@ -616,6 +636,7 @@ static int f81232_port_probe(struct usb_serial_port *port)mutex_init (&priv->lock);INIT_WORK (&priv->interrupt_work, f81232_interrupt_work); +INIT_WORK (&priv->lsr_work, f81232_lsr_worker);usb_set_serial_port_data (port, priv);
@@ -646,8 +667,10 @@ static int f81232_suspend(struct usb_serial *serial, pm_message_t message)usb_kill_urb (port->interrupt_in_urb); -if (port_priv) +if (port_priv) {flush_work (&port_priv->interrupt_work); +flush_work (&port_priv->lsr_work); + }return 0 ; }
PATCH v9 3/4 USB: serial: f81232: add high baud rate support
In reply to Ji-Ze Hong (Peter Hong)The F81232 had 4 clocksource 1.846/18.46/14.77/24MHz and baud rates can be up to 1.5Mbits with 24MHz.
F81232 Clock registers (106h)
Bit1-0: Clock source selector
00: 1.846MHz.
01: 18.46MHz.
10: 24MHz.
11: 14.77MHz.---
v9:
1: no change
v8:
1: no change
v7:
1: no change
v6:
1: no change
v5:
1: no change
v4:
1: no change
v3:
1: change the baud_base in f81232_get_serial_info()
drivers/usb/serial/f81232.c | 105 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 94 insertions(+), 11 deletions(-)
drivers/usb/serial/f81232.c +94 -11
@@ -28,7 +28,8 @@ static const struct usb_device_id id_table[] = {MODULE_DEVICE_TABLE (usb, id_table);/* Maximum baudrate for F81232 */ -#define F81232_MAX_BAUDRATE 115200 +#define F81232_MAX_BAUDRATE 1500000 +#define F81232_DEF_BAUDRATE 9600/* USB Control EP parameter */ #define F81232_REGISTER_REQUEST 0xa0
@@ -44,18 +45,42 @@ MODULE_DEVICE_TABLE(usb, id_table);#define LINE_STATUS_REGISTER (0x05 + SERIAL_BASE_ADDRESS)#define MODEM_STATUS_REGISTER (0x06 + SERIAL_BASE_ADDRESS) +/* +* F81232 Clock registers (106h) +* +* Bit1-0: Clock source selector +* 00: 1.846MHz. +* 01: 18.46MHz. +* 10: 24MHz. +* 11: 14.77MHz. +*/ +#define F81232_CLK_REGISTER 0x106 +#define F81232_CLK_1_846_MHZ 0 +#define F81232_CLK_18_46_MHZ BIT(0) +#define F81232_CLK_24_MHZ BIT(1) +#define F81232_CLK_14_77_MHZ (BIT(1) | BIT(0)) +#define F81232_CLK_MASK GENMASK(1, 0) +struct f81232_private {struct mutex lock;u8 modem_control;u8 modem_status; +speed_t baud_base;struct work_struct lsr_work;struct work_struct interrupt_work;struct usb_serial_port *port; }; -static int calc_baud_divisor (speed_t baudrate) +static u32 const baudrate_table[] = {115200 ,921600 ,1152000 ,1500000 }; +static u8 const clock_table[] = {F81232_CLK_1_846_MHZ ,F81232_CLK_14_77_MHZ , +F81232_CLK_18_46_MHZ ,F81232_CLK_24_MHZ }; + +static int calc_baud_divisor (speed_t baudrate,speed_t clockrate) { -return DIV_ROUND_CLOSEST (F81232_MAX_BAUDRATE , baudrate); +if (!baudrate) +return 0 ; + +return DIV_ROUND_CLOSEST (clockrate, baudrate); }static int f81232_get_register (struct usb_serial_port *port,u16 reg,u8 *val)
@@ -129,6 +154,21 @@ static int f81232_set_register(struct usb_serial_port *port, u16 reg, u8 val)return status; } +static int f81232_set_mask_register (struct usb_serial_port *port,u16 reg, +u8 mask,u8 val) +{ +int status; +u8 tmp; + + status =f81232_get_register (port, reg, &tmp); +if (status) +return status; + + tmp = (tmp & ~mask) | (val & mask); + +return f81232_set_register (port, reg, tmp); +} +static void f81232_read_msr (struct usb_serial_port *port) {int status;
@@ -346,13 +386,53 @@ static void f81232_break_ctl(struct tty_struct *tty, int break_state)*/ } -static void f81232_set_baudrate (struct usb_serial_port *port,speed_t baudrate) +static int f81232_find_clk (speed_t baudrate) +{ +int idx; + +for (idx =0 ; idx <ARRAY_SIZE (baudrate_table); ++idx) { +if (baudrate <= baudrate_table[idx] && + baudrate_table[idx] % baudrate ==0 ) +return idx; + } + +return -EINVAL ; +} + +static void f81232_set_baudrate (struct tty_struct *tty, +struct usb_serial_port *port,speed_t baudrate, +speed_t old_baudrate) { +struct f81232_private *priv =usb_get_serial_port_data (port);u8 lcr;int divisor;int status =0 ; +int i; +int idx; +speed_t baud_list[] = {baudrate, old_baudrate,F81232_DEF_BAUDRATE }; + +for (i =0 ; i <ARRAY_SIZE (baud_list); ++i) { + idx =f81232_find_clk (baud_list[i]); +if (idx >=0 ) { + baudrate = baud_list[i]; +tty_encode_baud_rate (tty, baudrate, baudrate); +break ; + } + } + +if (idx <0 ) +return ; - divisor =calc_baud_divisor (baudrate); + priv->baud_base = baudrate_table[idx]; + divisor =calc_baud_divisor (baudrate, priv->baud_base); + + status =f81232_set_mask_register (port,F81232_CLK_REGISTER , +F81232_CLK_MASK , clock_table[idx]); +if (status) { +dev_err (&port->dev,"%s failed to set CLK_REG: %d\n" , + __func__, status); +return ; + } status =f81232_get_register (port,LINE_CONTROL_REGISTER , &lcr);/* get LCR */
@@ -442,6 +522,7 @@ static void f81232_set_termios(struct tty_struct *tty,u8 new_lcr =0 ;int status =0 ;speed_t baudrate; +speed_t old_baud;/* Don't change anything if nothing has changed */ if (old_termios && !tty_termios_hw_change (&tty->termios, old_termios))
@@ -454,11 +535,12 @@ static void f81232_set_termios(struct tty_struct *tty, baudrate =tty_get_baud_rate (tty);if (baudrate >0 ) { -if (baudrate >F81232_MAX_BAUDRATE ) { - baudrate =F81232_MAX_BAUDRATE ; -tty_encode_baud_rate (tty, baudrate, baudrate); - } -f81232_set_baudrate (port, baudrate); +if (old_termios) + old_baud =tty_termios_baud_rate (old_termios); +else + old_baud =F81232_DEF_BAUDRATE ; + +f81232_set_baudrate (tty, port, baudrate, old_baud); }if (C_PARENB (tty)) {
@@ -595,11 +677,12 @@ static int f81232_get_serial_info(struct tty_struct *tty, struct serial_struct *ss) {struct usb_serial_port *port = tty->driver_data; +struct f81232_private *priv =usb_get_serial_port_data (port); ss->type =PORT_16550A ; ss->line = port->minor; ss->port = port->port_number; - ss->baud_base =F81232_MAX_BAUDRATE ; + ss->baud_base = priv->baud_base;return 0 ; }
PATCH v9 4/4 USB: serial: f81232: implement break control
In reply to Ji-Ze Hong (Peter Hong)Implement Fintek F81232 break on/off with LCR register. It's the same with 16550A LCR register layout.
---
v9:
1: no change
v8:
1: no change
v7:
1: no change
v6:
1: no change
v5:
1: no change
v4:
1: no change
v3:
1: using shadow_lcr to save LCR in f81232_break_ctl()
drivers/usb/serial/f81232.c | 29 +++++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-)
drivers/usb/serial/f81232.c +23 -6
@@ -65,6 +65,7 @@ struct f81232_private {struct mutex lock;u8 modem_control;u8 modem_status; +u8 shadow_lcr;speed_t baud_base;struct work_struct lsr_work;struct work_struct interrupt_work;
@@ -377,13 +378,23 @@ static void f81232_process_read_urb(struct urb *urb)static void f81232_break_ctl (struct tty_struct *tty,int break_state) { -/* FIXME - Stubbed out for now */ +struct usb_serial_port *port = tty->driver_data; +struct f81232_private *priv =usb_get_serial_port_data (port); +int status; -/* -* break_state = -1 to turn on break, and 0 to turn off break -* see drivers/char/tty_io.c to see it used. -* last_set_data_urb_value NEVER has the break bit set in it. -*/ +mutex_lock (&priv->lock); + +if (break_state) + priv->shadow_lcr |=UART_LCR_SBC ; +else + priv->shadow_lcr &= ~UART_LCR_SBC ; + + status =f81232_set_register (port,LINE_CONTROL_REGISTER , + priv->shadow_lcr); +if (status) +dev_err (&port->dev,"set break failed: %d\n" , status); + +mutex_unlock (&priv->lock); }static int f81232_find_clk (speed_t baudrate)
@@ -519,6 +530,7 @@ static int f81232_port_disable(struct usb_serial_port *port)static void f81232_set_termios (struct tty_struct *tty,struct usb_serial_port *port,struct ktermios *old_termios) { +struct f81232_private *priv =usb_get_serial_port_data (port);u8 new_lcr =0 ;int status =0 ;speed_t baudrate;
@@ -572,11 +584,16 @@ static void f81232_set_termios(struct tty_struct *tty,break ; } +mutex_lock (&priv->lock); + + new_lcr |= (priv->shadow_lcr &UART_LCR_SBC ); status =f81232_set_register (port,LINE_CONTROL_REGISTER , new_lcr);if (status) {dev_err (&port->dev,"%s failed to set LCR: %d\n" , __func__, status); } + +mutex_unlock (&priv->lock); }static int f81232_tiocmget (struct tty_struct *tty)
Johan Hovold
maintainerRe: [PATCH V9 4/4] USB: serial: f81232: implement break control
Quoted 45 lines · Ji-Ze Hong (Peter Hong)
Implement Fintek F81232 break on/off with LCR register. It's the same with 16550A LCR register layout.
Signed-off-by Ji-Ze Hong (Peter Hong) <[email protected]>---
static void
f81232_break_ctl(struct tty_struct *tty, int break_state){drivers/usb/serial/f81232.c +17
-6-/* FIXME - Stubbed out for now */ +struct usb_serial_port *port = tty->driver_data; +struct f81232_private *priv =usb_get_serial_port_data (port); +int status; -/* -* break_state = -1 to turn on break, and 0 to turn off break -* see drivers/char/tty_io.c to see it used. -* last_set_data_urb_value NEVER has the break bit set in it. -*/ +mutex_lock (&priv->lock); + +if (break_state) + priv->shadow_lcr |=UART_LCR_SBC ; +else + priv->shadow_lcr &= ~UART_LCR_SBC ; + + status =f81232_set_register (port,LINE_CONTROL_REGISTER , + priv->shadow_lcr); +if (status) +dev_err (&port->dev,"set break failed: %d\n" , status); + +mutex_unlock (&priv->lock); }@@ -519,6 +530,7 @@ static int f81232_port_disable(struct usb_serial_port *port)static void f81232_set_termios (struct tty_struct *tty,struct usb_serial_port *port,struct ktermios *old_termios) { +struct f81232_private *priv =usb_get_serial_port_data (port);u8 new_lcr =0 ;int status =0 ;speed_t baudrate;@@ -572,11 +584,16 @@ static void f81232_set_termios(struct tty_struct *tty,break ; }
drivers/usb/serial/f81232.c static void f81232_set_termios(struct tty_struct *tty, · line 596
Quoted 10 lines
drivers/usb/serial/f81232.c +5
-0+mutex_lock (&priv->lock); + + new_lcr |= (priv->shadow_lcr &UART_LCR_SBC ); status =f81232_set_register (port,LINE_CONTROL_REGISTER , new_lcr);if (status) {dev_err (&port->dev,"%s failed to set LCR: %d\n" , __func__, status); } + +mutex_unlock (&priv->lock);
You forgot to update shadow_lcr here so the line settings would be corrupted (e.g set to 5 bit words) whenever a break is signalled.
Did you not test this version of the patch?
I added the missing update, but please test the code that's in my usb-next branch in a minute and confirm everything works as expected.
All four patches now applied.
Thanks,
Johan
Re: [PATCH V9 4/4] USB: serial: f81232: implement break control
Hi Johan,
Johan Hovold 於 2019/5/3 下午 02:59 寫道:
Quoted 26 lines
Quoted 12 lines
drivers/usb/serial/f81232.c +1
-0@@ -519,6 +530,7 @@ static int f81232_port_disable(struct usb_serial_port *port)static void f81232_set_termios (struct tty_struct *tty,struct usb_serial_port *port,struct ktermios *old_termios) { +struct f81232_private *priv =usb_get_serial_port_data (port);u8 new_lcr =0 ;int status =0 ;speed_t baudrate;@@ -572,11 +584,16 @@ static void f81232_set_termios(struct tty_struct *tty,break ; }drivers/usb/serial/f81232.c static void f81232_set_termios(struct tty_struct *tty, · line 596
Quoted 10 lines
drivers/usb/serial/f81232.c +5
-0+mutex_lock (&priv->lock); + + new_lcr |= (priv->shadow_lcr &UART_LCR_SBC ); status =f81232_set_register (port,LINE_CONTROL_REGISTER , new_lcr);if (status) {dev_err (&port->dev,"%s failed to set LCR: %d\n" , __func__, status); } + +mutex_unlock (&priv->lock);You forgot to update
shadow_lcrhere so the line settings would be corrupted (e.g set to 5 bit words) whenever a break is signalled.
Quoted 5 lines
Did you not test this version of the patch?
I added the missing update, but please test the code that's in my usb-next branch in a minute and confirm everything works as expected.
Thanks for mention and patch the lost parts. I had only tested the break signal and forgot to test full LCR. I'll add it to my test items and it tested well on new usb-next branch after your fix.
BTW, Should I disable break control when port close ?
Thanks.
Johan Hovold
maintainerRe: [PATCH V9 4/4] USB: serial: f81232: implement break control
Quoted 5 lines · Ji-Ze Hong (Peter Hong)
Quoted 5 lines
You forgot to update
shadow_lcrhere so the line settings would be corrupted (e.g set to 5 bit words) whenever a break is signalled.Did you not test this version of the patch?
Quoted 7 lines
Quoted 3 lines
I added the missing update, but please test the code that's in my usb-next branch in a minute and confirm everything works as expected.
Thanks for mention and patch the lost parts. I had only tested the break signal and forgot to test full LCR. I'll add it to my test items and it tested well on new usb-next branch after your fix.
Thanks for confirming!
Quoted 1 line
BTW, Should I disable break control when port close ?
Yes, that would be good even if many tty drivers currently fail to do so.
Johan
Johan Hovold
maintainerQuoted 7 lines · Ji-Ze Hong (Peter Hong)
The F81232 will use interrupt worker to handle MSR change. This patch will fix the issue that interrupt work should stop in
close()andsuspend().Cc Johan Hovold <[email protected]>Signed-off-by Ji-Ze Hong (Peter Hong) <[email protected]>
drivers/usb/serial/f81232.c static int f81232_port_remove(struct usb_serial_port *port) · line 664
Quoted 10 lines
drivers/usb/serial/f81232.c +10
-0+static int f81232_resume (struct usb_serial *serial) +{ +struct usb_serial_port *port = serial->port[0 ]; +int result; + +if (tty_port_initialized (&port->port)) { + result =usb_submit_urb (port->interrupt_in_urb,GFP_NOIO ); +if (result) { +dev_err (&port->dev,"submit interrupt urb failed: %d" , + result);
I added the missing '\n' to the error message.
drivers/usb/serial/f81232.c static int f81232_port_remove(struct usb_serial_port *port) · line 670
Quoted 6 lines
drivers/usb/serial/f81232.c +6
-0+return result; + } + } + +return usb_serial_generic_resume (serial); +}
This also fixes the issue with interrupt events not being received after a suspend cycle, so I added that to the commit message.
Johan