From 36697529b5bbe36911e39a6309e7a7c9250d280a Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Sat, 15 Jun 2013 07:04:48 -0400 Subject: tty: Replace ldisc locking with ldisc_sem Line discipline locking was performed with a combination of a mutex, a status bit, a count, and a waitqueue -- basically, a rw semaphore. Replace the existing combination with an ld_semaphore. Fixes: 1) the 'reference acquire after ldisc locked' bug 2) the over-complicated halt mechanism 3) lock order wrt. tty_lock() 4) dropping locks while changing ldisc 5) previously unidentified deadlock while locking ldisc from both linked ttys concurrently 6) previously unidentified recursive deadlocks Adds much-needed lockdep diagnostics. Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- include/linux/tty.h | 4 +--- include/linux/tty_ldisc.h | 3 +-- 2 files changed, 2 insertions(+), 5 deletions(-) (limited to 'include/linux') diff --git a/include/linux/tty.h b/include/linux/tty.h index 01ac30efd6a6..7269daf7632b 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -238,7 +238,7 @@ struct tty_struct { int index; /* Protects ldisc changes: Lock tty not pty */ - struct mutex ldisc_mutex; + struct ld_semaphore ldisc_sem; struct tty_ldisc *ldisc; struct mutex atomic_write_lock; @@ -305,8 +305,6 @@ struct tty_file_private { #define TTY_DO_WRITE_WAKEUP 5 /* Call write_wakeup after queuing new */ #define TTY_PUSH 6 /* n_tty private */ #define TTY_CLOSING 7 /* ->close() in progress */ -#define TTY_LDISC 9 /* Line discipline attached */ -#define TTY_LDISC_CHANGING 10 /* Line discipline changing */ #define TTY_LDISC_OPEN 11 /* Line discipline is open */ #define TTY_PTY_LOCK 16 /* pty private */ #define TTY_NO_WRITE_SPLIT 17 /* Preserve write boundaries to driver */ diff --git a/include/linux/tty_ldisc.h b/include/linux/tty_ldisc.h index a1b048999821..23bdd9debb84 100644 --- a/include/linux/tty_ldisc.h +++ b/include/linux/tty_ldisc.h @@ -203,8 +203,7 @@ struct tty_ldisc_ops { struct tty_ldisc { struct tty_ldisc_ops *ops; - atomic_t users; - wait_queue_head_t wq_idle; + struct tty_struct *tty; }; #define TTY_LDISC_MAGIC 0x5403 -- cgit v1.2.3 From 24a89d1cb69b6c488cf16d98dd02e7820f62b40c Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Sat, 15 Jun 2013 09:14:15 -0400 Subject: tty: Make ldisc input flow control concurrency-friendly Although line discipline receiving is single-producer/single-consumer, using tty->receive_room to manage flow control creates unnecessary critical regions requiring additional lock use. Instead, introduce the optional .receive_buf2() ldisc method which returns the # of bytes actually received. Serialization is guaranteed by the caller. In turn, the line discipline should schedule the buffer work item whenever space becomes available; ie., when there is room to receive data and receive_room() previously returned 0 (the buffer work item stops processing if receive_buf2() returns 0). Note the 'no room' state need not be atomic despite concurrent use by two threads because only the buffer work thread can set the state and only the read() thread can clear the state. Add n_tty_receive_buf2() as the receive_buf2() method for N_TTY. Provide a public helper function, tty_ldisc_receive_buf(), to use when directly accessing the receive_buf() methods. Line disciplines not using input flow control can continue to set tty->receive_room to a fixed value and only provide the receive_buf() method. Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/n_tty.c | 72 +++++++++++++++++++++++++++++----------------- drivers/tty/tty_buffer.c | 13 ++++++--- drivers/tty/vt/selection.c | 4 +-- include/linux/tty.h | 13 +++++++++ include/linux/tty_ldisc.h | 13 +++++++++ 5 files changed, 82 insertions(+), 33 deletions(-) (limited to 'include/linux') diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c index 4bf0fc0843d7..eddeb7889e62 100644 --- a/drivers/tty/n_tty.c +++ b/drivers/tty/n_tty.c @@ -79,6 +79,9 @@ struct n_tty_data { unsigned long overrun_time; int num_overrun; + /* non-atomic */ + bool no_room; + unsigned char lnext:1, erasing:1, raw:1, real_raw:1, icanon:1; unsigned char echo_overrun:1; @@ -114,25 +117,10 @@ static inline int tty_put_user(struct tty_struct *tty, unsigned char x, return put_user(x, ptr); } -/** - * n_tty_set_room - receive space - * @tty: terminal - * - * Updates tty->receive_room to reflect the currently available space - * in the input buffer, and re-schedules the flip buffer work if space - * just became available. - * - * Locks: Concurrent update is protected with read_lock - */ - -static int set_room(struct tty_struct *tty) +static int receive_room(struct tty_struct *tty) { struct n_tty_data *ldata = tty->disc_data; int left; - int old_left; - unsigned long flags; - - raw_spin_lock_irqsave(&ldata->read_lock, flags); if (I_PARMRK(tty)) { /* Multiply read_cnt by 3, since each byte might take up to @@ -150,18 +138,27 @@ static int set_room(struct tty_struct *tty) */ if (left <= 0) left = ldata->icanon && !ldata->canon_data; - old_left = tty->receive_room; - tty->receive_room = left; - raw_spin_unlock_irqrestore(&ldata->read_lock, flags); - - return left && !old_left; + return left; } +/** + * n_tty_set_room - receive space + * @tty: terminal + * + * Re-schedules the flip buffer work if space just became available. + * + * Locks: Concurrent update is protected with read_lock + */ + static void n_tty_set_room(struct tty_struct *tty) { + struct n_tty_data *ldata = tty->disc_data; + /* Did this open up the receive buffer? We may need to flip */ - if (set_room(tty)) { + if (unlikely(ldata->no_room) && receive_room(tty)) { + ldata->no_room = 0; + WARN_RATELIMIT(tty->port->itty == NULL, "scheduling with invalid itty\n"); /* see if ldisc has been killed - if so, this means that @@ -1408,8 +1405,8 @@ static void n_tty_write_wakeup(struct tty_struct *tty) * calls one at a time and in order (or using flush_to_ldisc) */ -static void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp, - char *fp, int count) +static void __receive_buf(struct tty_struct *tty, const unsigned char *cp, + char *fp, int count) { struct n_tty_data *ldata = tty->disc_data; const unsigned char *p; @@ -1464,8 +1461,6 @@ static void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp, tty->ops->flush_chars(tty); } - set_room(tty); - if ((!ldata->icanon && (ldata->read_cnt >= ldata->minimum_to_wake)) || L_EXTPROC(tty)) { kill_fasync(&tty->fasync, SIGIO, POLL_IN); @@ -1480,7 +1475,7 @@ static void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp, */ while (1) { tty_set_flow_change(tty, TTY_THROTTLE_SAFE); - if (tty->receive_room >= TTY_THRESHOLD_THROTTLE) + if (receive_room(tty) >= TTY_THRESHOLD_THROTTLE) break; if (!tty_throttle_safe(tty)) break; @@ -1488,6 +1483,28 @@ static void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp, __tty_set_flow_change(tty, 0); } +static void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp, + char *fp, int count) +{ + __receive_buf(tty, cp, fp, count); +} + +static int n_tty_receive_buf2(struct tty_struct *tty, const unsigned char *cp, + char *fp, int count) +{ + struct n_tty_data *ldata = tty->disc_data; + int room; + + tty->receive_room = room = receive_room(tty); + if (!room) + ldata->no_room = 1; + count = min(count, room); + if (count) + __receive_buf(tty, cp, fp, count); + + return count; +} + int is_ignored(int sig) { return (sigismember(¤t->blocked, sig) || @@ -2203,6 +2220,7 @@ struct tty_ldisc_ops tty_ldisc_N_TTY = { .receive_buf = n_tty_receive_buf, .write_wakeup = n_tty_write_wakeup, .fasync = n_tty_fasync, + .receive_buf2 = n_tty_receive_buf2, }; /** diff --git a/drivers/tty/tty_buffer.c b/drivers/tty/tty_buffer.c index 6c7a1d043c76..ff1b2e37c3ca 100644 --- a/drivers/tty/tty_buffer.c +++ b/drivers/tty/tty_buffer.c @@ -407,11 +407,16 @@ static int receive_buf(struct tty_struct *tty, struct tty_buffer *head, int count) { struct tty_ldisc *disc = tty->ldisc; + char *p = head->char_buf_ptr + head->read; + unsigned char *f = head->flag_buf_ptr + head->read; - count = min_t(int, count, tty->receive_room); - if (count) - disc->ops->receive_buf(tty, head->char_buf_ptr + head->read, - head->flag_buf_ptr + head->read, count); + if (disc->ops->receive_buf2) + count = disc->ops->receive_buf2(tty, p, f, count); + else { + count = min_t(int, count, tty->receive_room); + if (count) + disc->ops->receive_buf(tty, p, f, count); + } head->read += count; return count; } diff --git a/drivers/tty/vt/selection.c b/drivers/tty/vt/selection.c index 60b7b6926059..2ca8d6b6514c 100644 --- a/drivers/tty/vt/selection.c +++ b/drivers/tty/vt/selection.c @@ -356,8 +356,8 @@ int paste_selection(struct tty_struct *tty) continue; } count = sel_buffer_lth - pasted; - count = min(count, tty->receive_room); - ld->ops->receive_buf(tty, sel_buffer + pasted, NULL, count); + count = tty_ldisc_receive_buf(ld, sel_buffer + pasted, NULL, + count); pasted += count; } remove_wait_queue(&vc->paste_wait, &wait); diff --git a/include/linux/tty.h b/include/linux/tty.h index 7269daf7632b..8323ee4f95b9 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -557,6 +557,19 @@ extern void tty_ldisc_init(struct tty_struct *tty); extern void tty_ldisc_deinit(struct tty_struct *tty); extern void tty_ldisc_begin(void); +static inline int tty_ldisc_receive_buf(struct tty_ldisc *ld, unsigned char *p, + char *f, int count) +{ + if (ld->ops->receive_buf2) + count = ld->ops->receive_buf2(ld->tty, p, f, count); + else { + count = min_t(int, count, ld->tty->receive_room); + if (count) + ld->ops->receive_buf(ld->tty, p, f, count); + } + return count; +} + /* n_tty.c */ extern struct tty_ldisc_ops tty_ldisc_N_TTY; diff --git a/include/linux/tty_ldisc.h b/include/linux/tty_ldisc.h index 23bdd9debb84..f15c898ff462 100644 --- a/include/linux/tty_ldisc.h +++ b/include/linux/tty_ldisc.h @@ -109,6 +109,17 @@ * * Tells the discipline that the DCD pin has changed its status. * Used exclusively by the N_PPS (Pulse-Per-Second) line discipline. + * + * int (*receive_buf2)(struct tty_struct *, const unsigned char *cp, + * char *fp, int count); + * + * This function is called by the low-level tty driver to send + * characters received by the hardware to the line discpline for + * processing. is a pointer to the buffer of input + * character received by the device. is a pointer to a + * pointer of flag bytes which indicate whether a character was + * received with a parity error, etc. + * If assigned, prefer this function for automatic flow control. */ #include @@ -195,6 +206,8 @@ struct tty_ldisc_ops { void (*write_wakeup)(struct tty_struct *); void (*dcd_change)(struct tty_struct *, unsigned int); void (*fasync)(struct tty_struct *tty, int on); + int (*receive_buf2)(struct tty_struct *, const unsigned char *cp, + char *fp, int count); struct module *owner; -- cgit v1.2.3 From 6a1c0680cf3ba94356ecd58833e1540c93472a57 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Sat, 15 Jun 2013 09:14:23 -0400 Subject: tty: Convert termios_mutex to termios_rwsem termios is commonly accessed unsafely (especially by N_TTY) because the existing mutex forces exclusive access. Convert existing usage. Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/net/irda/irtty-sir.c | 8 ++-- drivers/tty/n_tty.c | 2 +- drivers/tty/pty.c | 4 +- drivers/tty/tty_io.c | 14 +++---- drivers/tty/tty_ioctl.c | 90 ++++++++++++++++++++++---------------------- drivers/tty/tty_ldisc.c | 10 ++--- drivers/tty/vt/vt.c | 4 +- include/linux/tty.h | 7 ++-- 8 files changed, 70 insertions(+), 69 deletions(-) (limited to 'include/linux') diff --git a/drivers/net/irda/irtty-sir.c b/drivers/net/irda/irtty-sir.c index a41267197839..177441afeb96 100644 --- a/drivers/net/irda/irtty-sir.c +++ b/drivers/net/irda/irtty-sir.c @@ -123,14 +123,14 @@ static int irtty_change_speed(struct sir_dev *dev, unsigned speed) tty = priv->tty; - mutex_lock(&tty->termios_mutex); + down_write(&tty->termios_rwsem); old_termios = tty->termios; cflag = tty->termios.c_cflag; tty_encode_baud_rate(tty, speed, speed); if (tty->ops->set_termios) tty->ops->set_termios(tty, &old_termios); priv->io.speed = speed; - mutex_unlock(&tty->termios_mutex); + up_write(&tty->termios_rwsem); return 0; } @@ -280,7 +280,7 @@ static inline void irtty_stop_receiver(struct tty_struct *tty, int stop) struct ktermios old_termios; int cflag; - mutex_lock(&tty->termios_mutex); + down_write(&tty->termios_rwsem); old_termios = tty->termios; cflag = tty->termios.c_cflag; @@ -292,7 +292,7 @@ static inline void irtty_stop_receiver(struct tty_struct *tty, int stop) tty->termios.c_cflag = cflag; if (tty->ops->set_termios) tty->ops->set_termios(tty, &old_termios); - mutex_unlock(&tty->termios_mutex); + up_write(&tty->termios_rwsem); } /*****************************************************************/ diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c index d159059560e8..ab923bb2b262 100644 --- a/drivers/tty/n_tty.c +++ b/drivers/tty/n_tty.c @@ -1539,7 +1539,7 @@ int is_ignored(int sig) * guaranteed that this function will not be re-entered or in progress * when the ldisc is closed. * - * Locking: Caller holds tty->termios_mutex + * Locking: Caller holds tty->termios_rwsem */ static void n_tty_set_termios(struct tty_struct *tty, struct ktermios *old) diff --git a/drivers/tty/pty.c b/drivers/tty/pty.c index abfd99089781..1b39dd639ee9 100644 --- a/drivers/tty/pty.c +++ b/drivers/tty/pty.c @@ -287,7 +287,7 @@ static int pty_resize(struct tty_struct *tty, struct winsize *ws) struct tty_struct *pty = tty->link; /* For a PTY we need to lock the tty side */ - mutex_lock(&tty->termios_mutex); + down_write(&tty->termios_rwsem); if (!memcmp(ws, &tty->winsize, sizeof(*ws))) goto done; @@ -314,7 +314,7 @@ static int pty_resize(struct tty_struct *tty, struct winsize *ws) tty->winsize = *ws; pty->winsize = *ws; /* Never used so will go away soon */ done: - mutex_unlock(&tty->termios_mutex); + up_write(&tty->termios_rwsem); return 0; } diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c index 0fa5db4c7a7a..639e24ade9bf 100644 --- a/drivers/tty/tty_io.c +++ b/drivers/tty/tty_io.c @@ -604,7 +604,7 @@ static int tty_signal_session_leader(struct tty_struct *tty, int exit_session) * redirect lock for undoing redirection * file list lock for manipulating list of ttys * tty_ldiscs_lock from called functions - * termios_mutex resetting termios data + * termios_rwsem resetting termios data * tasklist_lock to walk task list for hangup event * ->siglock to protect ->signal/->sighand */ @@ -2230,7 +2230,7 @@ static int tiocsti(struct tty_struct *tty, char __user *p) * * Copies the kernel idea of the window size into the user buffer. * - * Locking: tty->termios_mutex is taken to ensure the winsize data + * Locking: tty->termios_rwsem is taken to ensure the winsize data * is consistent. */ @@ -2238,9 +2238,9 @@ static int tiocgwinsz(struct tty_struct *tty, struct winsize __user *arg) { int err; - mutex_lock(&tty->termios_mutex); + down_read(&tty->termios_rwsem); err = copy_to_user(arg, &tty->winsize, sizeof(*arg)); - mutex_unlock(&tty->termios_mutex); + up_read(&tty->termios_rwsem); return err ? -EFAULT: 0; } @@ -2261,7 +2261,7 @@ int tty_do_resize(struct tty_struct *tty, struct winsize *ws) unsigned long flags; /* Lock the tty */ - mutex_lock(&tty->termios_mutex); + down_write(&tty->termios_rwsem); if (!memcmp(ws, &tty->winsize, sizeof(*ws))) goto done; /* Get the PID values and reference them so we can @@ -2276,7 +2276,7 @@ int tty_do_resize(struct tty_struct *tty, struct winsize *ws) tty->winsize = *ws; done: - mutex_unlock(&tty->termios_mutex); + up_write(&tty->termios_rwsem); return 0; } EXPORT_SYMBOL(tty_do_resize); @@ -3015,7 +3015,7 @@ void initialize_tty_struct(struct tty_struct *tty, tty->session = NULL; tty->pgrp = NULL; mutex_init(&tty->legacy_mutex); - mutex_init(&tty->termios_mutex); + init_rwsem(&tty->termios_rwsem); init_ldsem(&tty->ldisc_sem); init_waitqueue_head(&tty->write_wait); init_waitqueue_head(&tty->read_wait); diff --git a/drivers/tty/tty_ioctl.c b/drivers/tty/tty_ioctl.c index 3500d4114147..9ce20df8a2c8 100644 --- a/drivers/tty/tty_ioctl.c +++ b/drivers/tty/tty_ioctl.c @@ -94,20 +94,20 @@ EXPORT_SYMBOL(tty_driver_flush_buffer); * @tty: terminal * * Indicate that a tty should stop transmitting data down the stack. - * Takes the termios mutex to protect against parallel throttle/unthrottle + * Takes the termios rwsem to protect against parallel throttle/unthrottle * and also to ensure the driver can consistently reference its own * termios data at this point when implementing software flow control. */ void tty_throttle(struct tty_struct *tty) { - mutex_lock(&tty->termios_mutex); + down_write(&tty->termios_rwsem); /* check TTY_THROTTLED first so it indicates our state */ if (!test_and_set_bit(TTY_THROTTLED, &tty->flags) && tty->ops->throttle) tty->ops->throttle(tty); tty->flow_change = 0; - mutex_unlock(&tty->termios_mutex); + up_write(&tty->termios_rwsem); } EXPORT_SYMBOL(tty_throttle); @@ -116,7 +116,7 @@ EXPORT_SYMBOL(tty_throttle); * @tty: terminal * * Indicate that a tty may continue transmitting data down the stack. - * Takes the termios mutex to protect against parallel throttle/unthrottle + * Takes the termios rwsem to protect against parallel throttle/unthrottle * and also to ensure the driver can consistently reference its own * termios data at this point when implementing software flow control. * @@ -126,12 +126,12 @@ EXPORT_SYMBOL(tty_throttle); void tty_unthrottle(struct tty_struct *tty) { - mutex_lock(&tty->termios_mutex); + down_write(&tty->termios_rwsem); if (test_and_clear_bit(TTY_THROTTLED, &tty->flags) && tty->ops->unthrottle) tty->ops->unthrottle(tty); tty->flow_change = 0; - mutex_unlock(&tty->termios_mutex); + up_write(&tty->termios_rwsem); } EXPORT_SYMBOL(tty_unthrottle); @@ -151,7 +151,7 @@ int tty_throttle_safe(struct tty_struct *tty) { int ret = 0; - mutex_lock(&tty->termios_mutex); + down_write(&tty->termios_rwsem); if (!test_bit(TTY_THROTTLED, &tty->flags)) { if (tty->flow_change != TTY_THROTTLE_SAFE) ret = 1; @@ -161,7 +161,7 @@ int tty_throttle_safe(struct tty_struct *tty) tty->ops->throttle(tty); } } - mutex_unlock(&tty->termios_mutex); + up_write(&tty->termios_rwsem); return ret; } @@ -182,7 +182,7 @@ int tty_unthrottle_safe(struct tty_struct *tty) { int ret = 0; - mutex_lock(&tty->termios_mutex); + down_write(&tty->termios_rwsem); if (test_bit(TTY_THROTTLED, &tty->flags)) { if (tty->flow_change != TTY_UNTHROTTLE_SAFE) ret = 1; @@ -192,7 +192,7 @@ int tty_unthrottle_safe(struct tty_struct *tty) tty->ops->unthrottle(tty); } } - mutex_unlock(&tty->termios_mutex); + up_write(&tty->termios_rwsem); return ret; } @@ -468,7 +468,7 @@ EXPORT_SYMBOL_GPL(tty_termios_encode_baud_rate); * @obad: output baud rate * * Update the current termios data for the tty with the new speed - * settings. The caller must hold the termios_mutex for the tty in + * settings. The caller must hold the termios_rwsem for the tty in * question. */ @@ -528,7 +528,7 @@ EXPORT_SYMBOL(tty_termios_hw_change); * is a bit of layering violation here with n_tty in terms of the * internal knowledge of this function. * - * Locking: termios_mutex + * Locking: termios_rwsem */ int tty_set_termios(struct tty_struct *tty, struct ktermios *new_termios) @@ -544,7 +544,7 @@ int tty_set_termios(struct tty_struct *tty, struct ktermios *new_termios) /* FIXME: we need to decide on some locking/ordering semantics for the set_termios notification eventually */ - mutex_lock(&tty->termios_mutex); + down_write(&tty->termios_rwsem); old_termios = tty->termios; tty->termios = *new_termios; unset_locked_termios(&tty->termios, &old_termios, &tty->termios_locked); @@ -586,7 +586,7 @@ int tty_set_termios(struct tty_struct *tty, struct ktermios *new_termios) (ld->ops->set_termios)(tty, &old_termios); tty_ldisc_deref(ld); } - mutex_unlock(&tty->termios_mutex); + up_write(&tty->termios_rwsem); return 0; } EXPORT_SYMBOL_GPL(tty_set_termios); @@ -601,7 +601,7 @@ EXPORT_SYMBOL_GPL(tty_set_termios); * functions before using tty_set_termios to do the actual changes. * * Locking: - * Called functions take ldisc and termios_mutex locks + * Called functions take ldisc and termios_rwsem locks */ static int set_termios(struct tty_struct *tty, void __user *arg, int opt) @@ -613,9 +613,9 @@ static int set_termios(struct tty_struct *tty, void __user *arg, int opt) if (retval) return retval; - mutex_lock(&tty->termios_mutex); + down_read(&tty->termios_rwsem); tmp_termios = tty->termios; - mutex_unlock(&tty->termios_mutex); + up_read(&tty->termios_rwsem); if (opt & TERMIOS_TERMIO) { if (user_termio_to_kernel_termios(&tmp_termios, @@ -667,16 +667,16 @@ static int set_termios(struct tty_struct *tty, void __user *arg, int opt) static void copy_termios(struct tty_struct *tty, struct ktermios *kterm) { - mutex_lock(&tty->termios_mutex); + down_read(&tty->termios_rwsem); *kterm = tty->termios; - mutex_unlock(&tty->termios_mutex); + up_read(&tty->termios_rwsem); } static void copy_termios_locked(struct tty_struct *tty, struct ktermios *kterm) { - mutex_lock(&tty->termios_mutex); + down_read(&tty->termios_rwsem); *kterm = tty->termios_locked; - mutex_unlock(&tty->termios_mutex); + up_read(&tty->termios_rwsem); } static int get_termio(struct tty_struct *tty, struct termio __user *termio) @@ -723,10 +723,10 @@ static int set_termiox(struct tty_struct *tty, void __user *arg, int opt) return -ERESTARTSYS; } - mutex_lock(&tty->termios_mutex); + down_write(&tty->termios_rwsem); if (tty->ops->set_termiox) tty->ops->set_termiox(tty, &tnew); - mutex_unlock(&tty->termios_mutex); + up_write(&tty->termios_rwsem); return 0; } @@ -761,13 +761,13 @@ static int get_sgttyb(struct tty_struct *tty, struct sgttyb __user *sgttyb) { struct sgttyb tmp; - mutex_lock(&tty->termios_mutex); + down_read(&tty->termios_rwsem); tmp.sg_ispeed = tty->termios.c_ispeed; tmp.sg_ospeed = tty->termios.c_ospeed; tmp.sg_erase = tty->termios.c_cc[VERASE]; tmp.sg_kill = tty->termios.c_cc[VKILL]; tmp.sg_flags = get_sgflags(tty); - mutex_unlock(&tty->termios_mutex); + up_read(&tty->termios_rwsem); return copy_to_user(sgttyb, &tmp, sizeof(tmp)) ? -EFAULT : 0; } @@ -806,7 +806,7 @@ static void set_sgflags(struct ktermios *termios, int flags) * Updates a terminal from the legacy BSD style terminal information * structure. * - * Locking: termios_mutex + * Locking: termios_rwsem */ static int set_sgttyb(struct tty_struct *tty, struct sgttyb __user *sgttyb) @@ -822,7 +822,7 @@ static int set_sgttyb(struct tty_struct *tty, struct sgttyb __user *sgttyb) if (copy_from_user(&tmp, sgttyb, sizeof(tmp))) return -EFAULT; - mutex_lock(&tty->termios_mutex); + down_write(&tty->termios_rwsem); termios = tty->termios; termios.c_cc[VERASE] = tmp.sg_erase; termios.c_cc[VKILL] = tmp.sg_kill; @@ -832,7 +832,7 @@ static int set_sgttyb(struct tty_struct *tty, struct sgttyb __user *sgttyb) tty_termios_encode_baud_rate(&termios, termios.c_ispeed, termios.c_ospeed); #endif - mutex_unlock(&tty->termios_mutex); + up_write(&tty->termios_rwsem); tty_set_termios(tty, &termios); return 0; } @@ -843,14 +843,14 @@ static int get_tchars(struct tty_struct *tty, struct tchars __user *tchars) { struct tchars tmp; - mutex_lock(&tty->termios_mutex); + down_read(&tty->termios_rwsem); tmp.t_intrc = tty->termios.c_cc[VINTR]; tmp.t_quitc = tty->termios.c_cc[VQUIT]; tmp.t_startc = tty->termios.c_cc[VSTART]; tmp.t_stopc = tty->termios.c_cc[VSTOP]; tmp.t_eofc = tty->termios.c_cc[VEOF]; tmp.t_brkc = tty->termios.c_cc[VEOL2]; /* what is brkc anyway? */ - mutex_unlock(&tty->termios_mutex); + up_read(&tty->termios_rwsem); return copy_to_user(tchars, &tmp, sizeof(tmp)) ? -EFAULT : 0; } @@ -860,14 +860,14 @@ static int set_tchars(struct tty_struct *tty, struct tchars __user *tchars) if (copy_from_user(&tmp, tchars, sizeof(tmp))) return -EFAULT; - mutex_lock(&tty->termios_mutex); + down_write(&tty->termios_rwsem); tty->termios.c_cc[VINTR] = tmp.t_intrc; tty->termios.c_cc[VQUIT] = tmp.t_quitc; tty->termios.c_cc[VSTART] = tmp.t_startc; tty->termios.c_cc[VSTOP] = tmp.t_stopc; tty->termios.c_cc[VEOF] = tmp.t_eofc; tty->termios.c_cc[VEOL2] = tmp.t_brkc; /* what is brkc anyway? */ - mutex_unlock(&tty->termios_mutex); + up_write(&tty->termios_rwsem); return 0; } #endif @@ -877,7 +877,7 @@ static int get_ltchars(struct tty_struct *tty, struct ltchars __user *ltchars) { struct ltchars tmp; - mutex_lock(&tty->termios_mutex); + down_read(&tty->termios_rwsem); tmp.t_suspc = tty->termios.c_cc[VSUSP]; /* what is dsuspc anyway? */ tmp.t_dsuspc = tty->termios.c_cc[VSUSP]; @@ -886,7 +886,7 @@ static int get_ltchars(struct tty_struct *tty, struct ltchars __user *ltchars) tmp.t_flushc = tty->termios.c_cc[VEOL2]; tmp.t_werasc = tty->termios.c_cc[VWERASE]; tmp.t_lnextc = tty->termios.c_cc[VLNEXT]; - mutex_unlock(&tty->termios_mutex); + up_read(&tty->termios_rwsem); return copy_to_user(ltchars, &tmp, sizeof(tmp)) ? -EFAULT : 0; } @@ -897,7 +897,7 @@ static int set_ltchars(struct tty_struct *tty, struct ltchars __user *ltchars) if (copy_from_user(&tmp, ltchars, sizeof(tmp))) return -EFAULT; - mutex_lock(&tty->termios_mutex); + down_write(&tty->termios_rwsem); tty->termios.c_cc[VSUSP] = tmp.t_suspc; /* what is dsuspc anyway? */ tty->termios.c_cc[VEOL2] = tmp.t_dsuspc; @@ -906,7 +906,7 @@ static int set_ltchars(struct tty_struct *tty, struct ltchars __user *ltchars) tty->termios.c_cc[VEOL2] = tmp.t_flushc; tty->termios.c_cc[VWERASE] = tmp.t_werasc; tty->termios.c_cc[VLNEXT] = tmp.t_lnextc; - mutex_unlock(&tty->termios_mutex); + up_write(&tty->termios_rwsem); return 0; } #endif @@ -946,7 +946,7 @@ static int send_prio_char(struct tty_struct *tty, char ch) * @arg: enable/disable CLOCAL * * Perform a change to the CLOCAL state and call into the driver - * layer to make it visible. All done with the termios mutex + * layer to make it visible. All done with the termios rwsem */ static int tty_change_softcar(struct tty_struct *tty, int arg) @@ -955,7 +955,7 @@ static int tty_change_softcar(struct tty_struct *tty, int arg) int bit = arg ? CLOCAL : 0; struct ktermios old; - mutex_lock(&tty->termios_mutex); + down_write(&tty->termios_rwsem); old = tty->termios; tty->termios.c_cflag &= ~CLOCAL; tty->termios.c_cflag |= bit; @@ -963,7 +963,7 @@ static int tty_change_softcar(struct tty_struct *tty, int arg) tty->ops->set_termios(tty, &old); if ((tty->termios.c_cflag & CLOCAL) != bit) ret = -EINVAL; - mutex_unlock(&tty->termios_mutex); + up_write(&tty->termios_rwsem); return ret; } @@ -1066,9 +1066,9 @@ int tty_mode_ioctl(struct tty_struct *tty, struct file *file, if (user_termios_to_kernel_termios(&kterm, (struct termios __user *) arg)) return -EFAULT; - mutex_lock(&real_tty->termios_mutex); + down_write(&real_tty->termios_rwsem); real_tty->termios_locked = kterm; - mutex_unlock(&real_tty->termios_mutex); + up_write(&real_tty->termios_rwsem); return 0; #else case TIOCGLCKTRMIOS: @@ -1083,9 +1083,9 @@ int tty_mode_ioctl(struct tty_struct *tty, struct file *file, if (user_termios_to_kernel_termios_1(&kterm, (struct termios __user *) arg)) return -EFAULT; - mutex_lock(&real_tty->termios_mutex); + down_write(&real_tty->termios_rwsem); real_tty->termios_locked = kterm; - mutex_unlock(&real_tty->termios_mutex); + up_write(&real_tty->termios_rwsem); return ret; #endif #ifdef TCGETX @@ -1093,9 +1093,9 @@ int tty_mode_ioctl(struct tty_struct *tty, struct file *file, struct termiox ktermx; if (real_tty->termiox == NULL) return -EINVAL; - mutex_lock(&real_tty->termios_mutex); + down_read(&real_tty->termios_rwsem); memcpy(&ktermx, real_tty->termiox, sizeof(struct termiox)); - mutex_unlock(&real_tty->termios_mutex); + up_read(&real_tty->termios_rwsem); if (copy_to_user(p, &ktermx, sizeof(struct termiox))) ret = -EFAULT; return ret; diff --git a/drivers/tty/tty_ldisc.c b/drivers/tty/tty_ldisc.c index e52d61573553..6458e11e8e9d 100644 --- a/drivers/tty/tty_ldisc.c +++ b/drivers/tty/tty_ldisc.c @@ -415,14 +415,14 @@ EXPORT_SYMBOL_GPL(tty_ldisc_flush); * they are not on hot paths so a little discipline won't do * any harm. * - * Locking: takes termios_mutex + * Locking: takes termios_rwsem */ static void tty_set_termios_ldisc(struct tty_struct *tty, int num) { - mutex_lock(&tty->termios_mutex); + down_write(&tty->termios_rwsem); tty->termios.c_line = num; - mutex_unlock(&tty->termios_mutex); + up_write(&tty->termios_rwsem); } /** @@ -602,11 +602,11 @@ int tty_set_ldisc(struct tty_struct *tty, int ldisc) static void tty_reset_termios(struct tty_struct *tty) { - mutex_lock(&tty->termios_mutex); + down_write(&tty->termios_rwsem); tty->termios = tty->driver->init_termios; tty->termios.c_ispeed = tty_termios_input_baud_rate(&tty->termios); tty->termios.c_ospeed = tty_termios_baud_rate(&tty->termios); - mutex_unlock(&tty->termios_mutex); + up_write(&tty->termios_rwsem); } diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c index c677829baa8b..02af6ccefe6a 100644 --- a/drivers/tty/vt/vt.c +++ b/drivers/tty/vt/vt.c @@ -828,7 +828,7 @@ static inline int resize_screen(struct vc_data *vc, int width, int height, * If the caller passes a tty structure then update the termios winsize * information and perform any necessary signal handling. * - * Caller must hold the console semaphore. Takes the termios mutex and + * Caller must hold the console semaphore. Takes the termios rwsem and * ctrl_lock of the tty IFF a tty is passed. */ @@ -972,7 +972,7 @@ int vc_resize(struct vc_data *vc, unsigned int cols, unsigned int rows) * the actual work. * * Takes the console sem and the called methods then take the tty - * termios_mutex and the tty ctrl_lock in that order. + * termios_rwsem and the tty ctrl_lock in that order. */ static int vt_resize(struct tty_struct *tty, struct winsize *ws) { diff --git a/include/linux/tty.h b/include/linux/tty.h index 8323ee4f95b9..d3042076d163 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -10,6 +10,7 @@ #include #include #include +#include @@ -243,9 +244,9 @@ struct tty_struct { struct mutex atomic_write_lock; struct mutex legacy_mutex; - struct mutex termios_mutex; + struct rw_semaphore termios_rwsem; spinlock_t ctrl_lock; - /* Termios values are protected by the termios mutex */ + /* Termios values are protected by the termios rwsem */ struct ktermios termios, termios_locked; struct termiox *termiox; /* May be NULL for unsupported */ char name[64]; @@ -253,7 +254,7 @@ struct tty_struct { struct pid *session; unsigned long flags; int count; - struct winsize winsize; /* termios mutex */ + struct winsize winsize; /* termios rwsem */ unsigned char stopped:1, hw_stopped:1, flow_stopped:1, packet:1; unsigned char ctrl_status; /* ctrl_lock */ unsigned int receive_room; /* Bytes free for queue */ -- cgit v1.2.3 From d8c1f929aa8164cd8eaa830068d2fa3159c0764a Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Sat, 15 Jun 2013 09:14:31 -0400 Subject: tty: Only guarantee termios read safety for throttle/unthrottle No tty driver modifies termios during throttle() or unthrottle(). Therefore, only read safety is required. However, tty_throttle_safe and tty_unthrottle_safe must still be mutually exclusive; introduce throttle_mutex for that purpose. Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/n_tty.c | 4 ---- drivers/tty/tty_io.c | 1 + drivers/tty/tty_ioctl.c | 8 ++++---- include/linux/tty.h | 1 + 4 files changed, 6 insertions(+), 8 deletions(-) (limited to 'include/linux') diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c index d0c8805d8131..b78ee464bd09 100644 --- a/drivers/tty/n_tty.c +++ b/drivers/tty/n_tty.c @@ -1518,9 +1518,7 @@ static void __receive_buf(struct tty_struct *tty, const unsigned char *cp, tty_set_flow_change(tty, TTY_THROTTLE_SAFE); if (receive_room(tty) >= TTY_THRESHOLD_THROTTLE) break; - up_read(&tty->termios_rwsem); throttled = tty_throttle_safe(tty); - down_read(&tty->termios_rwsem); if (!throttled) break; } @@ -2086,9 +2084,7 @@ do_it_again: if (!tty->count) break; n_tty_set_room(tty); - up_read(&tty->termios_rwsem); unthrottled = tty_unthrottle_safe(tty); - down_read(&tty->termios_rwsem); if (!unthrottled) break; } diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c index 639e24ade9bf..1b32da6f6cdd 100644 --- a/drivers/tty/tty_io.c +++ b/drivers/tty/tty_io.c @@ -3015,6 +3015,7 @@ void initialize_tty_struct(struct tty_struct *tty, tty->session = NULL; tty->pgrp = NULL; mutex_init(&tty->legacy_mutex); + mutex_init(&tty->throttle_mutex); init_rwsem(&tty->termios_rwsem); init_ldsem(&tty->ldisc_sem); init_waitqueue_head(&tty->write_wait); diff --git a/drivers/tty/tty_ioctl.c b/drivers/tty/tty_ioctl.c index 9ce20df8a2c8..03ba081c5772 100644 --- a/drivers/tty/tty_ioctl.c +++ b/drivers/tty/tty_ioctl.c @@ -151,7 +151,7 @@ int tty_throttle_safe(struct tty_struct *tty) { int ret = 0; - down_write(&tty->termios_rwsem); + mutex_lock(&tty->throttle_mutex); if (!test_bit(TTY_THROTTLED, &tty->flags)) { if (tty->flow_change != TTY_THROTTLE_SAFE) ret = 1; @@ -161,7 +161,7 @@ int tty_throttle_safe(struct tty_struct *tty) tty->ops->throttle(tty); } } - up_write(&tty->termios_rwsem); + mutex_unlock(&tty->throttle_mutex); return ret; } @@ -182,7 +182,7 @@ int tty_unthrottle_safe(struct tty_struct *tty) { int ret = 0; - down_write(&tty->termios_rwsem); + mutex_lock(&tty->throttle_mutex); if (test_bit(TTY_THROTTLED, &tty->flags)) { if (tty->flow_change != TTY_UNTHROTTLE_SAFE) ret = 1; @@ -192,7 +192,7 @@ int tty_unthrottle_safe(struct tty_struct *tty) tty->ops->unthrottle(tty); } } - up_write(&tty->termios_rwsem); + mutex_unlock(&tty->throttle_mutex); return ret; } diff --git a/include/linux/tty.h b/include/linux/tty.h index d3042076d163..57a70d1d0412 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -244,6 +244,7 @@ struct tty_struct { struct mutex atomic_write_lock; struct mutex legacy_mutex; + struct mutex throttle_mutex; struct rw_semaphore termios_rwsem; spinlock_t ctrl_lock; /* Termios values are protected by the termios rwsem */ -- cgit v1.2.3 From 1fc359fc3ea72314cc3ebdfa94c60e020c152cd2 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Sat, 15 Jun 2013 09:36:01 -0400 Subject: tty: Compute flip buffer ptrs The char_buf_ptr and flag_buf_ptr values are trivially derived from the .data field offset; compute values as needed. Fixes a long-standing type-mismatch with the char and flag ptrs. Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/tty_buffer.c | 22 ++++++++++------------ include/linux/tty.h | 12 ++++++++++-- include/linux/tty_flip.h | 4 ++-- 3 files changed, 22 insertions(+), 16 deletions(-) (limited to 'include/linux') diff --git a/drivers/tty/tty_buffer.c b/drivers/tty/tty_buffer.c index ff1b2e37c3ca..170674cb68fc 100644 --- a/drivers/tty/tty_buffer.c +++ b/drivers/tty/tty_buffer.c @@ -71,8 +71,6 @@ static struct tty_buffer *tty_buffer_alloc(struct tty_port *port, size_t size) p->next = NULL; p->commit = 0; p->read = 0; - p->char_buf_ptr = (char *)(p->data); - p->flag_buf_ptr = (unsigned char *)p->char_buf_ptr + size; port->buf.memory_used += size; return p; } @@ -265,8 +263,8 @@ int tty_insert_flip_string_fixed_flag(struct tty_port *port, if (unlikely(space == 0)) { break; } - memcpy(tb->char_buf_ptr + tb->used, chars, space); - memset(tb->flag_buf_ptr + tb->used, flag, space); + memcpy(char_buf_ptr(tb, tb->used), chars, space); + memset(flag_buf_ptr(tb, tb->used), flag, space); tb->used += space; copied += space; chars += space; @@ -303,8 +301,8 @@ int tty_insert_flip_string_flags(struct tty_port *port, if (unlikely(space == 0)) { break; } - memcpy(tb->char_buf_ptr + tb->used, chars, space); - memcpy(tb->flag_buf_ptr + tb->used, flags, space); + memcpy(char_buf_ptr(tb, tb->used), chars, space); + memcpy(flag_buf_ptr(tb, tb->used), flags, space); tb->used += space; copied += space; chars += space; @@ -364,8 +362,8 @@ int tty_prepare_flip_string(struct tty_port *port, unsigned char **chars, int space = tty_buffer_request_room(port, size); if (likely(space)) { struct tty_buffer *tb = port->buf.tail; - *chars = tb->char_buf_ptr + tb->used; - memset(tb->flag_buf_ptr + tb->used, TTY_NORMAL, space); + *chars = char_buf_ptr(tb, tb->used); + memset(flag_buf_ptr(tb, tb->used), TTY_NORMAL, space); tb->used += space; } return space; @@ -394,8 +392,8 @@ int tty_prepare_flip_string_flags(struct tty_port *port, int space = tty_buffer_request_room(port, size); if (likely(space)) { struct tty_buffer *tb = port->buf.tail; - *chars = tb->char_buf_ptr + tb->used; - *flags = tb->flag_buf_ptr + tb->used; + *chars = char_buf_ptr(tb, tb->used); + *flags = flag_buf_ptr(tb, tb->used); tb->used += space; } return space; @@ -407,8 +405,8 @@ static int receive_buf(struct tty_struct *tty, struct tty_buffer *head, int count) { struct tty_ldisc *disc = tty->ldisc; - char *p = head->char_buf_ptr + head->read; - unsigned char *f = head->flag_buf_ptr + head->read; + unsigned char *p = char_buf_ptr(head, head->read); + char *f = flag_buf_ptr(head, head->read); if (disc->ops->receive_buf2) count = disc->ops->receive_buf2(tty, p, f, count); diff --git a/include/linux/tty.h b/include/linux/tty.h index 57a70d1d0412..87bbaa31ebf5 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -31,8 +31,6 @@ struct tty_buffer { struct tty_buffer *next; - char *char_buf_ptr; - unsigned char *flag_buf_ptr; int used; int size; int commit; @@ -41,6 +39,16 @@ struct tty_buffer { unsigned long data[0]; }; +static inline unsigned char *char_buf_ptr(struct tty_buffer *b, int ofs) +{ + return ((unsigned char *)b->data) + ofs; +} + +static inline char *flag_buf_ptr(struct tty_buffer *b, int ofs) +{ + return (char *)char_buf_ptr(b, ofs) + b->size; +} + /* * We default to dicing tty buffer allocations to this many characters * in order to avoid multiple page allocations. We know the size of diff --git a/include/linux/tty_flip.h b/include/linux/tty_flip.h index e0f252633b47..ad0303994c32 100644 --- a/include/linux/tty_flip.h +++ b/include/linux/tty_flip.h @@ -18,8 +18,8 @@ static inline int tty_insert_flip_char(struct tty_port *port, { struct tty_buffer *tb = port->buf.tail; if (tb && tb->used < tb->size) { - tb->flag_buf_ptr[tb->used] = flag; - tb->char_buf_ptr[tb->used++] = ch; + *flag_buf_ptr(tb, tb->used) = flag; + *char_buf_ptr(tb, tb->used++) = ch; return 1; } return tty_insert_flip_string_flags(port, &ch, &flag, 1); -- cgit v1.2.3 From 809850b7a5fcc0a96d023e1171a7944c60fd5a71 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Sat, 15 Jun 2013 09:36:06 -0400 Subject: tty: Use lockless flip buffer free list In preparation for lockless flip buffers, make the flip buffer free list lockless. NB: using llist is not the optimal solution, as the driver and buffer work may contend over the llist head unnecessarily. However, test measurements indicate this contention is low. Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/tty_buffer.c | 29 ++++++++++++----------------- include/linux/llist.h | 23 +++++++++++++++++++++++ include/linux/tty.h | 8 ++++++-- 3 files changed, 41 insertions(+), 19 deletions(-) (limited to 'include/linux') diff --git a/drivers/tty/tty_buffer.c b/drivers/tty/tty_buffer.c index 0259a766b875..069640e5b9c0 100644 --- a/drivers/tty/tty_buffer.c +++ b/drivers/tty/tty_buffer.c @@ -44,16 +44,17 @@ static void tty_buffer_reset(struct tty_buffer *p, size_t size) void tty_buffer_free_all(struct tty_port *port) { struct tty_bufhead *buf = &port->buf; - struct tty_buffer *p; + struct tty_buffer *p, *next; + struct llist_node *llist; while ((p = buf->head) != NULL) { buf->head = p->next; kfree(p); } - while ((p = buf->free) != NULL) { - buf->free = p->next; + llist = llist_del_all(&buf->free); + llist_for_each_entry_safe(p, next, llist, free) kfree(p); - } + buf->tail = NULL; buf->memory_used = 0; } @@ -68,22 +69,20 @@ void tty_buffer_free_all(struct tty_port *port) * allocation behaviour. * Return NULL if out of memory or the allocation would exceed the * per device queue - * - * Locking: Caller must hold tty->buf.lock */ static struct tty_buffer *tty_buffer_alloc(struct tty_port *port, size_t size) { - struct tty_buffer **tbh = &port->buf.free; + struct llist_node *free; struct tty_buffer *p; /* Round the buffer size out */ size = __ALIGN_MASK(size, TTYB_ALIGN_MASK); if (size <= MIN_TTYB_SIZE) { - if (*tbh) { - p = *tbh; - *tbh = p->next; + free = llist_del_first(&port->buf.free); + if (free) { + p = llist_entry(free, struct tty_buffer, free); goto found; } } @@ -109,8 +108,6 @@ found: * * Free a tty buffer, or add it to the free list according to our * internal strategy - * - * Locking: Caller must hold tty->buf.lock */ static void tty_buffer_free(struct tty_port *port, struct tty_buffer *b) @@ -123,10 +120,8 @@ static void tty_buffer_free(struct tty_port *port, struct tty_buffer *b) if (b->size > MIN_TTYB_SIZE) kfree(b); - else { - b->next = buf->free; - buf->free = b; - } + else + llist_add(&b->free, &buf->free); } /** @@ -542,7 +537,7 @@ void tty_buffer_init(struct tty_port *port) spin_lock_init(&buf->lock); buf->head = NULL; buf->tail = NULL; - buf->free = NULL; + init_llist_head(&buf->free); buf->memory_used = 0; INIT_WORK(&buf->work, flush_to_ldisc); } diff --git a/include/linux/llist.h b/include/linux/llist.h index cdaa7f023899..8828a78dec9a 100644 --- a/include/linux/llist.h +++ b/include/linux/llist.h @@ -124,6 +124,29 @@ static inline void init_llist_head(struct llist_head *list) &(pos)->member != NULL; \ (pos) = llist_entry((pos)->member.next, typeof(*(pos)), member)) +/** + * llist_for_each_entry_safe - iterate over some deleted entries of lock-less list of given type + * safe against removal of list entry + * @pos: the type * to use as a loop cursor. + * @n: another type * to use as temporary storage + * @node: the first entry of deleted list entries. + * @member: the name of the llist_node with the struct. + * + * In general, some entries of the lock-less list can be traversed + * safely only after being removed from list, so start with an entry + * instead of list head. + * + * If being used on entries deleted from lock-less list directly, the + * traverse order is from the newest to the oldest added entry. If + * you want to traverse from the oldest to the newest, you must + * reverse the order by yourself before traversing. + */ +#define llist_for_each_entry_safe(pos, n, node, member) \ + for (pos = llist_entry((node), typeof(*pos), member); \ + &pos->member != NULL && \ + (n = llist_entry(pos->member.next, typeof(*n), member), true); \ + pos = n) + /** * llist_empty - tests whether a lock-less list is empty * @head: the list to test diff --git a/include/linux/tty.h b/include/linux/tty.h index 87bbaa31ebf5..5043b12f23ea 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -11,6 +11,7 @@ #include #include #include +#include @@ -30,7 +31,10 @@ #define __DISABLED_CHAR '\0' struct tty_buffer { - struct tty_buffer *next; + union { + struct tty_buffer *next; + struct llist_node free; + }; int used; int size; int commit; @@ -65,7 +69,7 @@ struct tty_bufhead { spinlock_t lock; struct tty_buffer *head; /* Queue head */ struct tty_buffer *tail; /* Active buffer */ - struct tty_buffer *free; /* Free queue head */ + struct llist_head free; /* Free queue head */ int memory_used; /* Buffer space used excluding free queue */ }; -- cgit v1.2.3 From 7391ee16950e772076d321792d9fbf030f921345 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Sat, 15 Jun 2013 09:36:07 -0400 Subject: tty: Simplify flip buffer list with 0-sized sentinel Use a 0-sized sentinel to avoid assigning the head ptr from the driver side thread. This also eliminates testing head/tail for NULL. When the sentinel is first 'consumed' by the buffer work (or by tty_buffer_flush()), it is detached from the list but not freed nor added to the free list. Both buffer work and tty_buffer_flush() continue to preserve at least 1 flip buffer to which head & tail is pointed. Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/tty_buffer.c | 49 ++++++++++++++++++------------------------------ include/linux/tty.h | 1 + 2 files changed, 19 insertions(+), 31 deletions(-) (limited to 'include/linux') diff --git a/drivers/tty/tty_buffer.c b/drivers/tty/tty_buffer.c index 069640e5b9c0..231b7a8710f1 100644 --- a/drivers/tty/tty_buffer.c +++ b/drivers/tty/tty_buffer.c @@ -49,13 +49,16 @@ void tty_buffer_free_all(struct tty_port *port) while ((p = buf->head) != NULL) { buf->head = p->next; - kfree(p); + if (p->size > 0) + kfree(p); } llist = llist_del_all(&buf->free); llist_for_each_entry_safe(p, next, llist, free) kfree(p); - buf->tail = NULL; + tty_buffer_reset(&buf->sentinel, 0); + buf->head = &buf->sentinel; + buf->tail = &buf->sentinel; buf->memory_used = 0; } @@ -120,7 +123,7 @@ static void tty_buffer_free(struct tty_port *port, struct tty_buffer *b) if (b->size > MIN_TTYB_SIZE) kfree(b); - else + else if (b->size > 0) llist_add(&b->free, &buf->free); } @@ -140,8 +143,6 @@ static void __tty_buffer_flush(struct tty_port *port) struct tty_bufhead *buf = &port->buf; struct tty_buffer *next; - if (unlikely(buf->head == NULL)) - return; while ((next = buf->head->next) != NULL) { tty_buffer_free(port, buf->head); buf->head = next; @@ -200,23 +201,14 @@ int tty_buffer_request_room(struct tty_port *port, size_t size) int left; unsigned long flags; spin_lock_irqsave(&buf->lock, flags); - /* OPTIMISATION: We could keep a per tty "zero" sized buffer to - remove this conditional if its worth it. This would be invisible - to the callers */ b = buf->tail; - if (b != NULL) - left = b->size - b->used; - else - left = 0; + left = b->size - b->used; if (left < size) { /* This is the slow path - looking for new buffers to use */ if ((n = tty_buffer_alloc(port, size)) != NULL) { - if (b != NULL) { - b->next = n; - b->commit = b->used; - } else - buf->head = n; + b->next = n; + b->commit = b->used; buf->tail = n; } else size = left; @@ -247,10 +239,8 @@ int tty_insert_flip_string_fixed_flag(struct tty_port *port, int goal = min_t(size_t, size - copied, TTY_BUFFER_PAGE); int space = tty_buffer_request_room(port, goal); struct tty_buffer *tb = port->buf.tail; - /* If there is no space then tb may be NULL */ - if (unlikely(space == 0)) { + if (unlikely(space == 0)) break; - } memcpy(char_buf_ptr(tb, tb->used), chars, space); memset(flag_buf_ptr(tb, tb->used), flag, space); tb->used += space; @@ -285,10 +275,8 @@ int tty_insert_flip_string_flags(struct tty_port *port, int goal = min_t(size_t, size - copied, TTY_BUFFER_PAGE); int space = tty_buffer_request_room(port, goal); struct tty_buffer *tb = port->buf.tail; - /* If there is no space then tb may be NULL */ - if (unlikely(space == 0)) { + if (unlikely(space == 0)) break; - } memcpy(char_buf_ptr(tb, tb->used), chars, space); memcpy(flag_buf_ptr(tb, tb->used), flags, space); tb->used += space; @@ -322,8 +310,7 @@ void tty_schedule_flip(struct tty_port *port) WARN_ON(port->low_latency); spin_lock_irqsave(&buf->lock, flags); - if (buf->tail != NULL) - buf->tail->commit = buf->tail->used; + buf->tail->commit = buf->tail->used; spin_unlock_irqrestore(&buf->lock, flags); schedule_work(&buf->work); } @@ -438,8 +425,8 @@ static void flush_to_ldisc(struct work_struct *work) spin_lock_irqsave(&buf->lock, flags); if (!test_and_set_bit(TTYP_FLUSHING, &port->iflags)) { - struct tty_buffer *head; - while ((head = buf->head) != NULL) { + while (1) { + struct tty_buffer *head = buf->head; int count; count = head->commit - head->read; @@ -509,8 +496,7 @@ void tty_flip_buffer_push(struct tty_port *port) unsigned long flags; spin_lock_irqsave(&buf->lock, flags); - if (buf->tail != NULL) - buf->tail->commit = buf->tail->used; + buf->tail->commit = buf->tail->used; spin_unlock_irqrestore(&buf->lock, flags); if (port->low_latency) @@ -535,8 +521,9 @@ void tty_buffer_init(struct tty_port *port) struct tty_bufhead *buf = &port->buf; spin_lock_init(&buf->lock); - buf->head = NULL; - buf->tail = NULL; + tty_buffer_reset(&buf->sentinel, 0); + buf->head = &buf->sentinel; + buf->tail = &buf->sentinel; init_llist_head(&buf->free); buf->memory_used = 0; INIT_WORK(&buf->work, flush_to_ldisc); diff --git a/include/linux/tty.h b/include/linux/tty.h index 5043b12f23ea..2e93eb831c61 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -67,6 +67,7 @@ static inline char *flag_buf_ptr(struct tty_buffer *b, int ofs) struct tty_bufhead { struct work_struct work; spinlock_t lock; + struct tty_buffer sentinel; struct tty_buffer *head; /* Queue head */ struct tty_buffer *tail; /* Active buffer */ struct llist_head free; /* Free queue head */ -- cgit v1.2.3 From 7bfe0b7116be207cf2204ae06335cc89d8f8ee02 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Sat, 15 Jun 2013 09:36:08 -0400 Subject: tty: Track flip buffer memory limit atomically Lockless flip buffers require atomically updating the bytes-in-use watermark. The pty driver also peeks at the watermark value to limit memory consumption to a much lower value than the default; query the watermark with new fn, tty_buffer_space_avail(). Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/pty.c | 10 +++------- drivers/tty/tty_buffer.c | 37 +++++++++++++++++++++++++++++++------ include/linux/tty.h | 3 +-- include/linux/tty_flip.h | 1 + 4 files changed, 36 insertions(+), 15 deletions(-) (limited to 'include/linux') diff --git a/drivers/tty/pty.c b/drivers/tty/pty.c index 1b39dd639ee9..b38a28bd9511 100644 --- a/drivers/tty/pty.c +++ b/drivers/tty/pty.c @@ -89,17 +89,13 @@ static void pty_unthrottle(struct tty_struct *tty) * pty_space - report space left for writing * @to: tty we are writing into * - * The tty buffers allow 64K but we sneak a peak and clip at 8K this - * allows a lot of overspill room for echo and other fun messes to - * be handled properly + * Limit the buffer space used by ptys to 8k. */ static int pty_space(struct tty_struct *to) { - int n = 8192 - to->port->buf.memory_used; - if (n < 0) - return 0; - return n; + int n = tty_buffer_space_avail(to->port); + return min(n, 8192); } /** diff --git a/drivers/tty/tty_buffer.c b/drivers/tty/tty_buffer.c index 231b7a8710f1..5d5a56407aa8 100644 --- a/drivers/tty/tty_buffer.c +++ b/drivers/tty/tty_buffer.c @@ -22,6 +22,31 @@ #define MIN_TTYB_SIZE 256 #define TTYB_ALIGN_MASK 255 +/* + * Byte threshold to limit memory consumption for flip buffers. + * The actual memory limit is > 2x this amount. + */ +#define TTYB_MEM_LIMIT 65536 + + +/** + * tty_buffer_space_avail - return unused buffer space + * @port - tty_port owning the flip buffer + * + * Returns the # of bytes which can be written by the driver without + * reaching the buffer limit. + * + * Note: this does not guarantee that memory is available to write + * the returned # of bytes (use tty_prepare_flip_string_xxx() to + * pre-allocate if memory guarantee is required). + */ + +int tty_buffer_space_avail(struct tty_port *port) +{ + int space = TTYB_MEM_LIMIT - atomic_read(&port->buf.memory_used); + return max(space, 0); +} + static void tty_buffer_reset(struct tty_buffer *p, size_t size) { p->used = 0; @@ -59,7 +84,8 @@ void tty_buffer_free_all(struct tty_port *port) tty_buffer_reset(&buf->sentinel, 0); buf->head = &buf->sentinel; buf->tail = &buf->sentinel; - buf->memory_used = 0; + + atomic_set(&buf->memory_used, 0); } /** @@ -92,7 +118,7 @@ static struct tty_buffer *tty_buffer_alloc(struct tty_port *port, size_t size) /* Should possibly check if this fails for the largest buffer we have queued and recycle that ? */ - if (port->buf.memory_used + size > 65536) + if (atomic_read(&port->buf.memory_used) > TTYB_MEM_LIMIT) return NULL; p = kmalloc(sizeof(struct tty_buffer) + 2 * size, GFP_ATOMIC); if (p == NULL) @@ -100,7 +126,7 @@ static struct tty_buffer *tty_buffer_alloc(struct tty_port *port, size_t size) found: tty_buffer_reset(p, size); - port->buf.memory_used += size; + atomic_add(size, &port->buf.memory_used); return p; } @@ -118,8 +144,7 @@ static void tty_buffer_free(struct tty_port *port, struct tty_buffer *b) struct tty_bufhead *buf = &port->buf; /* Dumb strategy for now - should keep some stats */ - buf->memory_used -= b->size; - WARN_ON(buf->memory_used < 0); + WARN_ON(atomic_sub_return(b->size, &buf->memory_used) < 0); if (b->size > MIN_TTYB_SIZE) kfree(b); @@ -525,7 +550,7 @@ void tty_buffer_init(struct tty_port *port) buf->head = &buf->sentinel; buf->tail = &buf->sentinel; init_llist_head(&buf->free); - buf->memory_used = 0; + atomic_set(&buf->memory_used, 0); INIT_WORK(&buf->work, flush_to_ldisc); } diff --git a/include/linux/tty.h b/include/linux/tty.h index 2e93eb831c61..7c124541f011 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -71,8 +71,7 @@ struct tty_bufhead { struct tty_buffer *head; /* Queue head */ struct tty_buffer *tail; /* Active buffer */ struct llist_head free; /* Free queue head */ - int memory_used; /* Buffer space used excluding - free queue */ + atomic_t memory_used; /* In-use buffers excluding free list */ }; /* * When a break, frame error, or parity error happens, these codes are diff --git a/include/linux/tty_flip.h b/include/linux/tty_flip.h index ad0303994c32..6944ed2ce692 100644 --- a/include/linux/tty_flip.h +++ b/include/linux/tty_flip.h @@ -1,6 +1,7 @@ #ifndef _LINUX_TTY_FLIP_H #define _LINUX_TTY_FLIP_H +extern int tty_buffer_space_avail(struct tty_port *port); extern int tty_buffer_request_room(struct tty_port *port, size_t size); extern int tty_insert_flip_string_flags(struct tty_port *port, const unsigned char *chars, const char *flags, size_t size); -- cgit v1.2.3 From e9975fdec0138f1b2a85b9624e41660abd9865d4 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Sat, 15 Jun 2013 09:36:10 -0400 Subject: tty: Ensure single-threaded flip buffer consumer with mutex The buffer work may race with parallel tty_buffer_flush. Use a mutex to guarantee exclusive modify access to the head flip buffer. Remove the unneeded spin lock. Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/staging/dgrp/dgrp_tty.c | 2 ++ drivers/tty/tty_buffer.c | 40 +++++++++++++++++++--------------------- include/linux/tty.h | 2 +- 3 files changed, 22 insertions(+), 22 deletions(-) (limited to 'include/linux') diff --git a/drivers/staging/dgrp/dgrp_tty.c b/drivers/staging/dgrp/dgrp_tty.c index 654f6010b473..0d52de3729c6 100644 --- a/drivers/staging/dgrp/dgrp_tty.c +++ b/drivers/staging/dgrp/dgrp_tty.c @@ -1120,7 +1120,9 @@ static void dgrp_tty_close(struct tty_struct *tty, struct file *file) if (!sent_printer_offstr) dgrp_tty_flush_buffer(tty); + spin_unlock_irqrestore(&nd->nd_lock, lock_flags); tty_ldisc_flush(tty); + spin_lock_irqsave(&nd->nd_lock, lock_flags); break; } diff --git a/drivers/tty/tty_buffer.c b/drivers/tty/tty_buffer.c index 685757c6ce87..c3c606c52722 100644 --- a/drivers/tty/tty_buffer.c +++ b/drivers/tty/tty_buffer.c @@ -157,8 +157,6 @@ static void tty_buffer_free(struct tty_port *port, struct tty_buffer *b) * flush all the buffers containing receive data. Caller must * hold the buffer lock and must have ensured no parallel flush to * ldisc is running. - * - * Locking: Caller must hold tty->buf.lock */ static void __tty_buffer_flush(struct tty_port *port) @@ -182,29 +180,29 @@ static void __tty_buffer_flush(struct tty_port *port) * being processed by flush_to_ldisc then we defer the processing * to that function * - * Locking: none + * Locking: takes flush_mutex to ensure single-threaded flip buffer + * 'consumer' */ void tty_buffer_flush(struct tty_struct *tty) { struct tty_port *port = tty->port; struct tty_bufhead *buf = &port->buf; - unsigned long flags; - - spin_lock_irqsave(&buf->lock, flags); + mutex_lock(&buf->flush_mutex); /* If the data is being pushed to the tty layer then we can't process it here. Instead set a flag and the flush_to_ldisc path will process the flush request before it exits */ if (test_bit(TTYP_FLUSHING, &port->iflags)) { set_bit(TTYP_FLUSHPENDING, &port->iflags); - spin_unlock_irqrestore(&buf->lock, flags); + mutex_unlock(&buf->flush_mutex); wait_event(tty->read_wait, test_bit(TTYP_FLUSHPENDING, &port->iflags) == 0); return; - } else - __tty_buffer_flush(port); - spin_unlock_irqrestore(&buf->lock, flags); + } + + __tty_buffer_flush(port); + mutex_unlock(&buf->flush_mutex); } /** @@ -408,9 +406,10 @@ receive_buf(struct tty_struct *tty, struct tty_buffer *head, int count) * This routine is called out of the software interrupt to flush data * from the buffer chain to the line discipline. * - * Locking: holds tty->buf.lock to guard buffer list. Drops the lock - * while invoking the line discipline receive_buf method. The - * receive_buf method is single threaded for each tty instance. + * The receive_buf method is single threaded for each tty instance. + * + * Locking: takes flush_mutex to ensure single-threaded flip buffer + * 'consumer' */ static void flush_to_ldisc(struct work_struct *work) @@ -418,7 +417,6 @@ static void flush_to_ldisc(struct work_struct *work) struct tty_port *port = container_of(work, struct tty_port, buf.work); struct tty_bufhead *buf = &port->buf; struct tty_struct *tty; - unsigned long flags; struct tty_ldisc *disc; tty = port->itty; @@ -429,7 +427,7 @@ static void flush_to_ldisc(struct work_struct *work) if (disc == NULL) return; - spin_lock_irqsave(&buf->lock, flags); + mutex_lock(&buf->flush_mutex); if (!test_and_set_bit(TTYP_FLUSHING, &port->iflags)) { while (1) { @@ -444,11 +442,13 @@ static void flush_to_ldisc(struct work_struct *work) tty_buffer_free(port, head); continue; } - spin_unlock_irqrestore(&buf->lock, flags); + + mutex_unlock(&buf->flush_mutex); count = receive_buf(tty, head, count); - spin_lock_irqsave(&buf->lock, flags); + mutex_lock(&buf->flush_mutex); + /* Ldisc or user is trying to flush the buffers. We may have a deferred request to flush the input buffer, if so pull the chain under the lock @@ -464,7 +464,7 @@ static void flush_to_ldisc(struct work_struct *work) clear_bit(TTYP_FLUSHING, &port->iflags); } - spin_unlock_irqrestore(&buf->lock, flags); + mutex_unlock(&buf->flush_mutex); tty_ldisc_deref(disc); } @@ -514,15 +514,13 @@ EXPORT_SYMBOL(tty_flip_buffer_push); * * Set up the initial state of the buffer management for a tty device. * Must be called before the other tty buffer functions are used. - * - * Locking: none */ void tty_buffer_init(struct tty_port *port) { struct tty_bufhead *buf = &port->buf; - spin_lock_init(&buf->lock); + mutex_init(&buf->flush_mutex); tty_buffer_reset(&buf->sentinel, 0); buf->head = &buf->sentinel; buf->tail = &buf->sentinel; diff --git a/include/linux/tty.h b/include/linux/tty.h index 7c124541f011..1c8fef0e3ff6 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -66,7 +66,7 @@ static inline char *flag_buf_ptr(struct tty_buffer *b, int ofs) struct tty_bufhead { struct work_struct work; - spinlock_t lock; + struct mutex flush_mutex; struct tty_buffer sentinel; struct tty_buffer *head; /* Queue head */ struct tty_buffer *tail; /* Active buffer */ -- cgit v1.2.3 From d7a68be4f265be10e24be931c257af30ca55566b Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Sat, 15 Jun 2013 09:36:11 -0400 Subject: tty: Only perform flip buffer flush from tty_buffer_flush() Now that dropping the buffer lock is not necessary (as result of converting the spin lock to a mutex), the flip buffer flush no longer needs to be handled by the buffer work. Simply signal a flush is required; the buffer work will exit the i/o loop, which allows tty_buffer_flush() to proceed. Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/tty_buffer.c | 63 ++++++++++++++++-------------------------------- include/linux/tty.h | 1 - 2 files changed, 21 insertions(+), 43 deletions(-) (limited to 'include/linux') diff --git a/drivers/tty/tty_buffer.c b/drivers/tty/tty_buffer.c index c3c606c52722..39cae611fe59 100644 --- a/drivers/tty/tty_buffer.c +++ b/drivers/tty/tty_buffer.c @@ -189,19 +189,11 @@ void tty_buffer_flush(struct tty_struct *tty) struct tty_port *port = tty->port; struct tty_bufhead *buf = &port->buf; - mutex_lock(&buf->flush_mutex); - /* If the data is being pushed to the tty layer then we can't - process it here. Instead set a flag and the flush_to_ldisc - path will process the flush request before it exits */ - if (test_bit(TTYP_FLUSHING, &port->iflags)) { - set_bit(TTYP_FLUSHPENDING, &port->iflags); - mutex_unlock(&buf->flush_mutex); - wait_event(tty->read_wait, - test_bit(TTYP_FLUSHPENDING, &port->iflags) == 0); - return; - } + set_bit(TTYP_FLUSHPENDING, &port->iflags); + mutex_lock(&buf->flush_mutex); __tty_buffer_flush(port); + clear_bit(TTYP_FLUSHPENDING, &port->iflags); mutex_unlock(&buf->flush_mutex); } @@ -429,39 +421,26 @@ static void flush_to_ldisc(struct work_struct *work) mutex_lock(&buf->flush_mutex); - if (!test_and_set_bit(TTYP_FLUSHING, &port->iflags)) { - while (1) { - struct tty_buffer *head = buf->head; - int count; - - count = head->commit - head->read; - if (!count) { - if (head->next == NULL) - break; - buf->head = head->next; - tty_buffer_free(port, head); - continue; - } - - mutex_unlock(&buf->flush_mutex); - - count = receive_buf(tty, head, count); - - mutex_lock(&buf->flush_mutex); - - /* Ldisc or user is trying to flush the buffers. - We may have a deferred request to flush the - input buffer, if so pull the chain under the lock - and empty the queue */ - if (test_bit(TTYP_FLUSHPENDING, &port->iflags)) { - __tty_buffer_flush(port); - clear_bit(TTYP_FLUSHPENDING, &port->iflags); - wake_up(&tty->read_wait); - break; - } else if (!count) + while (1) { + struct tty_buffer *head = buf->head; + int count; + + /* Ldisc or user is trying to flush the buffers. */ + if (test_bit(TTYP_FLUSHPENDING, &port->iflags)) + break; + + count = head->commit - head->read; + if (!count) { + if (head->next == NULL) break; + buf->head = head->next; + tty_buffer_free(port, head); + continue; } - clear_bit(TTYP_FLUSHING, &port->iflags); + + count = receive_buf(tty, head, count); + if (!count) + break; } mutex_unlock(&buf->flush_mutex); diff --git a/include/linux/tty.h b/include/linux/tty.h index 1c8fef0e3ff6..1d5bacca3652 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -213,7 +213,6 @@ struct tty_port { wait_queue_head_t delta_msr_wait; /* Modem status change */ unsigned long flags; /* TTY flags ASY_*/ unsigned long iflags; /* TTYP_ internal flags */ -#define TTYP_FLUSHING 1 /* Flushing to ldisc in progress */ #define TTYP_FLUSHPENDING 2 /* Queued buffer flush pending */ unsigned char console:1, /* port is a console */ low_latency:1; /* direct buffer flush */ -- cgit v1.2.3 From 8c1fb49ba107c7db9441ef6ec0ab5830d112cc2a Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Sat, 15 Jun 2013 09:36:12 -0400 Subject: tty: Avoid false-sharing flip buffer ptrs Separate the head and tail ptrs to avoid cache-line contention (so called 'false-sharing') between concurrent threads. Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- include/linux/tty.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'include/linux') diff --git a/include/linux/tty.h b/include/linux/tty.h index 1d5bacca3652..b8e8adf95bf3 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -65,13 +65,13 @@ static inline char *flag_buf_ptr(struct tty_buffer *b, int ofs) struct tty_bufhead { + struct tty_buffer *head; /* Queue head */ struct work_struct work; struct mutex flush_mutex; struct tty_buffer sentinel; - struct tty_buffer *head; /* Queue head */ - struct tty_buffer *tail; /* Active buffer */ struct llist_head free; /* Free queue head */ atomic_t memory_used; /* In-use buffers excluding free list */ + struct tty_buffer *tail; /* Active buffer */ }; /* * When a break, frame error, or parity error happens, these codes are -- cgit v1.2.3 From 0f56bd2f6a97d8b0eb5c8f9bc04b83a6c16d1d48 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Sat, 15 Jun 2013 09:36:13 -0400 Subject: tty: Use non-atomic state to signal flip buffer flush pending Atomic bit ops are no longer required to indicate a flip buffer flush is pending, as the flush_mutex is sufficient barrier. Remove the unnecessary port .iflags field and localize flip buffer state to struct tty_bufhead. Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/tty_buffer.c | 7 ++++--- include/linux/tty.h | 3 +-- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'include/linux') diff --git a/drivers/tty/tty_buffer.c b/drivers/tty/tty_buffer.c index 39cae611fe59..fb042b9a8d68 100644 --- a/drivers/tty/tty_buffer.c +++ b/drivers/tty/tty_buffer.c @@ -189,11 +189,11 @@ void tty_buffer_flush(struct tty_struct *tty) struct tty_port *port = tty->port; struct tty_bufhead *buf = &port->buf; - set_bit(TTYP_FLUSHPENDING, &port->iflags); + buf->flushpending = 1; mutex_lock(&buf->flush_mutex); __tty_buffer_flush(port); - clear_bit(TTYP_FLUSHPENDING, &port->iflags); + buf->flushpending = 0; mutex_unlock(&buf->flush_mutex); } @@ -426,7 +426,7 @@ static void flush_to_ldisc(struct work_struct *work) int count; /* Ldisc or user is trying to flush the buffers. */ - if (test_bit(TTYP_FLUSHPENDING, &port->iflags)) + if (buf->flushpending) break; count = head->commit - head->read; @@ -505,6 +505,7 @@ void tty_buffer_init(struct tty_port *port) buf->tail = &buf->sentinel; init_llist_head(&buf->free); atomic_set(&buf->memory_used, 0); + buf->flushpending = 0; INIT_WORK(&buf->work, flush_to_ldisc); } diff --git a/include/linux/tty.h b/include/linux/tty.h index b8e8adf95bf3..991575fe3451 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -68,6 +68,7 @@ struct tty_bufhead { struct tty_buffer *head; /* Queue head */ struct work_struct work; struct mutex flush_mutex; + unsigned int flushpending:1; struct tty_buffer sentinel; struct llist_head free; /* Free queue head */ atomic_t memory_used; /* In-use buffers excluding free list */ @@ -212,8 +213,6 @@ struct tty_port { wait_queue_head_t close_wait; /* Close waiters */ wait_queue_head_t delta_msr_wait; /* Modem status change */ unsigned long flags; /* TTY flags ASY_*/ - unsigned long iflags; /* TTYP_ internal flags */ -#define TTYP_FLUSHPENDING 2 /* Queued buffer flush pending */ unsigned char console:1, /* port is a console */ low_latency:1; /* direct buffer flush */ struct mutex mutex; /* Locking */ -- cgit v1.2.3 From a7c8d58c79853adeebf0a1ddc9c63e433b4d97f1 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Sat, 15 Jun 2013 09:36:15 -0400 Subject: tty: Fix unsafe vt paste_selection() Convert the tty_buffer_flush() exclusion mechanism to a public interface - tty_buffer_lock/unlock_exclusive() - and use the interface to safely write the paste selection to the line discipline. Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/tty_buffer.c | 61 ++++++++++++++++++++++++++++++++++++---------- drivers/tty/vt/selection.c | 4 ++- include/linux/tty.h | 4 +-- include/linux/tty_flip.h | 3 +++ 4 files changed, 56 insertions(+), 16 deletions(-) (limited to 'include/linux') diff --git a/drivers/tty/tty_buffer.c b/drivers/tty/tty_buffer.c index dbe4a718e2dc..f22e116db105 100644 --- a/drivers/tty/tty_buffer.c +++ b/drivers/tty/tty_buffer.c @@ -29,6 +29,42 @@ #define TTYB_MEM_LIMIT 65536 +/** + * tty_buffer_lock_exclusive - gain exclusive access to buffer + * tty_buffer_unlock_exclusive - release exclusive access + * + * @port - tty_port owning the flip buffer + * + * Guarantees safe use of the line discipline's receive_buf() method by + * excluding the buffer work and any pending flush from using the flip + * buffer. Data can continue to be added concurrently to the flip buffer + * from the driver side. + * + * On release, the buffer work is restarted if there is data in the + * flip buffer + */ + +void tty_buffer_lock_exclusive(struct tty_port *port) +{ + struct tty_bufhead *buf = &port->buf; + + atomic_inc(&buf->priority); + mutex_lock(&buf->lock); +} + +void tty_buffer_unlock_exclusive(struct tty_port *port) +{ + struct tty_bufhead *buf = &port->buf; + int restart; + + restart = buf->head->commit != buf->head->read; + + atomic_dec(&buf->priority); + mutex_unlock(&buf->lock); + if (restart) + queue_work(system_unbound_wq, &buf->work); +} + /** * tty_buffer_space_avail - return unused buffer space * @port - tty_port owning the flip buffer @@ -158,7 +194,7 @@ static void tty_buffer_free(struct tty_port *port, struct tty_buffer *b) * being processed by flush_to_ldisc then we defer the processing * to that function * - * Locking: takes flush_mutex to ensure single-threaded flip buffer + * Locking: takes buffer lock to ensure single-threaded flip buffer * 'consumer' */ @@ -168,16 +204,16 @@ void tty_buffer_flush(struct tty_struct *tty) struct tty_bufhead *buf = &port->buf; struct tty_buffer *next; - buf->flushpending = 1; + atomic_inc(&buf->priority); - mutex_lock(&buf->flush_mutex); + mutex_lock(&buf->lock); while ((next = buf->head->next) != NULL) { tty_buffer_free(port, buf->head); buf->head = next; } buf->head->read = buf->head->commit; - buf->flushpending = 0; - mutex_unlock(&buf->flush_mutex); + atomic_dec(&buf->priority); + mutex_unlock(&buf->lock); } /** @@ -383,7 +419,7 @@ receive_buf(struct tty_struct *tty, struct tty_buffer *head, int count) * * The receive_buf method is single threaded for each tty instance. * - * Locking: takes flush_mutex to ensure single-threaded flip buffer + * Locking: takes buffer lock to ensure single-threaded flip buffer * 'consumer' */ @@ -402,14 +438,14 @@ static void flush_to_ldisc(struct work_struct *work) if (disc == NULL) return; - mutex_lock(&buf->flush_mutex); + mutex_lock(&buf->lock); while (1) { struct tty_buffer *head = buf->head; int count; - /* Ldisc or user is trying to flush the buffers. */ - if (buf->flushpending) + /* Ldisc or user is trying to gain exclusive access */ + if (atomic_read(&buf->priority)) break; count = head->commit - head->read; @@ -426,7 +462,7 @@ static void flush_to_ldisc(struct work_struct *work) break; } - mutex_unlock(&buf->flush_mutex); + mutex_unlock(&buf->lock); tty_ldisc_deref(disc); } @@ -482,13 +518,12 @@ void tty_buffer_init(struct tty_port *port) { struct tty_bufhead *buf = &port->buf; - mutex_init(&buf->flush_mutex); + mutex_init(&buf->lock); tty_buffer_reset(&buf->sentinel, 0); buf->head = &buf->sentinel; buf->tail = &buf->sentinel; init_llist_head(&buf->free); atomic_set(&buf->memory_used, 0); - buf->flushpending = 0; + atomic_set(&buf->priority, 0); INIT_WORK(&buf->work, flush_to_ldisc); } - diff --git a/drivers/tty/vt/selection.c b/drivers/tty/vt/selection.c index 2ca8d6b6514c..ea27804d87af 100644 --- a/drivers/tty/vt/selection.c +++ b/drivers/tty/vt/selection.c @@ -24,6 +24,7 @@ #include #include #include +#include /* Don't take this from : 011-015 on the screen aren't spaces */ #define isspace(c) ((c) == ' ') @@ -346,8 +347,8 @@ int paste_selection(struct tty_struct *tty) console_unlock(); ld = tty_ldisc_ref_wait(tty); + tty_buffer_lock_exclusive(&vc->port); - /* FIXME: this is completely unsafe */ add_wait_queue(&vc->paste_wait, &wait); while (sel_buffer && sel_buffer_lth > pasted) { set_current_state(TASK_INTERRUPTIBLE); @@ -363,6 +364,7 @@ int paste_selection(struct tty_struct *tty) remove_wait_queue(&vc->paste_wait, &wait); __set_current_state(TASK_RUNNING); + tty_buffer_unlock_exclusive(&vc->port); tty_ldisc_deref(ld); return 0; } diff --git a/include/linux/tty.h b/include/linux/tty.h index 991575fe3451..7a9a3b0a6b5a 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -67,8 +67,8 @@ static inline char *flag_buf_ptr(struct tty_buffer *b, int ofs) struct tty_bufhead { struct tty_buffer *head; /* Queue head */ struct work_struct work; - struct mutex flush_mutex; - unsigned int flushpending:1; + struct mutex lock; + atomic_t priority; struct tty_buffer sentinel; struct llist_head free; /* Free queue head */ atomic_t memory_used; /* In-use buffers excluding free list */ diff --git a/include/linux/tty_flip.h b/include/linux/tty_flip.h index 6944ed2ce692..21ddd7d9ea1f 100644 --- a/include/linux/tty_flip.h +++ b/include/linux/tty_flip.h @@ -32,4 +32,7 @@ static inline int tty_insert_flip_string(struct tty_port *port, return tty_insert_flip_string_fixed_flag(port, chars, TTY_NORMAL, size); } +extern void tty_buffer_lock_exclusive(struct tty_port *port); +extern void tty_buffer_unlock_exclusive(struct tty_port *port); + #endif /* _LINUX_TTY_FLIP_H */ -- cgit v1.2.3 From 9114fe8ccf1871f630d2c14cd60e5f455b015459 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Sat, 15 Jun 2013 09:36:16 -0400 Subject: tty: Remove private constant from global namespace TTY_BUFFER_PAGE is only used within drivers/tty/tty_buffer.c; relocate to that file scope. Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/tty_buffer.c | 10 ++++++++++ include/linux/tty.h | 11 ----------- 2 files changed, 10 insertions(+), 11 deletions(-) (limited to 'include/linux') diff --git a/drivers/tty/tty_buffer.c b/drivers/tty/tty_buffer.c index f22e116db105..c043136fbe51 100644 --- a/drivers/tty/tty_buffer.c +++ b/drivers/tty/tty_buffer.c @@ -28,6 +28,16 @@ */ #define TTYB_MEM_LIMIT 65536 +/* + * We default to dicing tty buffer allocations to this many characters + * in order to avoid multiple page allocations. We know the size of + * tty_buffer itself but it must also be taken into account that the + * the buffer is 256 byte aligned. See tty_buffer_find for the allocation + * logic this must match + */ + +#define TTY_BUFFER_PAGE (((PAGE_SIZE - sizeof(struct tty_buffer)) / 2) & ~0xFF) + /** * tty_buffer_lock_exclusive - gain exclusive access to buffer diff --git a/include/linux/tty.h b/include/linux/tty.h index 7a9a3b0a6b5a..5fd5d6f1ebc4 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -53,17 +53,6 @@ static inline char *flag_buf_ptr(struct tty_buffer *b, int ofs) return (char *)char_buf_ptr(b, ofs) + b->size; } -/* - * We default to dicing tty buffer allocations to this many characters - * in order to avoid multiple page allocations. We know the size of - * tty_buffer itself but it must also be taken into account that the - * the buffer is 256 byte aligned. See tty_buffer_find for the allocation - * logic this must match - */ - -#define TTY_BUFFER_PAGE (((PAGE_SIZE - sizeof(struct tty_buffer)) / 2) & ~0xFF) - - struct tty_bufhead { struct tty_buffer *head; /* Queue head */ struct work_struct work; -- cgit v1.2.3 From 40d5e0905a03601d40cd4e46b8690093c2355d03 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Sat, 15 Jun 2013 10:21:17 -0400 Subject: n_tty: Fix EOF push handling In canonical mode, an EOF which is not the first character of the line causes read() to complete and return the number of characters read so far (commonly referred to as EOF push). However, if the previous read() returned because the user buffer was full _and_ the next character is an EOF not at the beginning of the line, read() must not return 0, thus mistakenly indicating the end-of-file condition. The TTY_PUSH flag is used to indicate an EOF was received which is not at the beginning of the line. Because the EOF push condition is evaluated by a thread other than the read(), multiple EOF pushes can cause a premature end-of-file to be indicated. Instead, discover the 'EOF push as first read character' condition from the read() thread itself, and restart the i/o loop if detected. Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/n_tty.c | 34 +++++++++++++++++----------------- drivers/tty/tty_io.c | 1 - include/linux/tty.h | 1 - 3 files changed, 17 insertions(+), 19 deletions(-) (limited to 'include/linux') diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c index d4d71350a71d..25aaf1afa221 100644 --- a/drivers/tty/n_tty.c +++ b/drivers/tty/n_tty.c @@ -113,6 +113,7 @@ struct n_tty_data { /* consumer-published */ size_t read_tail; + size_t line_start; /* protected by output lock */ unsigned int column; @@ -337,6 +338,7 @@ static void reset_buffer_flags(struct n_tty_data *ldata) { ldata->read_head = ldata->canon_head = ldata->read_tail = 0; ldata->echo_head = ldata->echo_tail = ldata->echo_commit = 0; + ldata->line_start = 0; ldata->erasing = 0; bitmap_zero(ldata->read_flags, N_TTY_BUF_SIZE); @@ -1396,8 +1398,6 @@ send_signal: if (c == EOF_CHAR(tty)) { if (read_cnt(ldata) >= N_TTY_BUF_SIZE) return; - if (ldata->canon_head != ldata->read_head) - set_bit(TTY_PUSH, &tty->flags); c = __DISABLED_CHAR; goto handle_newline; } @@ -1604,6 +1604,7 @@ static void n_tty_set_termios(struct tty_struct *tty, struct ktermios *old) canon_change = (old->c_lflag ^ tty->termios.c_lflag) & ICANON; if (canon_change) { bitmap_zero(ldata->read_flags, N_TTY_BUF_SIZE); + ldata->line_start = 0; ldata->canon_head = ldata->read_tail; ldata->erasing = 0; ldata->lnext = 0; @@ -1837,6 +1838,7 @@ static int canon_copy_from_read_buf(struct tty_struct *tty, size_t eol; size_t tail; int ret, found = 0; + bool eof_push = 0; /* N.B. avoid overrun if nr == 0 */ n = min(*nr, read_cnt(ldata)); @@ -1863,8 +1865,10 @@ static int canon_copy_from_read_buf(struct tty_struct *tty, n = (found + eol + size) & (N_TTY_BUF_SIZE - 1); c = n; - if (found && read_buf(ldata, eol) == __DISABLED_CHAR) + if (found && read_buf(ldata, eol) == __DISABLED_CHAR) { n--; + eof_push = !n && ldata->read_tail != ldata->line_start; + } n_tty_trace("%s: eol:%zu found:%d n:%zu c:%zu size:%zu more:%zu\n", __func__, eol, found, n, c, size, more); @@ -1887,9 +1891,11 @@ static int canon_copy_from_read_buf(struct tty_struct *tty, smp_mb__after_clear_bit(); ldata->read_tail += c; - if (found) + if (found) { + ldata->line_start = ldata->read_tail; tty_audit_push(tty); - return 0; + } + return eof_push ? -EAGAIN : 0; } extern ssize_t redirected_tty_write(struct file *, const char __user *, @@ -1964,12 +1970,10 @@ static ssize_t n_tty_read(struct tty_struct *tty, struct file *file, int c; int minimum, time; ssize_t retval = 0; - ssize_t size; long timeout; unsigned long flags; int packet; -do_it_again: c = job_control(tty, file); if (c < 0) return c; @@ -2076,7 +2080,10 @@ do_it_again: if (ldata->icanon && !L_EXTPROC(tty)) { retval = canon_copy_from_read_buf(tty, &b, &nr); - if (retval) + if (retval == -EAGAIN) { + retval = 0; + continue; + } else if (retval) break; } else { int uncopied; @@ -2104,15 +2111,8 @@ do_it_again: ldata->minimum_to_wake = minimum; __set_current_state(TASK_RUNNING); - size = b - buf; - if (size) { - retval = size; - if (nr) - clear_bit(TTY_PUSH, &tty->flags); - } else if (test_and_clear_bit(TTY_PUSH, &tty->flags)) { - up_read(&tty->termios_rwsem); - goto do_it_again; - } + if (b - buf) + retval = b - buf; n_tty_set_room(tty); up_read(&tty->termios_rwsem); diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c index 1b32da6f6cdd..2174698dd6f7 100644 --- a/drivers/tty/tty_io.c +++ b/drivers/tty/tty_io.c @@ -664,7 +664,6 @@ static void __tty_hangup(struct tty_struct *tty, int exit_session) spin_lock_irq(&tty->ctrl_lock); clear_bit(TTY_THROTTLED, &tty->flags); - clear_bit(TTY_PUSH, &tty->flags); clear_bit(TTY_DO_WRITE_WAKEUP, &tty->flags); put_pid(tty->session); put_pid(tty->pgrp); diff --git a/include/linux/tty.h b/include/linux/tty.h index 5fd5d6f1ebc4..554b732d8b55 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -304,7 +304,6 @@ struct tty_file_private { #define TTY_EXCLUSIVE 3 /* Exclusive open mode */ #define TTY_DEBUG 4 /* Debugging */ #define TTY_DO_WRITE_WAKEUP 5 /* Call write_wakeup after queuing new */ -#define TTY_PUSH 6 /* n_tty private */ #define TTY_CLOSING 7 /* ->close() in progress */ #define TTY_LDISC_OPEN 11 /* Line discipline is open */ #define TTY_PTY_LOCK 16 /* pty private */ -- cgit v1.2.3 From dee4a0be69c0e2996188e0c46478eadc280a8954 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Wed, 24 Jul 2013 16:43:51 -0400 Subject: tty: Fix lock order in tty_do_resize() Commits 6a1c0680cf3ba94356ecd58833e1540c93472a57 and 9356b535fcb71db494fc434acceb79f56d15bda2, respectively 'tty: Convert termios_mutex to termios_rwsem' and 'n_tty: Access termios values safely' introduced a circular lock dependency with console_lock and termios_rwsem. The lockdep report [1] shows that n_tty_write() will attempt to claim console_lock while holding the termios_rwsem, whereas tty_do_resize() may already hold the console_lock while claiming the termios_rwsem. Since n_tty_write() and tty_do_resize() do not contend over the same data -- the tty->winsize structure -- correct the lock dependency by introducing a new lock which specifically serializes access to tty->winsize only. [1] Lockdep report ====================================================== [ INFO: possible circular locking dependency detected ] 3.10.0-0+tip-xeon+lockdep #0+tip Not tainted ------------------------------------------------------- modprobe/277 is trying to acquire lock: (&tty->termios_rwsem){++++..}, at: [] tty_do_resize+0x36/0xe0 but task is already holding lock: ((fb_notifier_list).rwsem){.+.+.+}, at: [] __blocking_notifier_call_chain+0x56/0xc0 which lock already depends on the new lock. the existing dependency chain (in reverse order) is: -> #2 ((fb_notifier_list).rwsem){.+.+.+}: [] lock_acquire+0x92/0x1f0 [] down_read+0x47/0x5c [] __blocking_notifier_call_chain+0x56/0xc0 [] blocking_notifier_call_chain+0x16/0x20 [] fb_notifier_call_chain+0x1b/0x20 [] register_framebuffer+0x1e2/0x320 [] drm_fb_helper_initial_config+0x371/0x540 [drm_kms_helper] [] nouveau_fbcon_init+0x105/0x140 [nouveau] [] nouveau_drm_load+0x43f/0x610 [nouveau] [] drm_get_pci_dev+0x17e/0x2a0 [drm] [] nouveau_drm_probe+0x25a/0x2a0 [nouveau] [] local_pci_probe+0x4b/0x80 [] pci_device_probe+0x111/0x120 [] driver_probe_device+0x8b/0x3a0 [] __driver_attach+0xab/0xb0 [] bus_for_each_dev+0x5d/0xa0 [] driver_attach+0x1e/0x20 [] bus_add_driver+0x111/0x290 [] driver_register+0x77/0x170 [] __pci_register_driver+0x64/0x70 [] drm_pci_init+0x11a/0x130 [drm] [] nouveau_drm_init+0x4d/0x1000 [nouveau] [] do_one_initcall+0xea/0x1a0 [] load_module+0x123b/0x1bf0 [] SyS_init_module+0xd7/0x120 [] system_call_fastpath+0x16/0x1b -> #1 (console_lock){+.+.+.}: [] lock_acquire+0x92/0x1f0 [] console_lock+0x77/0x80 [] con_flush_chars+0x31/0x50 [] n_tty_write+0x1ec/0x4d0 [] tty_write+0x159/0x2e0 [] redirected_tty_write+0xb5/0xc0 [] vfs_write+0xc5/0x1f0 [] SyS_write+0x55/0xa0 [] system_call_fastpath+0x16/0x1b -> #0 (&tty->termios_rwsem){++++..}: [] __lock_acquire+0x1c43/0x1d30 [] lock_acquire+0x92/0x1f0 [] down_write+0x44/0x70 [] tty_do_resize+0x36/0xe0 [] vc_do_resize+0x3e1/0x4c0 [] vc_resize+0x1f/0x30 [] fbcon_init+0x385/0x5a0 [] visual_init+0xbc/0x120 [] do_bind_con_driver+0x163/0x320 [] do_take_over_console+0x61/0x70 [] do_fbcon_takeover+0x63/0xc0 [] fbcon_event_notify+0x715/0x820 [] notifier_call_chain+0x5d/0x110 [] __blocking_notifier_call_chain+0x6c/0xc0 [] blocking_notifier_call_chain+0x16/0x20 [] fb_notifier_call_chain+0x1b/0x20 [] register_framebuffer+0x1e2/0x320 [] drm_fb_helper_initial_config+0x371/0x540 [drm_kms_helper] [] nouveau_fbcon_init+0x105/0x140 [nouveau] [] nouveau_drm_load+0x43f/0x610 [nouveau] [] drm_get_pci_dev+0x17e/0x2a0 [drm] [] nouveau_drm_probe+0x25a/0x2a0 [nouveau] [] local_pci_probe+0x4b/0x80 [] pci_device_probe+0x111/0x120 [] driver_probe_device+0x8b/0x3a0 [] __driver_attach+0xab/0xb0 [] bus_for_each_dev+0x5d/0xa0 [] driver_attach+0x1e/0x20 [] bus_add_driver+0x111/0x290 [] driver_register+0x77/0x170 [] __pci_register_driver+0x64/0x70 [] drm_pci_init+0x11a/0x130 [drm] [] nouveau_drm_init+0x4d/0x1000 [nouveau] [] do_one_initcall+0xea/0x1a0 [] load_module+0x123b/0x1bf0 [] SyS_init_module+0xd7/0x120 [] system_call_fastpath+0x16/0x1b other info that might help us debug this: Chain exists of: &tty->termios_rwsem --> console_lock --> (fb_notifier_list).rwsem Possible unsafe locking scenario: CPU0 CPU1 ---- ---- lock((fb_notifier_list).rwsem); lock(console_lock); lock((fb_notifier_list).rwsem); lock(&tty->termios_rwsem); *** DEADLOCK *** 7 locks held by modprobe/277: #0: (&__lockdep_no_validate__){......}, at: [] __driver_attach+0x5b/0xb0 #1: (&__lockdep_no_validate__){......}, at: [] __driver_attach+0x69/0xb0 #2: (drm_global_mutex){+.+.+.}, at: [] drm_get_pci_dev+0xbd/0x2a0 [drm] #3: (registration_lock){+.+.+.}, at: [] register_framebuffer+0x25/0x320 #4: (&fb_info->lock){+.+.+.}, at: [] lock_fb_info+0x26/0x60 #5: (console_lock){+.+.+.}, at: [] register_framebuffer+0x1d4/0x320 #6: ((fb_notifier_list).rwsem){.+.+.+}, at: [] __blocking_notifier_call_chain+0x56/0xc0 stack backtrace: CPU: 0 PID: 277 Comm: modprobe Not tainted 3.10.0-0+tip-xeon+lockdep #0+tip Hardware name: Dell Inc. Precision WorkStation T5400 /0RW203, BIOS A11 04/30/2012 ffffffff8213e5e0 ffff8802aa2fb298 ffffffff81755f19 ffff8802aa2fb2e8 ffffffff8174f506 ffff8802aa2fa000 ffff8802aa2fb378 ffff8802aa2ea8e8 ffff8802aa2ea910 ffff8802aa2ea8e8 0000000000000006 0000000000000007 Call Trace: [] dump_stack+0x19/0x1b [] print_circular_bug+0x1fb/0x20c [] __lock_acquire+0x1c43/0x1d30 [] ? mark_held_locks+0xae/0x120 [] ? trace_hardirqs_on_caller+0x105/0x1d0 [] lock_acquire+0x92/0x1f0 [] ? tty_do_resize+0x36/0xe0 [] down_write+0x44/0x70 [] ? tty_do_resize+0x36/0xe0 [] tty_do_resize+0x36/0xe0 [] vc_do_resize+0x3e1/0x4c0 [] vc_resize+0x1f/0x30 [] fbcon_init+0x385/0x5a0 [] visual_init+0xbc/0x120 [] do_bind_con_driver+0x163/0x320 [] do_take_over_console+0x61/0x70 [] do_fbcon_takeover+0x63/0xc0 [] fbcon_event_notify+0x715/0x820 [] notifier_call_chain+0x5d/0x110 [] __blocking_notifier_call_chain+0x6c/0xc0 [] blocking_notifier_call_chain+0x16/0x20 [] fb_notifier_call_chain+0x1b/0x20 [] register_framebuffer+0x1e2/0x320 [] drm_fb_helper_initial_config+0x371/0x540 [drm_kms_helper] [] ? kmemleak_alloc+0x5b/0xc0 [] ? kmem_cache_alloc_trace+0x104/0x290 [] ? drm_fb_helper_single_add_all_connectors+0x81/0xf0 [drm_kms_helper] [] nouveau_fbcon_init+0x105/0x140 [nouveau] [] nouveau_drm_load+0x43f/0x610 [nouveau] [] drm_get_pci_dev+0x17e/0x2a0 [drm] [] nouveau_drm_probe+0x25a/0x2a0 [nouveau] [] ? _raw_spin_unlock_irqrestore+0x42/0x80 [] local_pci_probe+0x4b/0x80 [] pci_device_probe+0x111/0x120 [] driver_probe_device+0x8b/0x3a0 [] __driver_attach+0xab/0xb0 [] ? driver_probe_device+0x3a0/0x3a0 [] bus_for_each_dev+0x5d/0xa0 [] driver_attach+0x1e/0x20 [] bus_add_driver+0x111/0x290 [] ? 0xffffffffa0229fff [] driver_register+0x77/0x170 [] ? 0xffffffffa0229fff [] __pci_register_driver+0x64/0x70 [] drm_pci_init+0x11a/0x130 [drm] [] ? 0xffffffffa0229fff [] ? 0xffffffffa0229fff [] nouveau_drm_init+0x4d/0x1000 [nouveau] [] do_one_initcall+0xea/0x1a0 [] load_module+0x123b/0x1bf0 [] ? ddebug_proc_open+0xb0/0xb0 [] ? trace_hardirqs_on_thunk+0x3a/0x3f [] SyS_init_module+0xd7/0x120 [] system_call_fastpath+0x16/0x1b Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/pty.c | 4 ++-- drivers/tty/tty_io.c | 11 ++++++----- include/linux/tty.h | 3 ++- 3 files changed, 10 insertions(+), 8 deletions(-) (limited to 'include/linux') diff --git a/drivers/tty/pty.c b/drivers/tty/pty.c index b940127ba1c8..25c9bc783722 100644 --- a/drivers/tty/pty.c +++ b/drivers/tty/pty.c @@ -281,7 +281,7 @@ static int pty_resize(struct tty_struct *tty, struct winsize *ws) struct tty_struct *pty = tty->link; /* For a PTY we need to lock the tty side */ - down_write(&tty->termios_rwsem); + mutex_lock(&tty->winsize_mutex); if (!memcmp(ws, &tty->winsize, sizeof(*ws))) goto done; @@ -308,7 +308,7 @@ static int pty_resize(struct tty_struct *tty, struct winsize *ws) tty->winsize = *ws; pty->winsize = *ws; /* Never used so will go away soon */ done: - up_write(&tty->termios_rwsem); + mutex_unlock(&tty->winsize_mutex); return 0; } diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c index 2174698dd6f7..26bb78c30a00 100644 --- a/drivers/tty/tty_io.c +++ b/drivers/tty/tty_io.c @@ -2229,7 +2229,7 @@ static int tiocsti(struct tty_struct *tty, char __user *p) * * Copies the kernel idea of the window size into the user buffer. * - * Locking: tty->termios_rwsem is taken to ensure the winsize data + * Locking: tty->winsize_mutex is taken to ensure the winsize data * is consistent. */ @@ -2237,9 +2237,9 @@ static int tiocgwinsz(struct tty_struct *tty, struct winsize __user *arg) { int err; - down_read(&tty->termios_rwsem); + mutex_lock(&tty->winsize_mutex); err = copy_to_user(arg, &tty->winsize, sizeof(*arg)); - up_read(&tty->termios_rwsem); + mutex_unlock(&tty->winsize_mutex); return err ? -EFAULT: 0; } @@ -2260,7 +2260,7 @@ int tty_do_resize(struct tty_struct *tty, struct winsize *ws) unsigned long flags; /* Lock the tty */ - down_write(&tty->termios_rwsem); + mutex_lock(&tty->winsize_mutex); if (!memcmp(ws, &tty->winsize, sizeof(*ws))) goto done; /* Get the PID values and reference them so we can @@ -2275,7 +2275,7 @@ int tty_do_resize(struct tty_struct *tty, struct winsize *ws) tty->winsize = *ws; done: - up_write(&tty->termios_rwsem); + mutex_unlock(&tty->winsize_mutex); return 0; } EXPORT_SYMBOL(tty_do_resize); @@ -3016,6 +3016,7 @@ void initialize_tty_struct(struct tty_struct *tty, mutex_init(&tty->legacy_mutex); mutex_init(&tty->throttle_mutex); init_rwsem(&tty->termios_rwsem); + mutex_init(&tty->winsize_mutex); init_ldsem(&tty->ldisc_sem); init_waitqueue_head(&tty->write_wait); init_waitqueue_head(&tty->read_wait); diff --git a/include/linux/tty.h b/include/linux/tty.h index 554b732d8b55..64f864651d86 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -245,6 +245,7 @@ struct tty_struct { struct mutex legacy_mutex; struct mutex throttle_mutex; struct rw_semaphore termios_rwsem; + struct mutex winsize_mutex; spinlock_t ctrl_lock; /* Termios values are protected by the termios rwsem */ struct ktermios termios, termios_locked; @@ -254,7 +255,7 @@ struct tty_struct { struct pid *session; unsigned long flags; int count; - struct winsize winsize; /* termios rwsem */ + struct winsize winsize; /* winsize_mutex */ unsigned char stopped:1, hw_stopped:1, flow_stopped:1, packet:1; unsigned char ctrl_status; /* ctrl_lock */ unsigned int receive_room; /* Bytes free for queue */ -- cgit v1.2.3 From 10d8b34a421716d55a4ff7c2d427c35e88b8fd60 Mon Sep 17 00:00:00 2001 From: Alexander Shiyan Date: Sat, 29 Jun 2013 10:44:17 +0400 Subject: serial: max310x: Driver rework This patch rework max310x driver. Major changes have been made: - Prepare driver to support ICs with more than one UART. - Prepare driver to support work with I2C-bus. The patch changes almost every function and can not be divided into parts. Signed-off-by: Alexander Shiyan Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/Kconfig | 4 +- drivers/tty/serial/max310x.c | 918 +++++++++++++++++----------------- include/linux/platform_data/max310x.h | 5 +- 3 files changed, 454 insertions(+), 473 deletions(-) (limited to 'include/linux') diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index 5e3d68917ffe..25772c15276d 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -291,9 +291,9 @@ config SERIAL_MAX3100 config SERIAL_MAX310X bool "MAX310X support" - depends on SPI + depends on SPI_MASTER select SERIAL_CORE - select REGMAP_SPI if SPI + select REGMAP_SPI if SPI_MASTER default n help This selects support for an advanced UART from Maxim (Dallas). diff --git a/drivers/tty/serial/max310x.c b/drivers/tty/serial/max310x.c index 8941e6418942..4620289e9e49 100644 --- a/drivers/tty/serial/max310x.c +++ b/drivers/tty/serial/max310x.c @@ -1,7 +1,7 @@ /* * Maxim (Dallas) MAX3107/8 serial driver * - * Copyright (C) 2012 Alexander Shiyan + * Copyright (C) 2012-2013 Alexander Shiyan * * Based on max3100.c, by Christian Pellegrin * Based on max3110.c, by Feng Tang @@ -17,7 +17,9 @@ /* TODO: MAX14830 support (Quad) */ #include +#include #include +#include #include #include #include @@ -25,8 +27,10 @@ #include #include #include + #include +#define MAX310X_NAME "max310x" #define MAX310X_MAJOR 204 #define MAX310X_MINOR 209 @@ -37,7 +41,8 @@ #define MAX310X_IRQSTS_REG (0x02) /* IRQ status */ #define MAX310X_LSR_IRQEN_REG (0x03) /* LSR IRQ enable */ #define MAX310X_LSR_IRQSTS_REG (0x04) /* LSR IRQ status */ -#define MAX310X_SPCHR_IRQEN_REG (0x05) /* Special char IRQ enable */ +#define MAX310X_REG_05 (0x05) +#define MAX310X_SPCHR_IRQEN_REG MAX310X_REG_05 /* Special char IRQ en */ #define MAX310X_SPCHR_IRQSTS_REG (0x06) /* Special char IRQ status */ #define MAX310X_STS_IRQEN_REG (0x07) /* Status IRQ enable */ #define MAX310X_STS_IRQSTS_REG (0x08) /* Status IRQ status */ @@ -63,8 +68,15 @@ #define MAX310X_BRGDIVLSB_REG (0x1c) /* Baud rate divisor LSB */ #define MAX310X_BRGDIVMSB_REG (0x1d) /* Baud rate divisor MSB */ #define MAX310X_CLKSRC_REG (0x1e) /* Clock source */ -/* Only present in MAX3107 */ -#define MAX3107_REVID_REG (0x1f) /* Revision identification */ +#define MAX310X_REG_1F (0x1f) + +#define MAX310X_REVID_REG MAX310X_REG_1F /* Revision ID */ + +#define MAX310X_GLOBALIRQ_REG MAX310X_REG_1F /* Global IRQ (RO) */ +#define MAX310X_GLOBALCMD_REG MAX310X_REG_1F /* Global Command (WO) */ + +/* Extended registers */ +#define MAX310X_REVID_EXTREG MAX310X_REG_05 /* Revision ID */ /* IRQ register bits */ #define MAX310X_IRQ_LSR_BIT (1 << 0) /* LSR interrupt */ @@ -246,58 +258,139 @@ #define MAX310X_CLKSRC_EXTCLK_BIT (1 << 4) /* External clock enable */ #define MAX310X_CLKSRC_CLK2RTS_BIT (1 << 7) /* Baud clk to RTS pin */ +/* Global commands */ +#define MAX310X_EXTREG_ENBL (0xce) +#define MAX310X_EXTREG_DSBL (0xcd) + /* Misc definitions */ #define MAX310X_FIFO_SIZE (128) +#define MAX310x_REV_MASK (0xfc) /* MAX3107 specific */ #define MAX3107_REV_ID (0xa0) -#define MAX3107_REV_MASK (0xfe) - -/* IRQ status bits definitions */ -#define MAX310X_IRQ_TX (MAX310X_IRQ_TXFIFO_BIT | \ - MAX310X_IRQ_TXEMPTY_BIT) -#define MAX310X_IRQ_RX (MAX310X_IRQ_RXFIFO_BIT | \ - MAX310X_IRQ_RXEMPTY_BIT) - -/* Supported chip types */ -enum { - MAX310X_TYPE_MAX3107 = 3107, - MAX310X_TYPE_MAX3108 = 3108, + +struct max310x_devtype { + char name[9]; + int nr; + int (*detect)(struct device *); + void (*power)(struct uart_port *, int); }; -struct max310x_port { - struct uart_driver uart; +struct max310x_one { struct uart_port port; + struct work_struct tx_work; +}; - const char *name; - int uartclk; - - unsigned int nr_gpio; +struct max310x_port { + struct uart_driver uart; + struct max310x_devtype *devtype; + struct regmap *regmap; + struct regmap_config regcfg; + struct mutex mutex; + struct max310x_pdata *pdata; + int gpio_used; #ifdef CONFIG_GPIOLIB struct gpio_chip gpio; #endif + struct max310x_one p[0]; +}; - struct regmap *regmap; - struct regmap_config regcfg; +static u8 max310x_port_read(struct uart_port *port, u8 reg) +{ + struct max310x_port *s = dev_get_drvdata(port->dev); + unsigned int val = 0; - struct workqueue_struct *wq; - struct work_struct tx_work; + regmap_read(s->regmap, port->iobase + reg, &val); - struct mutex max310x_mutex; + return val; +} - struct max310x_pdata *pdata; +static void max310x_port_write(struct uart_port *port, u8 reg, u8 val) +{ + struct max310x_port *s = dev_get_drvdata(port->dev); + + regmap_write(s->regmap, port->iobase + reg, val); +} + +static void max310x_port_update(struct uart_port *port, u8 reg, u8 mask, u8 val) +{ + struct max310x_port *s = dev_get_drvdata(port->dev); + + regmap_update_bits(s->regmap, port->iobase + reg, mask, val); +} + +static int max3107_detect(struct device *dev) +{ + struct max310x_port *s = dev_get_drvdata(dev); + unsigned int val = 0; + int ret; + + ret = regmap_read(s->regmap, MAX310X_REVID_REG, &val); + if (ret) + return ret; + + if (((val & MAX310x_REV_MASK) != MAX3107_REV_ID)) { + dev_err(dev, + "%s ID 0x%02x does not match\n", s->devtype->name, val); + return -ENODEV; + } + + return 0; +} + +static int max3108_detect(struct device *dev) +{ + struct max310x_port *s = dev_get_drvdata(dev); + unsigned int val = 0; + int ret; + + /* MAX3108 have not REV ID register, we just check default value + * from clocksource register to make sure everything works. + */ + ret = regmap_read(s->regmap, MAX310X_CLKSRC_REG, &val); + if (ret) + return ret; + + if (val != (MAX310X_CLKSRC_EXTCLK_BIT | MAX310X_CLKSRC_PLLBYP_BIT)) { + dev_err(dev, "%s not present\n", s->devtype->name); + return -ENODEV; + } + + return 0; +} + +static void max310x_power(struct uart_port *port, int on) +{ + max310x_port_update(port, MAX310X_MODE1_REG, + MAX310X_MODE1_FORCESLEEP_BIT, + on ? 0 : MAX310X_MODE1_FORCESLEEP_BIT); + if (on) + msleep(50); +} + +static const struct max310x_devtype max3107_devtype = { + .name = "MAX3107", + .nr = 1, + .detect = max3107_detect, + .power = max310x_power, }; -static bool max3107_8_reg_writeable(struct device *dev, unsigned int reg) +static const struct max310x_devtype max3108_devtype = { + .name = "MAX3108", + .nr = 1, + .detect = max3108_detect, + .power = max310x_power, +}; + +static bool max310x_reg_writeable(struct device *dev, unsigned int reg) { - switch (reg) { + switch (reg & 0x1f) { case MAX310X_IRQSTS_REG: case MAX310X_LSR_IRQSTS_REG: case MAX310X_SPCHR_IRQSTS_REG: case MAX310X_STS_IRQSTS_REG: case MAX310X_TXFIFOLVL_REG: case MAX310X_RXFIFOLVL_REG: - case MAX3107_REVID_REG: /* Only available on MAX3107 */ return false; default: break; @@ -308,7 +401,7 @@ static bool max3107_8_reg_writeable(struct device *dev, unsigned int reg) static bool max310x_reg_volatile(struct device *dev, unsigned int reg) { - switch (reg) { + switch (reg & 0x1f) { case MAX310X_RHR_REG: case MAX310X_IRQSTS_REG: case MAX310X_LSR_IRQSTS_REG: @@ -317,6 +410,9 @@ static bool max310x_reg_volatile(struct device *dev, unsigned int reg) case MAX310X_TXFIFOLVL_REG: case MAX310X_RXFIFOLVL_REG: case MAX310X_GPIODATA_REG: + case MAX310X_BRGDIVLSB_REG: + case MAX310X_REG_05: + case MAX310X_REG_1F: return true; default: break; @@ -327,7 +423,7 @@ static bool max310x_reg_volatile(struct device *dev, unsigned int reg) static bool max310x_reg_precious(struct device *dev, unsigned int reg) { - switch (reg) { + switch (reg & 0x1f) { case MAX310X_RHR_REG: case MAX310X_IRQSTS_REG: case MAX310X_SPCHR_IRQSTS_REG: @@ -340,42 +436,25 @@ static bool max310x_reg_precious(struct device *dev, unsigned int reg) return false; } -static void max310x_set_baud(struct max310x_port *s, int baud) +static void max310x_set_baud(struct uart_port *port, int baud) { - unsigned int mode = 0, div = s->uartclk / baud; + unsigned int mode = 0, div = port->uartclk / baud; if (!(div / 16)) { /* Mode x2 */ mode = MAX310X_BRGCFG_2XMODE_BIT; - div = (s->uartclk * 2) / baud; + div = (port->uartclk * 2) / baud; } if (!(div / 16)) { /* Mode x4 */ mode = MAX310X_BRGCFG_4XMODE_BIT; - div = (s->uartclk * 4) / baud; + div = (port->uartclk * 4) / baud; } - regmap_write(s->regmap, MAX310X_BRGDIVMSB_REG, - ((div / 16) >> 8) & 0xff); - regmap_write(s->regmap, MAX310X_BRGDIVLSB_REG, (div / 16) & 0xff); - regmap_write(s->regmap, MAX310X_BRGCFG_REG, (div % 16) | mode); -} - -static void max310x_wait_pll(struct max310x_port *s) -{ - int tryes = 1000; - - /* Wait for PLL only if crystal is used */ - if (!(s->pdata->driver_flags & MAX310X_EXT_CLK)) { - unsigned int sts = 0; - - while (tryes--) { - regmap_read(s->regmap, MAX310X_STS_IRQSTS_REG, &sts); - if (sts & MAX310X_STS_CLKREADY_BIT) - break; - } - } + max310x_port_write(port, MAX310X_BRGDIVMSB_REG, (div / 16) >> 8); + max310x_port_write(port, MAX310X_BRGDIVLSB_REG, div / 16); + max310x_port_write(port, MAX310X_BRGCFG_REG, (div % 16) | mode); } static int max310x_update_best_err(unsigned long f, long *besterr) @@ -449,49 +528,49 @@ static int max310x_set_ref_clk(struct max310x_port *s) regmap_write(s->regmap, MAX310X_CLKSRC_REG, clksrc); - if (pllcfg) - max310x_wait_pll(s); - - dev_dbg(s->port.dev, "Reference clock set to %lu Hz\n", bestfreq); + /* Wait for crystal */ + if (pllcfg && !(s->pdata->driver_flags & MAX310X_EXT_CLK)) + msleep(10); return (int)bestfreq; } -static void max310x_handle_rx(struct max310x_port *s, unsigned int rxlen) +static void max310x_handle_rx(struct uart_port *port, unsigned int rxlen) { - unsigned int sts = 0, ch = 0, flag; + unsigned int sts, ch, flag; - if (unlikely(rxlen >= MAX310X_FIFO_SIZE)) { - dev_warn(s->port.dev, "Possible RX FIFO overrun %d\n", rxlen); + if (unlikely(rxlen >= port->fifosize)) { + dev_warn_ratelimited(port->dev, + "Port %i: Possible RX FIFO overrun\n", + port->line); + port->icount.buf_overrun++; /* Ensure sanity of RX level */ - rxlen = MAX310X_FIFO_SIZE; + rxlen = port->fifosize; } - dev_dbg(s->port.dev, "RX Len = %u\n", rxlen); - while (rxlen--) { - regmap_read(s->regmap, MAX310X_RHR_REG, &ch); - regmap_read(s->regmap, MAX310X_LSR_IRQSTS_REG, &sts); + ch = max310x_port_read(port, MAX310X_RHR_REG); + sts = max310x_port_read(port, MAX310X_LSR_IRQSTS_REG); sts &= MAX310X_LSR_RXPAR_BIT | MAX310X_LSR_FRERR_BIT | MAX310X_LSR_RXOVR_BIT | MAX310X_LSR_RXBRK_BIT; - s->port.icount.rx++; + port->icount.rx++; flag = TTY_NORMAL; if (unlikely(sts)) { if (sts & MAX310X_LSR_RXBRK_BIT) { - s->port.icount.brk++; - if (uart_handle_break(&s->port)) + port->icount.brk++; + if (uart_handle_break(port)) continue; } else if (sts & MAX310X_LSR_RXPAR_BIT) - s->port.icount.parity++; + port->icount.parity++; else if (sts & MAX310X_LSR_FRERR_BIT) - s->port.icount.frame++; + port->icount.frame++; else if (sts & MAX310X_LSR_RXOVR_BIT) - s->port.icount.overrun++; + port->icount.overrun++; - sts &= s->port.read_status_mask; + sts &= port->read_status_mask; if (sts & MAX310X_LSR_RXBRK_BIT) flag = TTY_BREAK; else if (sts & MAX310X_LSR_RXPAR_BIT) @@ -502,129 +581,129 @@ static void max310x_handle_rx(struct max310x_port *s, unsigned int rxlen) flag = TTY_OVERRUN; } - if (uart_handle_sysrq_char(s->port, ch)) + if (uart_handle_sysrq_char(port, ch)) continue; - if (sts & s->port.ignore_status_mask) + if (sts & port->ignore_status_mask) continue; - uart_insert_char(&s->port, sts, MAX310X_LSR_RXOVR_BIT, - ch, flag); + uart_insert_char(port, sts, MAX310X_LSR_RXOVR_BIT, ch, flag); } - tty_flip_buffer_push(&s->port.state->port); + tty_flip_buffer_push(&port->state->port); } -static void max310x_handle_tx(struct max310x_port *s) +static void max310x_handle_tx(struct uart_port *port) { - struct circ_buf *xmit = &s->port.state->xmit; - unsigned int txlen = 0, to_send; + struct circ_buf *xmit = &port->state->xmit; + unsigned int txlen, to_send; - if (unlikely(s->port.x_char)) { - regmap_write(s->regmap, MAX310X_THR_REG, s->port.x_char); - s->port.icount.tx++; - s->port.x_char = 0; + if (unlikely(port->x_char)) { + max310x_port_write(port, MAX310X_THR_REG, port->x_char); + port->icount.tx++; + port->x_char = 0; return; } - if (uart_circ_empty(xmit) || uart_tx_stopped(&s->port)) + if (uart_circ_empty(xmit) || uart_tx_stopped(port)) return; /* Get length of data pending in circular buffer */ to_send = uart_circ_chars_pending(xmit); if (likely(to_send)) { /* Limit to size of TX FIFO */ - regmap_read(s->regmap, MAX310X_TXFIFOLVL_REG, &txlen); - txlen = MAX310X_FIFO_SIZE - txlen; + txlen = max310x_port_read(port, MAX310X_TXFIFOLVL_REG); + txlen = port->fifosize - txlen; to_send = (to_send > txlen) ? txlen : to_send; - dev_dbg(s->port.dev, "TX Len = %u\n", to_send); - /* Add data to send */ - s->port.icount.tx += to_send; + port->icount.tx += to_send; while (to_send--) { - regmap_write(s->regmap, MAX310X_THR_REG, - xmit->buf[xmit->tail]); + max310x_port_write(port, MAX310X_THR_REG, + xmit->buf[xmit->tail]); xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); }; } if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) - uart_write_wakeup(&s->port); + uart_write_wakeup(port); } -static irqreturn_t max310x_ist(int irq, void *dev_id) +static void max310x_port_irq(struct max310x_port *s, int portno) { - struct max310x_port *s = (struct max310x_port *)dev_id; - unsigned int ists = 0, lsr = 0, rxlen = 0; + struct uart_port *port = &s->p[portno].port; - mutex_lock(&s->max310x_mutex); + do { + unsigned int ists, lsr, rxlen; - for (;;) { /* Read IRQ status & RX FIFO level */ - regmap_read(s->regmap, MAX310X_IRQSTS_REG, &ists); - regmap_read(s->regmap, MAX310X_LSR_IRQSTS_REG, &lsr); - regmap_read(s->regmap, MAX310X_RXFIFOLVL_REG, &rxlen); - if (!ists && !(lsr & MAX310X_LSR_RXTO_BIT) && !rxlen) + ists = max310x_port_read(port, MAX310X_IRQSTS_REG); + rxlen = max310x_port_read(port, MAX310X_RXFIFOLVL_REG); + if (!ists && !rxlen) break; - dev_dbg(s->port.dev, "IRQ status: 0x%02x\n", ists); - - if (rxlen) - max310x_handle_rx(s, rxlen); - if (ists & MAX310X_IRQ_TX) - max310x_handle_tx(s); - if (ists & MAX310X_IRQ_CTS_BIT) - uart_handle_cts_change(&s->port, + if (ists & MAX310X_IRQ_CTS_BIT) { + lsr = max310x_port_read(port, MAX310X_LSR_IRQSTS_REG); + uart_handle_cts_change(port, !!(lsr & MAX310X_LSR_CTS_BIT)); - } + } + if (rxlen) + max310x_handle_rx(port, rxlen); + if (ists & MAX310X_IRQ_TXEMPTY_BIT) { + mutex_lock(&s->mutex); + max310x_handle_tx(port); + mutex_unlock(&s->mutex); + } + } while (1); +} - mutex_unlock(&s->max310x_mutex); +static irqreturn_t max310x_ist(int irq, void *dev_id) +{ + struct max310x_port *s = (struct max310x_port *)dev_id; + + if (s->uart.nr > 1) { + do { + unsigned int val = ~0; + + WARN_ON_ONCE(regmap_read(s->regmap, + MAX310X_GLOBALIRQ_REG, &val)); + val = ((1 << s->uart.nr) - 1) & ~val; + if (!val) + break; + max310x_port_irq(s, fls(val) - 1); + } while (1); + } else + max310x_port_irq(s, 0); return IRQ_HANDLED; } static void max310x_wq_proc(struct work_struct *ws) { - struct max310x_port *s = container_of(ws, struct max310x_port, tx_work); + struct max310x_one *one = container_of(ws, struct max310x_one, tx_work); + struct max310x_port *s = dev_get_drvdata(one->port.dev); - mutex_lock(&s->max310x_mutex); - max310x_handle_tx(s); - mutex_unlock(&s->max310x_mutex); + mutex_lock(&s->mutex); + max310x_handle_tx(&one->port); + mutex_unlock(&s->mutex); } static void max310x_start_tx(struct uart_port *port) { - struct max310x_port *s = container_of(port, struct max310x_port, port); + struct max310x_one *one = container_of(port, struct max310x_one, port); - queue_work(s->wq, &s->tx_work); -} - -static void max310x_stop_tx(struct uart_port *port) -{ - /* Do nothing */ -} - -static void max310x_stop_rx(struct uart_port *port) -{ - /* Do nothing */ + if (!work_pending(&one->tx_work)) + schedule_work(&one->tx_work); } static unsigned int max310x_tx_empty(struct uart_port *port) { - unsigned int val = 0; - struct max310x_port *s = container_of(port, struct max310x_port, port); - - mutex_lock(&s->max310x_mutex); - regmap_read(s->regmap, MAX310X_TXFIFOLVL_REG, &val); - mutex_unlock(&s->max310x_mutex); + unsigned int lvl, sts; - return val ? 0 : TIOCSER_TEMT; -} + lvl = max310x_port_read(port, MAX310X_TXFIFOLVL_REG); + sts = max310x_port_read(port, MAX310X_IRQSTS_REG); -static void max310x_enable_ms(struct uart_port *port) -{ - /* Modem status not supported */ + return ((sts & MAX310X_IRQ_TXEMPTY_BIT) && !lvl) ? TIOCSER_TEMT : 0; } static unsigned int max310x_get_mctrl(struct uart_port *port) @@ -644,28 +723,20 @@ static void max310x_set_mctrl(struct uart_port *port, unsigned int mctrl) static void max310x_break_ctl(struct uart_port *port, int break_state) { - struct max310x_port *s = container_of(port, struct max310x_port, port); - - mutex_lock(&s->max310x_mutex); - regmap_update_bits(s->regmap, MAX310X_LCR_REG, - MAX310X_LCR_TXBREAK_BIT, - break_state ? MAX310X_LCR_TXBREAK_BIT : 0); - mutex_unlock(&s->max310x_mutex); + max310x_port_update(port, MAX310X_LCR_REG, + MAX310X_LCR_TXBREAK_BIT, + break_state ? MAX310X_LCR_TXBREAK_BIT : 0); } static void max310x_set_termios(struct uart_port *port, struct ktermios *termios, struct ktermios *old) { - struct max310x_port *s = container_of(port, struct max310x_port, port); unsigned int lcr, flow = 0; int baud; - mutex_lock(&s->max310x_mutex); - /* Mask termios capabilities we don't support */ termios->c_cflag &= ~CMSPAR; - termios->c_iflag &= ~IXANY; /* Word size */ switch (termios->c_cflag & CSIZE) { @@ -696,7 +767,7 @@ static void max310x_set_termios(struct uart_port *port, lcr |= MAX310X_LCR_STOPLEN_BIT; /* 2 stops */ /* Update LCR register */ - regmap_write(s->regmap, MAX310X_LCR_REG, lcr); + max310x_port_write(port, MAX310X_LCR_REG, lcr); /* Set read status mask */ port->read_status_mask = MAX310X_LSR_RXOVR_BIT; @@ -717,8 +788,8 @@ static void max310x_set_termios(struct uart_port *port, MAX310X_LSR_RXBRK_BIT; /* Configure flow control */ - regmap_write(s->regmap, MAX310X_XON1_REG, termios->c_cc[VSTART]); - regmap_write(s->regmap, MAX310X_XOFF1_REG, termios->c_cc[VSTOP]); + max310x_port_write(port, MAX310X_XON1_REG, termios->c_cc[VSTART]); + max310x_port_write(port, MAX310X_XOFF1_REG, termios->c_cc[VSTOP]); if (termios->c_cflag & CRTSCTS) flow |= MAX310X_FLOWCTRL_AUTOCTS_BIT | MAX310X_FLOWCTRL_AUTORTS_BIT; @@ -728,7 +799,7 @@ static void max310x_set_termios(struct uart_port *port, if (termios->c_iflag & IXOFF) flow |= MAX310X_FLOWCTRL_SWFLOW1_BIT | MAX310X_FLOWCTRL_SWFLOWEN_BIT; - regmap_write(s->regmap, MAX310X_FLOWCTRL_REG, flow); + max310x_port_write(port, MAX310X_FLOWCTRL_REG, flow); /* Get baud rate generator configuration */ baud = uart_get_baud_rate(port, termios, old, @@ -736,36 +807,30 @@ static void max310x_set_termios(struct uart_port *port, port->uartclk / 4); /* Setup baudrate generator */ - max310x_set_baud(s, baud); + max310x_set_baud(port, baud); /* Update timeout according to new baud rate */ uart_update_timeout(port, termios->c_cflag, baud); - - mutex_unlock(&s->max310x_mutex); } static int max310x_startup(struct uart_port *port) { unsigned int val, line = port->line; - struct max310x_port *s = container_of(port, struct max310x_port, port); + struct max310x_port *s = dev_get_drvdata(port->dev); - if (s->pdata->suspend) - s->pdata->suspend(0); - - mutex_lock(&s->max310x_mutex); + s->devtype->power(port, 1); /* Configure baud rate, 9600 as default */ - max310x_set_baud(s, 9600); + max310x_set_baud(port, 9600); /* Configure LCR register, 8N1 mode by default */ - val = MAX310X_LCR_WORD_LEN_8; - regmap_write(s->regmap, MAX310X_LCR_REG, val); + max310x_port_write(port, MAX310X_LCR_REG, MAX310X_LCR_WORD_LEN_8); /* Configure MODE1 register */ - regmap_update_bits(s->regmap, MAX310X_MODE1_REG, - MAX310X_MODE1_TRNSCVCTRL_BIT, - (s->pdata->uart_flags[line] & MAX310X_AUTO_DIR_CTRL) - ? MAX310X_MODE1_TRNSCVCTRL_BIT : 0); + max310x_port_update(port, MAX310X_MODE1_REG, + MAX310X_MODE1_TRNSCVCTRL_BIT, + (s->pdata->uart_flags[line] & MAX310X_AUTO_DIR_CTRL) + ? MAX310X_MODE1_TRNSCVCTRL_BIT : 0); /* Configure MODE2 register */ val = MAX310X_MODE2_RXEMPTINV_BIT; @@ -776,63 +841,40 @@ static int max310x_startup(struct uart_port *port) /* Reset FIFOs */ val |= MAX310X_MODE2_FIFORST_BIT; - regmap_write(s->regmap, MAX310X_MODE2_REG, val); - - /* Configure FIFO trigger level register */ - /* RX FIFO trigger for 16 words, TX FIFO trigger for 64 words */ - val = MAX310X_FIFOTRIGLVL_RX(16) | MAX310X_FIFOTRIGLVL_TX(64); - regmap_write(s->regmap, MAX310X_FIFOTRIGLVL_REG, val); + max310x_port_write(port, MAX310X_MODE2_REG, val); + max310x_port_update(port, MAX310X_MODE2_REG, + MAX310X_MODE2_FIFORST_BIT, 0); /* Configure flow control levels */ /* Flow control halt level 96, resume level 48 */ - val = MAX310X_FLOWLVL_RES(48) | MAX310X_FLOWLVL_HALT(96); - regmap_write(s->regmap, MAX310X_FLOWLVL_REG, val); - - /* Clear timeout register */ - regmap_write(s->regmap, MAX310X_RXTO_REG, 0); - - /* Configure LSR interrupt enable register */ - /* Enable RX timeout interrupt */ - val = MAX310X_LSR_RXTO_BIT; - regmap_write(s->regmap, MAX310X_LSR_IRQEN_REG, val); + max310x_port_write(port, MAX310X_FLOWLVL_REG, + MAX310X_FLOWLVL_RES(48) | MAX310X_FLOWLVL_HALT(96)); - /* Clear FIFO reset */ - regmap_update_bits(s->regmap, MAX310X_MODE2_REG, - MAX310X_MODE2_FIFORST_BIT, 0); + /* Clear IRQ status register */ + max310x_port_read(port, MAX310X_IRQSTS_REG); - /* Clear IRQ status register by reading it */ - regmap_read(s->regmap, MAX310X_IRQSTS_REG, &val); - - /* Configure interrupt enable register */ - /* Enable CTS change interrupt */ - val = MAX310X_IRQ_CTS_BIT; - /* Enable RX, TX interrupts */ - val |= MAX310X_IRQ_RX | MAX310X_IRQ_TX; - regmap_write(s->regmap, MAX310X_IRQEN_REG, val); - - mutex_unlock(&s->max310x_mutex); + /* Enable RX, TX, CTS change interrupts */ + val = MAX310X_IRQ_RXEMPTY_BIT | MAX310X_IRQ_TXEMPTY_BIT; + max310x_port_write(port, MAX310X_IRQEN_REG, val | MAX310X_IRQ_CTS_BIT); return 0; } static void max310x_shutdown(struct uart_port *port) { - struct max310x_port *s = container_of(port, struct max310x_port, port); + struct max310x_port *s = dev_get_drvdata(port->dev); /* Disable all interrupts */ - mutex_lock(&s->max310x_mutex); - regmap_write(s->regmap, MAX310X_IRQEN_REG, 0); - mutex_unlock(&s->max310x_mutex); + max310x_port_write(port, MAX310X_IRQEN_REG, 0); - if (s->pdata->suspend) - s->pdata->suspend(1); + s->devtype->power(port, 0); } static const char *max310x_type(struct uart_port *port) { - struct max310x_port *s = container_of(port, struct max310x_port, port); + struct max310x_port *s = dev_get_drvdata(port->dev); - return (port->type == PORT_MAX310X) ? s->name : NULL; + return (port->type == PORT_MAX310X) ? s->devtype->name : NULL; } static int max310x_request_port(struct uart_port *port) @@ -841,134 +883,100 @@ static int max310x_request_port(struct uart_port *port) return 0; } -static void max310x_release_port(struct uart_port *port) -{ - /* Do nothing */ -} - static void max310x_config_port(struct uart_port *port, int flags) { if (flags & UART_CONFIG_TYPE) port->type = PORT_MAX310X; } -static int max310x_verify_port(struct uart_port *port, struct serial_struct *ser) +static int max310x_verify_port(struct uart_port *port, struct serial_struct *s) { - if ((ser->type == PORT_UNKNOWN) || (ser->type == PORT_MAX310X)) - return 0; - if (ser->irq == port->irq) - return 0; + if ((s->type != PORT_UNKNOWN) && (s->type != PORT_MAX310X)) + return -EINVAL; + if (s->irq != port->irq) + return -EINVAL; - return -EINVAL; + return 0; } -static struct uart_ops max310x_ops = { +static void max310x_null_void(struct uart_port *port) +{ + /* Do nothing */ +} + +static const struct uart_ops max310x_ops = { .tx_empty = max310x_tx_empty, .set_mctrl = max310x_set_mctrl, .get_mctrl = max310x_get_mctrl, - .stop_tx = max310x_stop_tx, + .stop_tx = max310x_null_void, .start_tx = max310x_start_tx, - .stop_rx = max310x_stop_rx, - .enable_ms = max310x_enable_ms, + .stop_rx = max310x_null_void, + .enable_ms = max310x_null_void, .break_ctl = max310x_break_ctl, .startup = max310x_startup, .shutdown = max310x_shutdown, .set_termios = max310x_set_termios, .type = max310x_type, .request_port = max310x_request_port, - .release_port = max310x_release_port, + .release_port = max310x_null_void, .config_port = max310x_config_port, .verify_port = max310x_verify_port, }; -#ifdef CONFIG_PM_SLEEP - -static int max310x_suspend(struct device *dev) +static int __maybe_unused max310x_suspend(struct spi_device *spi, + pm_message_t state) { - int ret; - struct max310x_port *s = dev_get_drvdata(dev); - - dev_dbg(dev, "Suspend\n"); + struct max310x_port *s = dev_get_drvdata(&spi->dev); + int i; - ret = uart_suspend_port(&s->uart, &s->port); - - mutex_lock(&s->max310x_mutex); - - /* Enable sleep mode */ - regmap_update_bits(s->regmap, MAX310X_MODE1_REG, - MAX310X_MODE1_FORCESLEEP_BIT, - MAX310X_MODE1_FORCESLEEP_BIT); - - mutex_unlock(&s->max310x_mutex); - - if (s->pdata->suspend) - s->pdata->suspend(1); + for (i = 0; i < s->uart.nr; i++) { + uart_suspend_port(&s->uart, &s->p[i].port); + s->devtype->power(&s->p[i].port, 0); + } - return ret; + return 0; } -static int max310x_resume(struct device *dev) +static int __maybe_unused max310x_resume(struct spi_device *spi) { - struct max310x_port *s = dev_get_drvdata(dev); - - dev_dbg(dev, "Resume\n"); - - if (s->pdata->suspend) - s->pdata->suspend(0); - - mutex_lock(&s->max310x_mutex); + struct max310x_port *s = dev_get_drvdata(&spi->dev); + int i; - /* Disable sleep mode */ - regmap_update_bits(s->regmap, MAX310X_MODE1_REG, - MAX310X_MODE1_FORCESLEEP_BIT, - 0); - - max310x_wait_pll(s); - - mutex_unlock(&s->max310x_mutex); + for (i = 0; i < s->uart.nr; i++) { + s->devtype->power(&s->p[i].port, 1); + uart_resume_port(&s->uart, &s->p[i].port); + } - return uart_resume_port(&s->uart, &s->port); + return 0; } -static SIMPLE_DEV_PM_OPS(max310x_pm_ops, max310x_suspend, max310x_resume); -#define MAX310X_PM_OPS (&max310x_pm_ops) - -#else -#define MAX310X_PM_OPS NULL -#endif - #ifdef CONFIG_GPIOLIB static int max310x_gpio_get(struct gpio_chip *chip, unsigned offset) { - unsigned int val = 0; + unsigned int val; struct max310x_port *s = container_of(chip, struct max310x_port, gpio); + struct uart_port *port = &s->p[offset / 4].port; - mutex_lock(&s->max310x_mutex); - regmap_read(s->regmap, MAX310X_GPIODATA_REG, &val); - mutex_unlock(&s->max310x_mutex); + val = max310x_port_read(port, MAX310X_GPIODATA_REG); - return !!((val >> 4) & (1 << offset)); + return !!((val >> 4) & (1 << (offset % 4))); } static void max310x_gpio_set(struct gpio_chip *chip, unsigned offset, int value) { struct max310x_port *s = container_of(chip, struct max310x_port, gpio); + struct uart_port *port = &s->p[offset / 4].port; - mutex_lock(&s->max310x_mutex); - regmap_update_bits(s->regmap, MAX310X_GPIODATA_REG, 1 << offset, value ? - 1 << offset : 0); - mutex_unlock(&s->max310x_mutex); + max310x_port_update(port, MAX310X_GPIODATA_REG, 1 << (offset % 4), + value ? 1 << (offset % 4) : 0); } static int max310x_gpio_direction_input(struct gpio_chip *chip, unsigned offset) { struct max310x_port *s = container_of(chip, struct max310x_port, gpio); + struct uart_port *port = &s->p[offset / 4].port; - mutex_lock(&s->max310x_mutex); - - regmap_update_bits(s->regmap, MAX310X_GPIOCFG_REG, 1 << offset, 0); - - mutex_unlock(&s->max310x_mutex); + max310x_port_update(port, MAX310X_GPIOCFG_REG, 1 << (offset % 4), 0); return 0; } @@ -977,74 +985,42 @@ static int max310x_gpio_direction_output(struct gpio_chip *chip, unsigned offset, int value) { struct max310x_port *s = container_of(chip, struct max310x_port, gpio); + struct uart_port *port = &s->p[offset / 4].port; - mutex_lock(&s->max310x_mutex); - - regmap_update_bits(s->regmap, MAX310X_GPIOCFG_REG, 1 << offset, - 1 << offset); - regmap_update_bits(s->regmap, MAX310X_GPIODATA_REG, 1 << offset, value ? - 1 << offset : 0); - - mutex_unlock(&s->max310x_mutex); + max310x_port_update(port, MAX310X_GPIODATA_REG, 1 << (offset % 4), + value ? 1 << (offset % 4) : 0); + max310x_port_update(port, MAX310X_GPIOCFG_REG, 1 << (offset % 4), + 1 << (offset % 4)); return 0; } #endif -/* Generic platform data */ -static struct max310x_pdata generic_plat_data = { - .driver_flags = MAX310X_EXT_CLK, - .uart_flags[0] = MAX310X_ECHO_SUPRESS, - .frequency = 26000000, -}; - -static int max310x_probe(struct spi_device *spi) +static int max310x_probe(struct device *dev, int is_spi, + struct max310x_devtype *devtype, int irq) { struct max310x_port *s; - struct device *dev = &spi->dev; - int chiptype = spi_get_device_id(spi)->driver_data; - struct max310x_pdata *pdata = dev->platform_data; - unsigned int val = 0; - int ret; + struct max310x_pdata *pdata = dev_get_platdata(dev); + int i, ret, uartclk; /* Check for IRQ */ - if (spi->irq <= 0) { + if (irq <= 0) { dev_err(dev, "No IRQ specified\n"); return -ENOTSUPP; } + if (!pdata) { + dev_err(dev, "No platform data supplied\n"); + return -EINVAL; + } + /* Alloc port structure */ - s = devm_kzalloc(dev, sizeof(struct max310x_port), GFP_KERNEL); + s = devm_kzalloc(dev, sizeof(*s) + + sizeof(struct max310x_one) * devtype->nr, GFP_KERNEL); if (!s) { dev_err(dev, "Error allocating port structure\n"); return -ENOMEM; } - dev_set_drvdata(dev, s); - - if (!pdata) { - dev_warn(dev, "No platform data supplied, using defaults\n"); - pdata = &generic_plat_data; - } - s->pdata = pdata; - - /* Individual chip settings */ - switch (chiptype) { - case MAX310X_TYPE_MAX3107: - s->name = "MAX3107"; - s->nr_gpio = 4; - s->uart.nr = 1; - s->regcfg.max_register = 0x1f; - break; - case MAX310X_TYPE_MAX3108: - s->name = "MAX3108"; - s->nr_gpio = 4; - s->uart.nr = 1; - s->regcfg.max_register = 0x1e; - break; - default: - dev_err(dev, "Unsupported chip type %i\n", chiptype); - return -ENOTSUPP; - } /* Check input frequency */ if ((pdata->driver_flags & MAX310X_EXT_CLK) && @@ -1055,13 +1031,11 @@ static int max310x_probe(struct spi_device *spi) ((pdata->frequency < 1000000) || (pdata->frequency > 4000000))) goto err_freq; - mutex_init(&s->max310x_mutex); + s->pdata = pdata; + s->devtype = devtype; + dev_set_drvdata(dev, s); - /* Setup SPI bus */ - spi->mode = SPI_MODE_0; - spi->bits_per_word = 8; - spi->max_speed_hz = 26000000; - spi_setup(spi); + mutex_init(&s->mutex); /* Setup regmap */ s->regcfg.reg_bits = 8; @@ -1069,109 +1043,100 @@ static int max310x_probe(struct spi_device *spi) s->regcfg.read_flag_mask = 0x00; s->regcfg.write_flag_mask = 0x80; s->regcfg.cache_type = REGCACHE_RBTREE; - s->regcfg.writeable_reg = max3107_8_reg_writeable; + s->regcfg.writeable_reg = max310x_reg_writeable; s->regcfg.volatile_reg = max310x_reg_volatile; s->regcfg.precious_reg = max310x_reg_precious; - s->regmap = devm_regmap_init_spi(spi, &s->regcfg); + s->regcfg.max_register = devtype->nr * 0x20 - 1; + + if (IS_ENABLED(CONFIG_SPI_MASTER) && is_spi) { + struct spi_device *spi = to_spi_device(dev); + + s->regmap = devm_regmap_init_spi(spi, &s->regcfg); + } else + return -ENOTSUPP; + if (IS_ERR(s->regmap)) { - ret = PTR_ERR(s->regmap); dev_err(dev, "Failed to initialize register map\n"); - goto err_out; - } - - /* Reset chip & check SPI function */ - ret = regmap_write(s->regmap, MAX310X_MODE2_REG, MAX310X_MODE2_RST_BIT); - if (ret) { - dev_err(dev, "SPI transfer failed\n"); - goto err_out; - } - /* Clear chip reset */ - regmap_write(s->regmap, MAX310X_MODE2_REG, 0); - - switch (chiptype) { - case MAX310X_TYPE_MAX3107: - /* Check REV ID to ensure we are talking to what we expect */ - regmap_read(s->regmap, MAX3107_REVID_REG, &val); - if (((val & MAX3107_REV_MASK) != MAX3107_REV_ID)) { - dev_err(dev, "%s ID 0x%02x does not match\n", - s->name, val); - ret = -ENODEV; - goto err_out; - } - break; - case MAX310X_TYPE_MAX3108: - /* MAX3108 have not REV ID register, we just check default value - * from clocksource register to make sure everything works. - */ - regmap_read(s->regmap, MAX310X_CLKSRC_REG, &val); - if (val != (MAX310X_CLKSRC_EXTCLK_BIT | - MAX310X_CLKSRC_PLLBYP_BIT)) { - dev_err(dev, "%s not present\n", s->name); - ret = -ENODEV; - goto err_out; - } - break; + return PTR_ERR(s->regmap); } /* Board specific configure */ - if (pdata->init) - pdata->init(); - if (pdata->suspend) - pdata->suspend(0); - - /* Calculate referecne clock */ - s->uartclk = max310x_set_ref_clk(s); - - /* Disable all interrupts */ - regmap_write(s->regmap, MAX310X_IRQEN_REG, 0); - - /* Setup MODE1 register */ - val = MAX310X_MODE1_IRQSEL_BIT; /* Enable IRQ pin */ - if (pdata->driver_flags & MAX310X_AUTOSLEEP) - val = MAX310X_MODE1_AUTOSLEEP_BIT; - regmap_write(s->regmap, MAX310X_MODE1_REG, val); - - /* Setup interrupt */ - ret = devm_request_threaded_irq(dev, spi->irq, NULL, max310x_ist, - IRQF_TRIGGER_FALLING | IRQF_ONESHOT, - dev_name(dev), s); - if (ret) { - dev_err(dev, "Unable to reguest IRQ %i\n", spi->irq); - goto err_out; + if (s->pdata->init) + s->pdata->init(); + + /* Check device to ensure we are talking to what we expect */ + ret = devtype->detect(dev); + if (ret) + return ret; + + for (i = 0; i < devtype->nr; i++) { + unsigned int offs = i << 5; + + /* Reset port */ + regmap_write(s->regmap, MAX310X_MODE2_REG + offs, + MAX310X_MODE2_RST_BIT); + /* Clear port reset */ + regmap_write(s->regmap, MAX310X_MODE2_REG + offs, 0); + + /* Wait for port startup */ + do { + regmap_read(s->regmap, + MAX310X_BRGDIVLSB_REG + offs, &ret); + } while (ret != 0x01); + + regmap_update_bits(s->regmap, MAX310X_MODE1_REG + offs, + MAX310X_MODE1_AUTOSLEEP_BIT, + MAX310X_MODE1_AUTOSLEEP_BIT); } + uartclk = max310x_set_ref_clk(s); + dev_dbg(dev, "Reference clock set to %i Hz\n", uartclk); + /* Register UART driver */ s->uart.owner = THIS_MODULE; - s->uart.driver_name = dev_name(dev); s->uart.dev_name = "ttyMAX"; s->uart.major = MAX310X_MAJOR; s->uart.minor = MAX310X_MINOR; + s->uart.nr = devtype->nr; ret = uart_register_driver(&s->uart); if (ret) { dev_err(dev, "Registering UART driver failed\n"); - goto err_out; + return ret; } - /* Initialize workqueue for start TX */ - s->wq = create_freezable_workqueue(dev_name(dev)); - INIT_WORK(&s->tx_work, max310x_wq_proc); - - /* Initialize UART port data */ - s->port.line = 0; - s->port.dev = dev; - s->port.irq = spi->irq; - s->port.type = PORT_MAX310X; - s->port.fifosize = MAX310X_FIFO_SIZE; - s->port.flags = UPF_SKIP_TEST | UPF_FIXED_TYPE; - s->port.iotype = UPIO_PORT; - s->port.membase = (void __iomem *)0xffffffff; /* Bogus value */ - s->port.uartclk = s->uartclk; - s->port.ops = &max310x_ops; - uart_add_one_port(&s->uart, &s->port); + for (i = 0; i < devtype->nr; i++) { + /* Initialize port data */ + s->p[i].port.line = i; + s->p[i].port.dev = dev; + s->p[i].port.irq = irq; + s->p[i].port.type = PORT_MAX310X; + s->p[i].port.fifosize = MAX310X_FIFO_SIZE; + s->p[i].port.flags = UPF_SKIP_TEST | UPF_FIXED_TYPE | + UPF_LOW_LATENCY; + s->p[i].port.iotype = UPIO_PORT; + s->p[i].port.iobase = i * 0x20; + s->p[i].port.membase = (void __iomem *)~0; + s->p[i].port.uartclk = uartclk; + s->p[i].port.ops = &max310x_ops; + /* Disable all interrupts */ + max310x_port_write(&s->p[i].port, MAX310X_IRQEN_REG, 0); + /* Clear IRQ status register */ + max310x_port_read(&s->p[i].port, MAX310X_IRQSTS_REG); + /* Enable IRQ pin */ + max310x_port_update(&s->p[i].port, MAX310X_MODE1_REG, + MAX310X_MODE1_IRQSEL_BIT, + MAX310X_MODE1_IRQSEL_BIT); + /* Initialize queue for start TX */ + INIT_WORK(&s->p[i].tx_work, max310x_wq_proc); + /* Register port */ + uart_add_one_port(&s->uart, &s->p[i].port); + /* Go to suspend mode */ + devtype->power(&s->p[i].port, 0); + } #ifdef CONFIG_GPIOLIB /* Setup GPIO cotroller */ - if (pdata->gpio_base) { + if (s->pdata->gpio_base) { s->gpio.owner = THIS_MODULE; s->gpio.dev = dev; s->gpio.label = dev_name(dev); @@ -1179,86 +1144,105 @@ static int max310x_probe(struct spi_device *spi) s->gpio.get = max310x_gpio_get; s->gpio.direction_output= max310x_gpio_direction_output; s->gpio.set = max310x_gpio_set; - s->gpio.base = pdata->gpio_base; - s->gpio.ngpio = s->nr_gpio; + s->gpio.base = s->pdata->gpio_base; + s->gpio.ngpio = devtype->nr * 4; s->gpio.can_sleep = 1; - if (gpiochip_add(&s->gpio)) { - /* Indicate that we should not call gpiochip_remove */ - s->gpio.base = 0; - } + if (!gpiochip_add(&s->gpio)) + s->gpio_used = 1; } else dev_info(dev, "GPIO support not enabled\n"); #endif - /* Go to suspend mode */ - if (pdata->suspend) - pdata->suspend(1); + /* Setup interrupt */ + ret = devm_request_threaded_irq(dev, irq, NULL, max310x_ist, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + dev_name(dev), s); + if (ret) { + dev_err(dev, "Unable to reguest IRQ %i\n", irq); +#ifdef CONFIG_GPIOLIB + if (s->gpio_used) + WARN_ON(gpiochip_remove(&s->gpio)); +#endif + } - return 0; + return ret; err_freq: dev_err(dev, "Frequency parameter incorrect\n"); - ret = -EINVAL; - -err_out: - dev_set_drvdata(dev, NULL); - - return ret; + return -EINVAL; } -static int max310x_remove(struct spi_device *spi) +static int max310x_remove(struct device *dev) { - struct device *dev = &spi->dev; struct max310x_port *s = dev_get_drvdata(dev); - int ret = 0; - - dev_dbg(dev, "Removing port\n"); - - devm_free_irq(dev, s->port.irq, s); + int i, ret = 0; - destroy_workqueue(s->wq); - - uart_remove_one_port(&s->uart, &s->port); + for (i = 0; i < s->uart.nr; i++) { + cancel_work_sync(&s->p[i].tx_work); + uart_remove_one_port(&s->uart, &s->p[i].port); + s->devtype->power(&s->p[i].port, 0); + } uart_unregister_driver(&s->uart); #ifdef CONFIG_GPIOLIB - if (s->pdata->gpio_base) { + if (s->gpio_used) ret = gpiochip_remove(&s->gpio); - if (ret) - dev_err(dev, "Failed to remove gpio chip: %d\n", ret); - } #endif - dev_set_drvdata(dev, NULL); - - if (s->pdata->suspend) - s->pdata->suspend(1); if (s->pdata->exit) s->pdata->exit(); return ret; } +#ifdef CONFIG_SPI_MASTER +static int max310x_spi_probe(struct spi_device *spi) +{ + struct max310x_devtype *devtype = + (struct max310x_devtype *)spi_get_device_id(spi)->driver_data; + int ret; + + /* Setup SPI bus */ + spi->bits_per_word = 8; + spi->mode = spi->mode ? : SPI_MODE_0; + spi->max_speed_hz = spi->max_speed_hz ? : 26000000; + ret = spi_setup(spi); + if (ret) { + dev_err(&spi->dev, "SPI setup failed\n"); + return ret; + } + + return max310x_probe(&spi->dev, 1, devtype, spi->irq); +} + +static int max310x_spi_remove(struct spi_device *spi) +{ + return max310x_remove(&spi->dev); +} + +static SIMPLE_DEV_PM_OPS(max310x_pm_ops, max310x_suspend, max310x_resume); + static const struct spi_device_id max310x_id_table[] = { - { "max3107", MAX310X_TYPE_MAX3107 }, - { "max3108", MAX310X_TYPE_MAX3108 }, + { "max3107", (kernel_ulong_t)&max3107_devtype, }, + { "max3108", (kernel_ulong_t)&max3108_devtype, }, { } }; MODULE_DEVICE_TABLE(spi, max310x_id_table); -static struct spi_driver max310x_driver = { +static struct spi_driver max310x_uart_driver = { .driver = { - .name = "max310x", + .name = MAX310X_NAME, .owner = THIS_MODULE, - .pm = MAX310X_PM_OPS, + .pm = &max310x_pm_ops, }, - .probe = max310x_probe, - .remove = max310x_remove, + .probe = max310x_spi_probe, + .remove = max310x_spi_remove, .id_table = max310x_id_table, }; -module_spi_driver(max310x_driver); +module_spi_driver(max310x_uart_driver); +#endif -MODULE_LICENSE("GPL v2"); +MODULE_LICENSE("GPL"); MODULE_AUTHOR("Alexander Shiyan "); MODULE_DESCRIPTION("MAX310X serial driver"); diff --git a/include/linux/platform_data/max310x.h b/include/linux/platform_data/max310x.h index 91648bf5fc5c..1aec0b620ac3 100644 --- a/include/linux/platform_data/max310x.h +++ b/include/linux/platform_data/max310x.h @@ -42,9 +42,8 @@ /* MAX310X platform data structure */ struct max310x_pdata { /* Flags global to driver */ - const u8 driver_flags:2; + const u8 driver_flags; #define MAX310X_EXT_CLK (0x00000001) /* External clock enable */ -#define MAX310X_AUTOSLEEP (0x00000002) /* Enable AutoSleep mode */ /* Flags global to UART port */ const u8 uart_flags[MAX310X_MAX_UARTS]; #define MAX310X_LOOPBACK (0x00000001) /* Loopback mode enable */ @@ -60,8 +59,6 @@ struct max310x_pdata { void (*init)(void); /* Called before finish */ void (*exit)(void); - /* Suspend callback */ - void (*suspend)(int do_suspend); }; #endif -- cgit v1.2.3 From 21fc509f1194c2fa06eff4c72238210089c29453 Mon Sep 17 00:00:00 2001 From: Alexander Shiyan Date: Sat, 29 Jun 2013 10:44:18 +0400 Subject: serial: max310x: Add MAX3109 support This patch adds support for MAX3109 (advanced dual universal asynchronous receiver-transmitter) into max310x driver. Signed-off-by: Alexander Shiyan Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/Kconfig | 2 +- drivers/tty/serial/max310x.c | 35 +++++++++++++++++++++++++++++++---- include/linux/platform_data/max310x.h | 4 ++-- 3 files changed, 34 insertions(+), 7 deletions(-) (limited to 'include/linux') diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index 25772c15276d..8e1a9c53ad7f 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -297,7 +297,7 @@ config SERIAL_MAX310X default n help This selects support for an advanced UART from Maxim (Dallas). - Supported ICs are MAX3107, MAX3108. + Supported ICs are MAX3107, MAX3108, MAX3109. Each IC contains 128 words each of receive and transmit FIFO that can be controlled through I2C or high-speed SPI. diff --git a/drivers/tty/serial/max310x.c b/drivers/tty/serial/max310x.c index 4620289e9e49..a6c46427363b 100644 --- a/drivers/tty/serial/max310x.c +++ b/drivers/tty/serial/max310x.c @@ -1,5 +1,5 @@ /* - * Maxim (Dallas) MAX3107/8 serial driver + * Maxim (Dallas) MAX3107/8/9 serial driver * * Copyright (C) 2012-2013 Alexander Shiyan * @@ -13,9 +13,6 @@ * (at your option) any later version. */ -/* TODO: MAX3109 support (Dual) */ -/* TODO: MAX14830 support (Quad) */ - #include #include #include @@ -269,6 +266,9 @@ /* MAX3107 specific */ #define MAX3107_REV_ID (0xa0) +/* MAX3109 specific */ +#define MAX3109_REV_ID (0xc0) + struct max310x_devtype { char name[9]; int nr; @@ -359,6 +359,25 @@ static int max3108_detect(struct device *dev) return 0; } +static int max3109_detect(struct device *dev) +{ + struct max310x_port *s = dev_get_drvdata(dev); + unsigned int val = 0; + int ret; + + ret = regmap_read(s->regmap, MAX310X_REVID_REG, &val); + if (ret) + return ret; + + if (((val & MAX310x_REV_MASK) != MAX3109_REV_ID)) { + dev_err(dev, + "%s ID 0x%02x does not match\n", s->devtype->name, val); + return -ENODEV; + } + + return 0; +} + static void max310x_power(struct uart_port *port, int on) { max310x_port_update(port, MAX310X_MODE1_REG, @@ -382,6 +401,13 @@ static const struct max310x_devtype max3108_devtype = { .power = max310x_power, }; +static const struct max310x_devtype max3109_devtype = { + .name = "MAX3109", + .nr = 2, + .detect = max3109_detect, + .power = max310x_power, +}; + static bool max310x_reg_writeable(struct device *dev, unsigned int reg) { switch (reg & 0x1f) { @@ -1226,6 +1252,7 @@ static SIMPLE_DEV_PM_OPS(max310x_pm_ops, max310x_suspend, max310x_resume); static const struct spi_device_id max310x_id_table[] = { { "max3107", (kernel_ulong_t)&max3107_devtype, }, { "max3108", (kernel_ulong_t)&max3108_devtype, }, + { "max3109", (kernel_ulong_t)&max3109_devtype, }, { } }; MODULE_DEVICE_TABLE(spi, max310x_id_table); diff --git a/include/linux/platform_data/max310x.h b/include/linux/platform_data/max310x.h index 1aec0b620ac3..4c128eda26ba 100644 --- a/include/linux/platform_data/max310x.h +++ b/include/linux/platform_data/max310x.h @@ -1,5 +1,5 @@ /* - * Maxim (Dallas) MAX3107/8 serial driver + * Maxim (Dallas) MAX3107/8/9 serial driver * * Copyright (C) 2012 Alexander Shiyan * @@ -37,7 +37,7 @@ * }; */ -#define MAX310X_MAX_UARTS 1 +#define MAX310X_MAX_UARTS 2 /* MAX310X platform data structure */ struct max310x_pdata { -- cgit v1.2.3 From 003236d9ac4d02721867e47f7ad4371ab7f74689 Mon Sep 17 00:00:00 2001 From: Alexander Shiyan Date: Sat, 29 Jun 2013 10:44:19 +0400 Subject: serial: max310x: Add MAX14830 support This patch adds support for MAX14830 (advanced quad universal asynchronous receiver-transmitter) into max310x driver. Signed-off-by: Alexander Shiyan Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/Kconfig | 2 +- drivers/tty/serial/max310x.c | 45 ++++++++++++++++++++++++++++++++++- include/linux/platform_data/max310x.h | 4 ++-- 3 files changed, 47 insertions(+), 4 deletions(-) (limited to 'include/linux') diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index 8e1a9c53ad7f..bc486deeeae5 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -297,7 +297,7 @@ config SERIAL_MAX310X default n help This selects support for an advanced UART from Maxim (Dallas). - Supported ICs are MAX3107, MAX3108, MAX3109. + Supported ICs are MAX3107, MAX3108, MAX3109, MAX14830. Each IC contains 128 words each of receive and transmit FIFO that can be controlled through I2C or high-speed SPI. diff --git a/drivers/tty/serial/max310x.c b/drivers/tty/serial/max310x.c index a6c46427363b..4ab5b272a593 100644 --- a/drivers/tty/serial/max310x.c +++ b/drivers/tty/serial/max310x.c @@ -1,5 +1,5 @@ /* - * Maxim (Dallas) MAX3107/8/9 serial driver + * Maxim (Dallas) MAX3107/8/9, MAX14830 serial driver * * Copyright (C) 2012-2013 Alexander Shiyan * @@ -269,6 +269,10 @@ /* MAX3109 specific */ #define MAX3109_REV_ID (0xc0) +/* MAX14830 specific */ +#define MAX14830_BRGCFG_CLKDIS_BIT (1 << 6) /* Clock Disable */ +#define MAX14830_REV_ID (0xb0) + struct max310x_devtype { char name[9]; int nr; @@ -387,6 +391,37 @@ static void max310x_power(struct uart_port *port, int on) msleep(50); } +static int max14830_detect(struct device *dev) +{ + struct max310x_port *s = dev_get_drvdata(dev); + unsigned int val = 0; + int ret; + + ret = regmap_write(s->regmap, MAX310X_GLOBALCMD_REG, + MAX310X_EXTREG_ENBL); + if (ret) + return ret; + + regmap_read(s->regmap, MAX310X_REVID_EXTREG, &val); + regmap_write(s->regmap, MAX310X_GLOBALCMD_REG, MAX310X_EXTREG_DSBL); + if (((val & MAX310x_REV_MASK) != MAX14830_REV_ID)) { + dev_err(dev, + "%s ID 0x%02x does not match\n", s->devtype->name, val); + return -ENODEV; + } + + return 0; +} + +static void max14830_power(struct uart_port *port, int on) +{ + max310x_port_update(port, MAX310X_BRGCFG_REG, + MAX14830_BRGCFG_CLKDIS_BIT, + on ? 0 : MAX14830_BRGCFG_CLKDIS_BIT); + if (on) + msleep(50); +} + static const struct max310x_devtype max3107_devtype = { .name = "MAX3107", .nr = 1, @@ -408,6 +443,13 @@ static const struct max310x_devtype max3109_devtype = { .power = max310x_power, }; +static const struct max310x_devtype max14830_devtype = { + .name = "MAX14830", + .nr = 4, + .detect = max14830_detect, + .power = max14830_power, +}; + static bool max310x_reg_writeable(struct device *dev, unsigned int reg) { switch (reg & 0x1f) { @@ -1253,6 +1295,7 @@ static const struct spi_device_id max310x_id_table[] = { { "max3107", (kernel_ulong_t)&max3107_devtype, }, { "max3108", (kernel_ulong_t)&max3108_devtype, }, { "max3109", (kernel_ulong_t)&max3109_devtype, }, + { "max14830", (kernel_ulong_t)&max14830_devtype, }, { } }; MODULE_DEVICE_TABLE(spi, max310x_id_table); diff --git a/include/linux/platform_data/max310x.h b/include/linux/platform_data/max310x.h index 4c128eda26ba..dd11dcd1a184 100644 --- a/include/linux/platform_data/max310x.h +++ b/include/linux/platform_data/max310x.h @@ -1,5 +1,5 @@ /* - * Maxim (Dallas) MAX3107/8/9 serial driver + * Maxim (Dallas) MAX3107/8/9, MAX14830 serial driver * * Copyright (C) 2012 Alexander Shiyan * @@ -37,7 +37,7 @@ * }; */ -#define MAX310X_MAX_UARTS 2 +#define MAX310X_MAX_UARTS 4 /* MAX310X platform data structure */ struct max310x_pdata { -- cgit v1.2.3 From 12197de2cdb2ded83db7b5a3a06f4b275fb8e62c Mon Sep 17 00:00:00 2001 From: Ian Abbott Date: Tue, 16 Jul 2013 16:14:38 +0100 Subject: pci_ids.h: move PCI_VENDOR_ID_AMCC here PCI_VENDOR_ID_AMCC is defined locally in "drivers/staging/comedi/comedidev.h" for a few comedi hardware drivers, namely "adl_pci9118", "addi_apci_1500" and "addi_apci_3120" (also "addi_apci_1710" but that is not currently built and will probably be removed soon). Move the define into "include/linux/pci_ids.h" as it is shared by several drivers (albeit all comedi drivers currently). PCI_VENDOR_ID_AMCC happens to have the same value (0x10e8) as PCI_VENDOR_ID_ADDIDATA_OLD. The vendor ID is actually assigned to Applied Micro Circuits Corporation and Addi-Data were using device IDs assigned by AMCC on some of their earlier PCI boards. The PCI_VENDOR_ID_ADDIDATA_OLD define is still being used by the "8250_pci" PCI serial board driver. Signed-off-by: Ian Abbott Acked-by: Bjorn Helgaas Signed-off-by: Greg Kroah-Hartman --- drivers/staging/comedi/comedidev.h | 1 - include/linux/pci_ids.h | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'include/linux') diff --git a/drivers/staging/comedi/comedidev.h b/drivers/staging/comedi/comedidev.h index b75915f30f48..9f4f73fa652c 100644 --- a/drivers/staging/comedi/comedidev.h +++ b/drivers/staging/comedi/comedidev.h @@ -400,7 +400,6 @@ int comedi_driver_unregister(struct comedi_driver *); */ #define PCI_VENDOR_ID_KOLTER 0x1001 #define PCI_VENDOR_ID_ICP 0x104c -#define PCI_VENDOR_ID_AMCC 0x10e8 #define PCI_VENDOR_ID_DT 0x1116 #define PCI_VENDOR_ID_IOTECH 0x1616 #define PCI_VENDOR_ID_CONTEC 0x1221 diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h index 3bed2e89611b..9d9911926598 100644 --- a/include/linux/pci_ids.h +++ b/include/linux/pci_ids.h @@ -1311,6 +1311,8 @@ #define PCI_DEVICE_ID_IMS_TT128 0x9128 #define PCI_DEVICE_ID_IMS_TT3D 0x9135 +#define PCI_VENDOR_ID_AMCC 0x10e8 + #define PCI_VENDOR_ID_INTERG 0x10ea #define PCI_DEVICE_ID_INTERG_1682 0x1682 #define PCI_DEVICE_ID_INTERG_2000 0x2000 @@ -2256,7 +2258,7 @@ /* * ADDI-DATA GmbH communication cards */ -#define PCI_VENDOR_ID_ADDIDATA_OLD 0x10E8 +#define PCI_VENDOR_ID_ADDIDATA_OLD 0x10E8 /* actually AMCC */ #define PCI_VENDOR_ID_ADDIDATA 0x15B8 #define PCI_DEVICE_ID_ADDIDATA_APCI7500 0x7000 #define PCI_DEVICE_ID_ADDIDATA_APCI7420 0x7001 -- cgit v1.2.3 From fee8bf4b74e37bfeb8998a349cb788680eb3f634 Mon Sep 17 00:00:00 2001 From: Ian Abbott Date: Tue, 16 Jul 2013 16:14:41 +0100 Subject: pci_ids.h: remove PCI_VENDOR_ID_ADDIDATA_OLD and PCI_DEVICE_ID_ADDIDATA_APCI7800 These two defines are no longer used. They were only used by the PCI serial driver "8250_pci" to support the original ADDI-DATA APCI-7800 card. In that driver, PCI_VENDOR_ID_ADDIDATA_OLD has been replaced with PCI_VENDOR_ID_AMCC which has the same value (0x10e8), and PCI_DEVICE_ID_ADDIDATA_APCI7800 has been replaced with a local #define PCI_DEVICE_ID_AMCC_ADDIDATA_APCI7800 with the same value (0x818e). Signed-off-by: Ian Abbott Acked-by: Bjorn Helgaas Signed-off-by: Greg Kroah-Hartman --- include/linux/pci_ids.h | 2 -- 1 file changed, 2 deletions(-) (limited to 'include/linux') diff --git a/include/linux/pci_ids.h b/include/linux/pci_ids.h index 9d9911926598..6dec3d6abe0b 100644 --- a/include/linux/pci_ids.h +++ b/include/linux/pci_ids.h @@ -2258,12 +2258,10 @@ /* * ADDI-DATA GmbH communication cards */ -#define PCI_VENDOR_ID_ADDIDATA_OLD 0x10E8 /* actually AMCC */ #define PCI_VENDOR_ID_ADDIDATA 0x15B8 #define PCI_DEVICE_ID_ADDIDATA_APCI7500 0x7000 #define PCI_DEVICE_ID_ADDIDATA_APCI7420 0x7001 #define PCI_DEVICE_ID_ADDIDATA_APCI7300 0x7002 -#define PCI_DEVICE_ID_ADDIDATA_APCI7800 0x818E #define PCI_DEVICE_ID_ADDIDATA_APCI7500_2 0x7009 #define PCI_DEVICE_ID_ADDIDATA_APCI7420_2 0x700A #define PCI_DEVICE_ID_ADDIDATA_APCI7300_2 0x700B -- cgit v1.2.3 From cf940ebe98e693aec2d10f9af2fb84eb55234e3c Mon Sep 17 00:00:00 2001 From: Andreas Platschek Date: Fri, 26 Jul 2013 06:46:37 +0200 Subject: tty: Remove dead code -> The ledptrs[] array is never initialized. -> There is no place where kbd->ledmode is set to LED_SHOW_MEM therefore the if statement does not make much sense. -> Since LED_SHOW_MEM is not used, it can be removed from the header file as well. Signed-off-by: Andreas Platschek Signed-off-by: Greg Kroah-Hartman --- drivers/tty/vt/keyboard.c | 21 +-------------------- include/linux/kbd_kern.h | 3 +-- 2 files changed, 2 insertions(+), 22 deletions(-) (limited to 'include/linux') diff --git a/drivers/tty/vt/keyboard.c b/drivers/tty/vt/keyboard.c index a9af1b9ae160..d0e3a4497707 100644 --- a/drivers/tty/vt/keyboard.c +++ b/drivers/tty/vt/keyboard.c @@ -132,12 +132,6 @@ static int shift_state = 0; static unsigned char ledstate = 0xff; /* undefined */ static unsigned char ledioctl; -static struct ledptr { - unsigned int *addr; - unsigned int mask; - unsigned char valid:1; -} ledptrs[3]; - /* * Notifier list for console keyboard events */ @@ -994,24 +988,11 @@ void setledstate(struct kbd_struct *kbd, unsigned int led) static inline unsigned char getleds(void) { struct kbd_struct *kbd = kbd_table + fg_console; - unsigned char leds; - int i; if (kbd->ledmode == LED_SHOW_IOCTL) return ledioctl; - leds = kbd->ledflagstate; - - if (kbd->ledmode == LED_SHOW_MEM) { - for (i = 0; i < 3; i++) - if (ledptrs[i].valid) { - if (*ledptrs[i].addr & ledptrs[i].mask) - leds |= (1 << i); - else - leds &= ~(1 << i); - } - } - return leds; + return kbd->ledflagstate; } static int kbd_update_leds_helper(struct input_handle *handle, void *data) diff --git a/include/linux/kbd_kern.h b/include/linux/kbd_kern.h index b7c8cdc1d422..cbfb171bbcba 100644 --- a/include/linux/kbd_kern.h +++ b/include/linux/kbd_kern.h @@ -36,10 +36,9 @@ struct kbd_struct { #define VC_CTRLRLOCK KG_CTRLR /* ctrlr lock mode */ unsigned char slockstate; /* for `sticky' Shift, Ctrl, etc. */ - unsigned char ledmode:2; /* one 2-bit value */ + unsigned char ledmode:1; #define LED_SHOW_FLAGS 0 /* traditional state */ #define LED_SHOW_IOCTL 1 /* only change leds upon ioctl */ -#define LED_SHOW_MEM 2 /* `heartbeat': peek into memory */ unsigned char ledflagstate:4; /* flags, not lights */ unsigned char default_ledflagstate:4; -- cgit v1.2.3 From 055560b04a8cd063aea916fd083b7aec02c2adb8 Mon Sep 17 00:00:00 2001 From: Elen Song Date: Mon, 22 Jul 2013 16:30:29 +0800 Subject: serial: at91: distinguish usart and uart Distinguish usart and uart by read ip name register, The usart read name is "USAR", The uart and dbgu read name is "DBGU". Signed-off-by: Elen Song Signed-off-by: Greg Kroah-Hartman --- drivers/tty/serial/atmel_serial.c | 37 +++++++++++++++++++++++++++++++++++++ include/linux/atmel_serial.h | 2 ++ 2 files changed, 39 insertions(+) (limited to 'include/linux') diff --git a/drivers/tty/serial/atmel_serial.c b/drivers/tty/serial/atmel_serial.c index 1db68713d656..8dbc3e67dfa4 100644 --- a/drivers/tty/serial/atmel_serial.c +++ b/drivers/tty/serial/atmel_serial.c @@ -97,6 +97,7 @@ static void atmel_stop_rx(struct uart_port *port); #define UART_PUT_BRGR(port,v) __raw_writel(v, (port)->membase + ATMEL_US_BRGR) #define UART_PUT_RTOR(port,v) __raw_writel(v, (port)->membase + ATMEL_US_RTOR) #define UART_PUT_TTGR(port, v) __raw_writel(v, (port)->membase + ATMEL_US_TTGR) +#define UART_GET_IP_NAME(port) __raw_readl((port)->membase + ATMEL_US_NAME) /* PDC registers */ #define UART_PUT_PTCR(port,v) __raw_writel(v, (port)->membase + ATMEL_PDC_PTCR) @@ -166,6 +167,7 @@ struct atmel_uart_port { struct serial_rs485 rs485; /* rs485 settings */ unsigned int tx_done_mask; + bool is_usart; /* usart or uart */ int (*prepare_rx)(struct uart_port *port); int (*prepare_tx)(struct uart_port *port); void (*schedule_rx)(struct uart_port *port); @@ -1477,6 +1479,34 @@ static void atmel_set_ops(struct uart_port *port) } } +/* + * Get ip name usart or uart + */ +static int atmel_get_ip_name(struct uart_port *port) +{ + struct atmel_uart_port *atmel_port = to_atmel_uart_port(port); + int name = UART_GET_IP_NAME(port); + int usart, uart; + /* usart and uart ascii */ + usart = 0x55534152; + uart = 0x44424755; + + atmel_port->is_usart = false; + + if (name == usart) { + dev_dbg(port->dev, "This is usart\n"); + atmel_port->is_usart = true; + } else if (name == uart) { + dev_dbg(port->dev, "This is uart\n"); + atmel_port->is_usart = false; + } else { + dev_err(port->dev, "Not supported ip name, set to uart\n"); + return -EINVAL; + } + + return 0; +} + /* * Perform initialization and enable port for reception */ @@ -2336,6 +2366,13 @@ static int atmel_serial_probe(struct platform_device *pdev) UART_PUT_CR(&port->uart, ATMEL_US_RTSEN); } + /* + * Get port name of usart or uart + */ + ret = atmel_get_ip_name(&port->uart); + if (ret < 0) + goto err_add_port; + return 0; err_add_port: diff --git a/include/linux/atmel_serial.h b/include/linux/atmel_serial.h index fd6833764d72..be201ca2990c 100644 --- a/include/linux/atmel_serial.h +++ b/include/linux/atmel_serial.h @@ -124,4 +124,6 @@ #define ATMEL_US_NER 0x44 /* Number of Errors Register */ #define ATMEL_US_IF 0x4c /* IrDA Filter Register */ +#define ATMEL_US_NAME 0xf0 /* Ip Name */ + #endif -- cgit v1.2.3 From 90efa75f7ab0be5677f0cca155dbf0b39eacdd03 Mon Sep 17 00:00:00 2001 From: Alexander Shiyan Date: Wed, 31 Jul 2013 14:56:30 +0400 Subject: serial: sccnxp: Using CLK API for getting UART clock This patch removes "frequency" parameter from SCCNXP platform_data and uses CLK API for getting clock. If CLK ommited, default IC frequency will be used instead. Signed-off-by: Alexander Shiyan Signed-off-by: Greg Kroah-Hartman --- arch/mips/sni/a20r.c | 1 - drivers/tty/serial/sccnxp.c | 36 ++++++++++++++++++----------- include/linux/platform_data/serial-sccnxp.h | 3 --- 3 files changed, 22 insertions(+), 18 deletions(-) (limited to 'include/linux') diff --git a/arch/mips/sni/a20r.c b/arch/mips/sni/a20r.c index dd0ab982d77e..f9407e170476 100644 --- a/arch/mips/sni/a20r.c +++ b/arch/mips/sni/a20r.c @@ -122,7 +122,6 @@ static struct resource sc26xx_rsrc[] = { static struct sccnxp_pdata sccnxp_data = { .reg_shift = 2, - .frequency = 3686400, .mctrl_cfg[0] = MCTRL_SIG(DTR_OP, LINE_OP7) | MCTRL_SIG(RTS_OP, LINE_OP3) | MCTRL_SIG(DSR_IP, LINE_IP5) | diff --git a/drivers/tty/serial/sccnxp.c b/drivers/tty/serial/sccnxp.c index 12a5c265f43a..81e70477e6fd 100644 --- a/drivers/tty/serial/sccnxp.c +++ b/drivers/tty/serial/sccnxp.c @@ -15,6 +15,7 @@ #define SUPPORT_SYSRQ #endif +#include #include #include #include @@ -783,9 +784,10 @@ static int sccnxp_probe(struct platform_device *pdev) struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); int chiptype = pdev->id_entry->driver_data; struct sccnxp_pdata *pdata = dev_get_platdata(&pdev->dev); - int i, ret, fifosize, freq_min, freq_max; + int i, ret, fifosize, freq_min, freq_max, uartclk; struct sccnxp_port *s; void __iomem *membase; + struct clk *clk; membase = devm_ioremap_resource(&pdev->dev, res); if (IS_ERR(membase)) @@ -898,11 +900,25 @@ static int sccnxp_probe(struct platform_device *pdev) } else if (PTR_ERR(s->regulator) == -EPROBE_DEFER) return -EPROBE_DEFER; - if (!pdata) { - dev_warn(&pdev->dev, - "No platform data supplied, using defaults\n"); - s->pdata.frequency = s->freq_std; + clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) { + if (PTR_ERR(clk) == -EPROBE_DEFER) { + ret = -EPROBE_DEFER; + goto err_out; + } + dev_notice(&pdev->dev, "Using default clock frequency\n"); + uartclk = s->freq_std; } else + uartclk = clk_get_rate(clk); + + /* Check input frequency */ + if ((uartclk < freq_min) || (uartclk > freq_max)) { + dev_err(&pdev->dev, "Frequency out of bounds\n"); + ret = -EINVAL; + goto err_out; + } + + if (pdata) memcpy(&s->pdata, pdata, sizeof(struct sccnxp_pdata)); if (s->pdata.poll_time_us) { @@ -920,14 +936,6 @@ static int sccnxp_probe(struct platform_device *pdev) } } - /* Check input frequency */ - if ((s->pdata.frequency < freq_min) || - (s->pdata.frequency > freq_max)) { - dev_err(&pdev->dev, "Frequency out of bounds\n"); - ret = -EINVAL; - goto err_out; - } - s->uart.owner = THIS_MODULE; s->uart.dev_name = "ttySC"; s->uart.major = SCCNXP_MAJOR; @@ -959,7 +967,7 @@ static int sccnxp_probe(struct platform_device *pdev) s->port[i].mapbase = res->start; s->port[i].membase = membase; s->port[i].regshift = s->pdata.reg_shift; - s->port[i].uartclk = s->pdata.frequency; + s->port[i].uartclk = uartclk; s->port[i].ops = &sccnxp_ops; uart_add_one_port(&s->uart, &s->port[i]); /* Set direction to input */ diff --git a/include/linux/platform_data/serial-sccnxp.h b/include/linux/platform_data/serial-sccnxp.h index bdc510d03245..af0c8c3b89ae 100644 --- a/include/linux/platform_data/serial-sccnxp.h +++ b/include/linux/platform_data/serial-sccnxp.h @@ -60,7 +60,6 @@ * }; * * static struct sccnxp_pdata sc2892_info = { - * .frequency = 3686400, * .mctrl_cfg[0] = MCTRL_SIG(DIR_OP, LINE_OP0), * .mctrl_cfg[1] = MCTRL_SIG(DIR_OP, LINE_OP1), * }; @@ -78,8 +77,6 @@ /* SCCNXP platform data structure */ struct sccnxp_pdata { - /* Frequency (extrenal clock or crystal) */ - int frequency; /* Shift for A0 line */ const u8 reg_shift; /* Modem control lines configuration */ -- cgit v1.2.3 From 85c996907473e4ef824774b97b26499adf66521f Mon Sep 17 00:00:00 2001 From: Alexander Shiyan Date: Wed, 31 Jul 2013 14:55:45 +0400 Subject: serial: sccnxp: Add DT support Add DT support to the SCCNCP serial driver. Signed-off-by: Alexander Shiyan Signed-off-by: Greg Kroah-Hartman --- .../bindings/tty/serial/sccnxp-serial.txt | 53 ++++++++++++++++++++++ drivers/tty/serial/sccnxp.c | 46 +++++++++++++++---- include/linux/platform_data/serial-sccnxp.h | 6 +-- 3 files changed, 93 insertions(+), 12 deletions(-) create mode 100644 Documentation/devicetree/bindings/tty/serial/sccnxp-serial.txt (limited to 'include/linux') diff --git a/Documentation/devicetree/bindings/tty/serial/sccnxp-serial.txt b/Documentation/devicetree/bindings/tty/serial/sccnxp-serial.txt new file mode 100644 index 000000000000..d18b1698133e --- /dev/null +++ b/Documentation/devicetree/bindings/tty/serial/sccnxp-serial.txt @@ -0,0 +1,53 @@ +* NXP (Philips) SCC+++(SCN+++) serial driver + +Required properties: +- compatible: Should be "nxp,". The supported ICs include sc2681, + sc2691, sc2692, sc2891, sc2892, sc28202, sc68681 and sc68692. +- reg: Address and length of the register set for the device. +- interrupts: Should contain the interrupt number. If omitted, + polling mode will be used instead, so "poll-interval" property should + be populated in this case. + +Optional properties: +- clocks: Phandle to input clock. If omitted, default IC frequency will be + used instead. +- poll-interval: Poll interval time in nanoseconds. +- vcc-supply: The regulator supplying the VCC to drive the chip. +- nxp,sccnxp-io-cfg: Array contains values for the emulated modem signals. + The number of values depends on the UART-number in the selected chip. + Each value should be composed according to the following rules: + (LINE1 << SIGNAL1) | ... | (LINEX << SIGNALX), where: + LINE - VALUE: + OP0 - 1 + OP1 - 2 + OP2 - 3 + OP3 - 4 + OP4 - 5 + OP5 - 6 + OP6 - 7 + OP7 - 8 + IP0 - 9 + IP1 - 10 + IP2 - 11 + IP3 - 12 + IP4 - 13 + IP5 - 14 + IP6 - 15 + SIGNAL - VALUE: + DTR - 0 + RTS - 4 + DSR - 8 + CTS - 12 + DCD - 16 + RNG - 20 + DIR - 24 + +Example (Dual UART with direction control on OP0 & OP1): +sc2892@10100000 { + compatible = "nxp,sc2892"; + reg = <0x10100000 0x10>; + poll-interval = <10000>; + clocks = <&sc2892_clk>; + vcc-supply = <&sc2892_reg>; + nxp,sccnxp-io-cfg = <0x01000000 0x02000000>; +}; diff --git a/drivers/tty/serial/sccnxp.c b/drivers/tty/serial/sccnxp.c index 49e9bbfe6cab..67f73d1a8e7b 100644 --- a/drivers/tty/serial/sccnxp.c +++ b/drivers/tty/serial/sccnxp.c @@ -20,6 +20,8 @@ #include #include #include +#include +#include #include #include #include @@ -853,10 +855,25 @@ static const struct platform_device_id sccnxp_id_table[] = { }; MODULE_DEVICE_TABLE(platform, sccnxp_id_table); +static const struct of_device_id sccnxp_dt_id_table[] = { + { .compatible = "nxp,sc2681", .data = &sc2681, }, + { .compatible = "nxp,sc2691", .data = &sc2691, }, + { .compatible = "nxp,sc2692", .data = &sc2692, }, + { .compatible = "nxp,sc2891", .data = &sc2891, }, + { .compatible = "nxp,sc2892", .data = &sc2892, }, + { .compatible = "nxp,sc28202", .data = &sc28202, }, + { .compatible = "nxp,sc68681", .data = &sc68681, }, + { .compatible = "nxp,sc68692", .data = &sc68692, }, + { } +}; +MODULE_DEVICE_TABLE(of, sccnxp_dt_id_table); + static int sccnxp_probe(struct platform_device *pdev) { struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); struct sccnxp_pdata *pdata = dev_get_platdata(&pdev->dev); + const struct of_device_id *of_id = + of_match_device(sccnxp_dt_id_table, &pdev->dev); int i, ret, uartclk; struct sccnxp_port *s; void __iomem *membase; @@ -875,7 +892,22 @@ static int sccnxp_probe(struct platform_device *pdev) spin_lock_init(&s->lock); - s->chip = (struct sccnxp_chip *)pdev->id_entry->driver_data; + if (of_id) { + s->chip = (struct sccnxp_chip *)of_id->data; + + of_property_read_u32(pdev->dev.of_node, "poll-interval", + &s->pdata.poll_time_us); + of_property_read_u32(pdev->dev.of_node, "reg-shift", + &s->pdata.reg_shift); + of_property_read_u32_array(pdev->dev.of_node, + "nxp,sccnxp-io-cfg", + s->pdata.mctrl_cfg, s->chip->nr); + } else { + s->chip = (struct sccnxp_chip *)pdev->id_entry->driver_data; + + if (pdata) + memcpy(&s->pdata, pdata, sizeof(struct sccnxp_pdata)); + } s->regulator = devm_regulator_get(&pdev->dev, "vcc"); if (!IS_ERR(s->regulator)) { @@ -906,16 +938,11 @@ static int sccnxp_probe(struct platform_device *pdev) goto err_out; } - if (pdata) - memcpy(&s->pdata, pdata, sizeof(struct sccnxp_pdata)); - if (s->pdata.poll_time_us) { dev_info(&pdev->dev, "Using poll mode, resolution %u usecs\n", s->pdata.poll_time_us); s->poll = 1; - } - - if (!s->poll) { + } else { s->irq = platform_get_irq(pdev, 0); if (s->irq < 0) { dev_err(&pdev->dev, "Missing irq resource data\n"); @@ -1016,8 +1043,9 @@ static int sccnxp_remove(struct platform_device *pdev) static struct platform_driver sccnxp_uart_driver = { .driver = { - .name = SCCNXP_NAME, - .owner = THIS_MODULE, + .name = SCCNXP_NAME, + .owner = THIS_MODULE, + .of_match_table = sccnxp_dt_id_table, }, .probe = sccnxp_probe, .remove = sccnxp_remove, diff --git a/include/linux/platform_data/serial-sccnxp.h b/include/linux/platform_data/serial-sccnxp.h index af0c8c3b89ae..98373d6add27 100644 --- a/include/linux/platform_data/serial-sccnxp.h +++ b/include/linux/platform_data/serial-sccnxp.h @@ -78,11 +78,11 @@ /* SCCNXP platform data structure */ struct sccnxp_pdata { /* Shift for A0 line */ - const u8 reg_shift; + u32 reg_shift; /* Modem control lines configuration */ - const u32 mctrl_cfg[SCCNXP_MAX_UARTS]; + u32 mctrl_cfg[SCCNXP_MAX_UARTS]; /* Timer value for polling mode (usecs) */ - const unsigned int poll_time_us; + u32 poll_time_us; }; #endif -- cgit v1.2.3 From 461a8ecb2d76fb6f3ac1a90eb886697db869d387 Mon Sep 17 00:00:00 2001 From: Greg Kroah-Hartman Date: Fri, 2 Aug 2013 15:25:19 +0800 Subject: Revert "serial: sccnxp: Add DT support" This reverts commit 85c996907473e4ef824774b97b26499adf66521f. Alexander wishes to remove this patch as it is incorrect. Reported-by: Alexander Shiyan Signed-off-by: Greg Kroah-Hartman --- .../bindings/tty/serial/sccnxp-serial.txt | 53 ---------------------- drivers/tty/serial/sccnxp.c | 46 ++++--------------- include/linux/platform_data/serial-sccnxp.h | 6 +-- 3 files changed, 12 insertions(+), 93 deletions(-) delete mode 100644 Documentation/devicetree/bindings/tty/serial/sccnxp-serial.txt (limited to 'include/linux') diff --git a/Documentation/devicetree/bindings/tty/serial/sccnxp-serial.txt b/Documentation/devicetree/bindings/tty/serial/sccnxp-serial.txt deleted file mode 100644 index d18b1698133e..000000000000 --- a/Documentation/devicetree/bindings/tty/serial/sccnxp-serial.txt +++ /dev/null @@ -1,53 +0,0 @@ -* NXP (Philips) SCC+++(SCN+++) serial driver - -Required properties: -- compatible: Should be "nxp,". The supported ICs include sc2681, - sc2691, sc2692, sc2891, sc2892, sc28202, sc68681 and sc68692. -- reg: Address and length of the register set for the device. -- interrupts: Should contain the interrupt number. If omitted, - polling mode will be used instead, so "poll-interval" property should - be populated in this case. - -Optional properties: -- clocks: Phandle to input clock. If omitted, default IC frequency will be - used instead. -- poll-interval: Poll interval time in nanoseconds. -- vcc-supply: The regulator supplying the VCC to drive the chip. -- nxp,sccnxp-io-cfg: Array contains values for the emulated modem signals. - The number of values depends on the UART-number in the selected chip. - Each value should be composed according to the following rules: - (LINE1 << SIGNAL1) | ... | (LINEX << SIGNALX), where: - LINE - VALUE: - OP0 - 1 - OP1 - 2 - OP2 - 3 - OP3 - 4 - OP4 - 5 - OP5 - 6 - OP6 - 7 - OP7 - 8 - IP0 - 9 - IP1 - 10 - IP2 - 11 - IP3 - 12 - IP4 - 13 - IP5 - 14 - IP6 - 15 - SIGNAL - VALUE: - DTR - 0 - RTS - 4 - DSR - 8 - CTS - 12 - DCD - 16 - RNG - 20 - DIR - 24 - -Example (Dual UART with direction control on OP0 & OP1): -sc2892@10100000 { - compatible = "nxp,sc2892"; - reg = <0x10100000 0x10>; - poll-interval = <10000>; - clocks = <&sc2892_clk>; - vcc-supply = <&sc2892_reg>; - nxp,sccnxp-io-cfg = <0x01000000 0x02000000>; -}; diff --git a/drivers/tty/serial/sccnxp.c b/drivers/tty/serial/sccnxp.c index 67f73d1a8e7b..49e9bbfe6cab 100644 --- a/drivers/tty/serial/sccnxp.c +++ b/drivers/tty/serial/sccnxp.c @@ -20,8 +20,6 @@ #include #include #include -#include -#include #include #include #include @@ -855,25 +853,10 @@ static const struct platform_device_id sccnxp_id_table[] = { }; MODULE_DEVICE_TABLE(platform, sccnxp_id_table); -static const struct of_device_id sccnxp_dt_id_table[] = { - { .compatible = "nxp,sc2681", .data = &sc2681, }, - { .compatible = "nxp,sc2691", .data = &sc2691, }, - { .compatible = "nxp,sc2692", .data = &sc2692, }, - { .compatible = "nxp,sc2891", .data = &sc2891, }, - { .compatible = "nxp,sc2892", .data = &sc2892, }, - { .compatible = "nxp,sc28202", .data = &sc28202, }, - { .compatible = "nxp,sc68681", .data = &sc68681, }, - { .compatible = "nxp,sc68692", .data = &sc68692, }, - { } -}; -MODULE_DEVICE_TABLE(of, sccnxp_dt_id_table); - static int sccnxp_probe(struct platform_device *pdev) { struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); struct sccnxp_pdata *pdata = dev_get_platdata(&pdev->dev); - const struct of_device_id *of_id = - of_match_device(sccnxp_dt_id_table, &pdev->dev); int i, ret, uartclk; struct sccnxp_port *s; void __iomem *membase; @@ -892,22 +875,7 @@ static int sccnxp_probe(struct platform_device *pdev) spin_lock_init(&s->lock); - if (of_id) { - s->chip = (struct sccnxp_chip *)of_id->data; - - of_property_read_u32(pdev->dev.of_node, "poll-interval", - &s->pdata.poll_time_us); - of_property_read_u32(pdev->dev.of_node, "reg-shift", - &s->pdata.reg_shift); - of_property_read_u32_array(pdev->dev.of_node, - "nxp,sccnxp-io-cfg", - s->pdata.mctrl_cfg, s->chip->nr); - } else { - s->chip = (struct sccnxp_chip *)pdev->id_entry->driver_data; - - if (pdata) - memcpy(&s->pdata, pdata, sizeof(struct sccnxp_pdata)); - } + s->chip = (struct sccnxp_chip *)pdev->id_entry->driver_data; s->regulator = devm_regulator_get(&pdev->dev, "vcc"); if (!IS_ERR(s->regulator)) { @@ -938,11 +906,16 @@ static int sccnxp_probe(struct platform_device *pdev) goto err_out; } + if (pdata) + memcpy(&s->pdata, pdata, sizeof(struct sccnxp_pdata)); + if (s->pdata.poll_time_us) { dev_info(&pdev->dev, "Using poll mode, resolution %u usecs\n", s->pdata.poll_time_us); s->poll = 1; - } else { + } + + if (!s->poll) { s->irq = platform_get_irq(pdev, 0); if (s->irq < 0) { dev_err(&pdev->dev, "Missing irq resource data\n"); @@ -1043,9 +1016,8 @@ static int sccnxp_remove(struct platform_device *pdev) static struct platform_driver sccnxp_uart_driver = { .driver = { - .name = SCCNXP_NAME, - .owner = THIS_MODULE, - .of_match_table = sccnxp_dt_id_table, + .name = SCCNXP_NAME, + .owner = THIS_MODULE, }, .probe = sccnxp_probe, .remove = sccnxp_remove, diff --git a/include/linux/platform_data/serial-sccnxp.h b/include/linux/platform_data/serial-sccnxp.h index 98373d6add27..af0c8c3b89ae 100644 --- a/include/linux/platform_data/serial-sccnxp.h +++ b/include/linux/platform_data/serial-sccnxp.h @@ -78,11 +78,11 @@ /* SCCNXP platform data structure */ struct sccnxp_pdata { /* Shift for A0 line */ - u32 reg_shift; + const u8 reg_shift; /* Modem control lines configuration */ - u32 mctrl_cfg[SCCNXP_MAX_UARTS]; + const u32 mctrl_cfg[SCCNXP_MAX_UARTS]; /* Timer value for polling mode (usecs) */ - u32 poll_time_us; + const unsigned int poll_time_us; }; #endif -- cgit v1.2.3 From 5c19e95216b93b0d29c6a4887e69a980edc6fc81 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Mon, 5 Aug 2013 14:40:44 +0200 Subject: OF: Add helper for matching against linux,stdout-path devicetrees may have a linux,stdout-path property in the chosen node describing the console device. This adds a helper function to match a device against this property so a driver can call add_preferred_console for a matching device. Signed-off-by: Sascha Hauer Acked-by: Rob Herring Signed-off-by: Greg Kroah-Hartman --- drivers/of/base.c | 26 ++++++++++++++++++++++++++ include/linux/of.h | 7 +++++++ 2 files changed, 33 insertions(+) (limited to 'include/linux') diff --git a/drivers/of/base.c b/drivers/of/base.c index 5c5427918eb2..1c3c79c79742 100644 --- a/drivers/of/base.c +++ b/drivers/of/base.c @@ -32,6 +32,7 @@ struct device_node *of_allnodes; EXPORT_SYMBOL(of_allnodes); struct device_node *of_chosen; struct device_node *of_aliases; +static struct device_node *of_stdout; DEFINE_MUTEX(of_aliases_mutex); @@ -1595,6 +1596,15 @@ void of_alias_scan(void * (*dt_alloc)(u64 size, u64 align)) of_chosen = of_find_node_by_path("/chosen"); if (of_chosen == NULL) of_chosen = of_find_node_by_path("/chosen@0"); + + if (of_chosen) { + const char *name; + + name = of_get_property(of_chosen, "linux,stdout-path", NULL); + if (name) + of_stdout = of_find_node_by_path(name); + } + of_aliases = of_find_node_by_path("/aliases"); if (!of_aliases) return; @@ -1703,3 +1713,19 @@ const char *of_prop_next_string(struct property *prop, const char *cur) return curv; } EXPORT_SYMBOL_GPL(of_prop_next_string); + +/** + * of_device_is_stdout_path - check if a device node matches the + * linux,stdout-path property + * + * Check if this device node matches the linux,stdout-path property + * in the chosen node. return true if yes, false otherwise. + */ +int of_device_is_stdout_path(struct device_node *dn) +{ + if (!of_stdout) + return false; + + return of_stdout == dn; +} +EXPORT_SYMBOL_GPL(of_device_is_stdout_path); diff --git a/include/linux/of.h b/include/linux/of.h index 1fd08ca23106..429e16801858 100644 --- a/include/linux/of.h +++ b/include/linux/of.h @@ -343,6 +343,8 @@ const char *of_prop_next_string(struct property *prop, const char *cur); s; \ s = of_prop_next_string(prop, s)) +int of_device_is_stdout_path(struct device_node *dn); + #else /* CONFIG_OF */ static inline const char* of_node_full_name(struct device_node *np) @@ -505,6 +507,11 @@ static inline int of_machine_is_compatible(const char *compat) return 0; } +static inline int of_device_is_stdout_path(struct device_node *dn) +{ + return 0; +} + #define of_match_ptr(_ptr) NULL #define of_match_node(_matches, _node) NULL #define of_property_for_each_u32(np, propname, prop, p, u) \ -- cgit v1.2.3