From cf0ebee0d0374c6d75494ac96f86b4aea482dd09 Mon Sep 17 00:00:00 2001 From: Sourav Poddar Date: Wed, 15 May 2013 21:05:37 +0530 Subject: serial: Move "uart_console" def to core header file. Move "uart_console" definition to serial core header file, so that it can be used by serial drivers. Get rid of the uart_console defintion from mpc52xx_uart driver. Cc: Santosh Shilimkar Cc: Rajendra nayak Reviewed-by: Felipe Balbi Reviewed-by: Kevin Hilman Tested-by: Kevin Hilman Signed-off-by: Sourav Poddar Signed-off-by: Greg Kroah-Hartman --- include/linux/serial_core.h | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'include') diff --git a/include/linux/serial_core.h b/include/linux/serial_core.h index 87d4bbc773f..b98291ac7f1 100644 --- a/include/linux/serial_core.h +++ b/include/linux/serial_core.h @@ -31,6 +31,13 @@ #include #include +#ifdef CONFIG_SERIAL_CORE_CONSOLE +#define uart_console(port) \ + ((port)->cons && (port)->cons->index == (port)->line) +#else +#define uart_console(port) (0) +#endif + struct uart_port; struct serial_struct; struct device; -- cgit v1.2.3-70-g09d2 From 582f55907d3f6cb762af20b56478bf25ef96430e Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Fri, 17 May 2013 12:49:48 -0400 Subject: tty: Remove TTY_HW_COOK_IN/OUT No in-tree tty driver supports cooked mode in hardware; remove. Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/n_tty.c | 12 +++--------- include/linux/tty.h | 2 -- 2 files changed, 3 insertions(+), 11 deletions(-) (limited to 'include') diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c index d655416087b..905a6fa5250 100644 --- a/drivers/tty/n_tty.c +++ b/drivers/tty/n_tty.c @@ -647,8 +647,7 @@ static void process_echoes(struct tty_struct *tty) if (no_space_left) break; } else { - if (O_OPOST(tty) && - !(test_bit(TTY_HW_COOK_OUT, &tty->flags))) { + if (O_OPOST(tty)) { int retval = do_output_char(c, tty, space); if (retval < 0) break; @@ -1516,12 +1515,7 @@ static void n_tty_set_termios(struct tty_struct *tty, struct ktermios *old) wake_up_interruptible(&tty->read_wait); ldata->icanon = (L_ICANON(tty) != 0); - if (test_bit(TTY_HW_COOK_IN, &tty->flags)) { - ldata->raw = 1; - ldata->real_raw = 1; - n_tty_set_room(tty); - return; - } + if (I_ISTRIP(tty) || I_IUCLC(tty) || I_IGNCR(tty) || I_ICRNL(tty) || I_INLCR(tty) || L_ICANON(tty) || I_IXON(tty) || L_ISIG(tty) || L_ECHO(tty) || @@ -2037,7 +2031,7 @@ static ssize_t n_tty_write(struct tty_struct *tty, struct file *file, retval = -EIO; break; } - if (O_OPOST(tty) && !(test_bit(TTY_HW_COOK_OUT, &tty->flags))) { + if (O_OPOST(tty)) { while (nr > 0) { ssize_t num = process_output_block(tty, b, nr); if (num < 0) { diff --git a/include/linux/tty.h b/include/linux/tty.h index 8780bd2a272..82ab69bc9b7 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -309,8 +309,6 @@ struct tty_file_private { #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_HW_COOK_OUT 14 /* Hardware can do output cooking */ -#define TTY_HW_COOK_IN 15 /* Hardware can do input cooking */ #define TTY_PTY_LOCK 16 /* pty private */ #define TTY_NO_WRITE_SPLIT 17 /* Preserve write boundaries to driver */ #define TTY_HUPPED 18 /* Post driver->hangup() */ -- cgit v1.2.3-70-g09d2 From dc9641895abb30aa456918a433a52e13fa0f56b1 Mon Sep 17 00:00:00 2001 From: Wang YanQing Date: Thu, 9 May 2013 02:13:59 +0800 Subject: vt: delete unneeded functions register_con_driver|take_over_console Now there is no place use register_con_driver|take_over_console, and we can achieve their function with do_register_con_driver| do_take_over_console easily, so just delete them to reduce code duplication. Signed-off-by: Wang YanQing Signed-off-by: Greg Kroah-Hartman --- drivers/tty/vt/vt.c | 45 --------------------------------------------- include/linux/console.h | 2 -- 2 files changed, 47 deletions(-) (limited to 'include') diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c index fbd447b390f..852d470d674 100644 --- a/drivers/tty/vt/vt.c +++ b/drivers/tty/vt/vt.c @@ -3576,26 +3576,6 @@ err: return retval; } -/** - * register_con_driver - register console driver to console layer - * @csw: console driver - * @first: the first console to take over, minimum value is 0 - * @last: the last console to take over, maximum value is MAX_NR_CONSOLES -1 - * - * DESCRIPTION: This function registers a console driver which can later - * bind to a range of consoles specified by @first and @last. It will - * also initialize the console driver by calling con_startup(). - */ -int register_con_driver(const struct consw *csw, int first, int last) -{ - int retval; - - console_lock(); - retval = do_register_con_driver(csw, first, last); - console_unlock(); - return retval; -} -EXPORT_SYMBOL(register_con_driver); /** * unregister_con_driver - unregister console driver from console layer @@ -3677,30 +3657,6 @@ int do_take_over_console(const struct consw *csw, int first, int last, int deflt } EXPORT_SYMBOL_GPL(do_take_over_console); -/* - * If we support more console drivers, this function is used - * when a driver wants to take over some existing consoles - * and become default driver for newly opened ones. - * - * take_over_console is basically a register followed by unbind - */ -int take_over_console(const struct consw *csw, int first, int last, int deflt) -{ - int err; - - err = register_con_driver(csw, first, last); - /* - * If we get an busy error we still want to bind the console driver - * and return success, as we may have unbound the console driver - * but not unregistered it. - */ - if (err == -EBUSY) - err = 0; - if (!err) - bind_con_driver(csw, first, last, deflt); - - return err; -} /* * give_up_console is a wrapper to unregister_con_driver. It will only @@ -4264,6 +4220,5 @@ EXPORT_SYMBOL(console_blanked); EXPORT_SYMBOL(vc_cons); EXPORT_SYMBOL(global_cursor_default); #ifndef VT_SINGLE_DRIVER -EXPORT_SYMBOL(take_over_console); EXPORT_SYMBOL(give_up_console); #endif diff --git a/include/linux/console.h b/include/linux/console.h index 73bab0f58af..8f9bd966dbc 100644 --- a/include/linux/console.h +++ b/include/linux/console.h @@ -75,10 +75,8 @@ extern const struct consw newport_con; /* SGI Newport console */ extern const struct consw prom_con; /* SPARC PROM console */ int con_is_bound(const struct consw *csw); -int register_con_driver(const struct consw *csw, int first, int last); int unregister_con_driver(const struct consw *csw); int do_unregister_con_driver(const struct consw *csw); -int take_over_console(const struct consw *sw, int first, int last, int deflt); int do_take_over_console(const struct consw *sw, int first, int last, int deflt); void give_up_console(const struct consw *sw); #ifdef CONFIG_HW_CONSOLE -- cgit v1.2.3-70-g09d2 From c1f5e38a5d3583b877619f95a922b607966dfc2e Mon Sep 17 00:00:00 2001 From: Wang YanQing Date: Thu, 9 May 2013 02:14:15 +0800 Subject: vt: delete unneeded function unbind_con_driver Now there is no place use unbind_con_driver, and we can achieve unbind_con_driver's function with do_unbind_con_driver easily, so just delete it to reduce code size and duplication. Signed-off-by: Wang YanQing Signed-off-by: Greg Kroah-Hartman --- drivers/tty/vt/vt.c | 28 ---------------------------- include/linux/vt_kern.h | 2 -- 2 files changed, 30 deletions(-) (limited to 'include') diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c index a422322cf8b..164cbeff567 100644 --- a/drivers/tty/vt/vt.c +++ b/drivers/tty/vt/vt.c @@ -3116,34 +3116,6 @@ static int con_is_graphics(const struct consw *csw, int first, int last) return retval; } -/** - * unbind_con_driver - unbind a console driver - * @csw: pointer to console driver to unregister - * @first: first in range of consoles that @csw should be unbound from - * @last: last in range of consoles that @csw should be unbound from - * @deflt: should next bound console driver be default after @csw is unbound? - * - * To unbind a driver from all possible consoles, pass 0 as @first and - * %MAX_NR_CONSOLES as @last. - * - * @deflt controls whether the console that ends up replacing @csw should be - * the default console. - * - * RETURNS: - * -ENODEV if @csw isn't a registered console driver or can't be unregistered - * or 0 on success. - */ -int unbind_con_driver(const struct consw *csw, int first, int last, int deflt) -{ - int retval; - - console_lock(); - retval = do_unbind_con_driver(csw, first, last, deflt); - console_unlock(); - return retval; -} -EXPORT_SYMBOL(unbind_con_driver); - /* unlocked version of unbind_con_driver() */ int do_unbind_con_driver(const struct consw *csw, int first, int last, int deflt) { diff --git a/include/linux/vt_kern.h b/include/linux/vt_kern.h index e8d65718560..adc7ff58bef 100644 --- a/include/linux/vt_kern.h +++ b/include/linux/vt_kern.h @@ -133,8 +133,6 @@ void change_console(struct vc_data *new_vc); void reset_vc(struct vc_data *vc); extern int do_unbind_con_driver(const struct consw *csw, int first, int last, int deflt); -extern int unbind_con_driver(const struct consw *csw, int first, int last, - int deflt); int vty_init(const struct file_operations *console_fops); static inline bool vt_force_oops_output(struct vc_data *vc) -- cgit v1.2.3-70-g09d2 From 50539dd4f88e8a689a38c94337768fd7ff3fd326 Mon Sep 17 00:00:00 2001 From: Wang YanQing Date: Thu, 9 May 2013 02:14:44 +0800 Subject: vt: delete unneeded function unregister_con_driver Now there is no place use unregister_con_driver, and we can achieve unregister_con_driver's function with unregister_con_driver easily, so just delete it to reduce code size and duplication. Signed-off-by: Wang YanQing Signed-off-by: Greg Kroah-Hartman --- drivers/tty/vt/vt.c | 13 +------------ include/linux/console.h | 1 - 2 files changed, 1 insertion(+), 13 deletions(-) (limited to 'include') diff --git a/drivers/tty/vt/vt.c b/drivers/tty/vt/vt.c index 8f4a71aa971..405e1c90ada 100644 --- a/drivers/tty/vt/vt.c +++ b/drivers/tty/vt/vt.c @@ -3545,7 +3545,7 @@ err: /** - * unregister_con_driver - unregister console driver from console layer + * do_unregister_con_driver - unregister console driver from console layer * @csw: console driver * * DESCRIPTION: All drivers that registers to the console layer must @@ -3555,17 +3555,6 @@ err: * * The driver must unbind first prior to unregistration. */ -int unregister_con_driver(const struct consw *csw) -{ - int retval; - - console_lock(); - retval = do_unregister_con_driver(csw); - console_unlock(); - return retval; -} -EXPORT_SYMBOL(unregister_con_driver); - int do_unregister_con_driver(const struct consw *csw) { int i, retval = -ENODEV; diff --git a/include/linux/console.h b/include/linux/console.h index 8f9bd966dbc..7571a16bd65 100644 --- a/include/linux/console.h +++ b/include/linux/console.h @@ -75,7 +75,6 @@ extern const struct consw newport_con; /* SGI Newport console */ extern const struct consw prom_con; /* SPARC PROM console */ int con_is_bound(const struct consw *csw); -int unregister_con_driver(const struct consw *csw); int do_unregister_con_driver(const struct consw *csw); int do_take_over_console(const struct consw *sw, int first, int last, int deflt); void give_up_console(const struct consw *sw); -- cgit v1.2.3-70-g09d2 From 4898e640caf03fdbaf2122d5a33949bf3e4a5b34 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Tue, 16 Apr 2013 06:15:50 -0400 Subject: tty: Add timed, writer-prioritized rw semaphore The semantics of a rw semaphore are almost ideally suited for tty line discipline lifetime management; multiple active threads obtain "references" (read locks) while performing i/o to prevent the loss or change of the current line discipline (write lock). Unfortunately, the existing rw_semaphore is ill-suited in other ways; 1) TIOCSETD ioctl (change line discipline) expects to return an error if the line discipline cannot be exclusively locked within 5 secs. Lock wait timeouts are not supported by rwsem. 2) A tty hangup is expected to halt and scrap pending i/o, so exclusive locking must be prioritized. Writer priority is not supported by rwsem. Add ld_semaphore which implements these requirements in a semantically similar way to rw_semaphore. Writer priority is handled by separate wait lists for readers and writers. Pending write waits are priortized before existing read waits and prevent further read locks. Wait timeouts are trivially added, but obviously change the lock semantics as lock attempts can fail (but only due to timeout). This implementation incorporates the write-lock stealing work of Michel Lespinasse . Cc: Michel Lespinasse Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/Makefile | 2 +- drivers/tty/tty_ldsem.c | 453 ++++++++++++++++++++++++++++++++++++++++++++++ include/linux/tty_ldisc.h | 46 +++++ 3 files changed, 500 insertions(+), 1 deletion(-) create mode 100644 drivers/tty/tty_ldsem.c (limited to 'include') diff --git a/drivers/tty/Makefile b/drivers/tty/Makefile index 6b78399bc7c..58ad1c05b7f 100644 --- a/drivers/tty/Makefile +++ b/drivers/tty/Makefile @@ -1,5 +1,5 @@ obj-$(CONFIG_TTY) += tty_io.o n_tty.o tty_ioctl.o tty_ldisc.o \ - tty_buffer.o tty_port.o tty_mutex.o + tty_buffer.o tty_port.o tty_mutex.o tty_ldsem.o obj-$(CONFIG_LEGACY_PTYS) += pty.o obj-$(CONFIG_UNIX98_PTYS) += pty.o obj-$(CONFIG_AUDIT) += tty_audit.o diff --git a/drivers/tty/tty_ldsem.c b/drivers/tty/tty_ldsem.c new file mode 100644 index 00000000000..22fad8ad5ac --- /dev/null +++ b/drivers/tty/tty_ldsem.c @@ -0,0 +1,453 @@ +/* + * Ldisc rw semaphore + * + * The ldisc semaphore is semantically a rw_semaphore but which enforces + * an alternate policy, namely: + * 1) Supports lock wait timeouts + * 2) Write waiter has priority + * 3) Downgrading is not supported + * + * Implementation notes: + * 1) Upper half of semaphore count is a wait count (differs from rwsem + * in that rwsem normalizes the upper half to the wait bias) + * 2) Lacks overflow checking + * + * The generic counting was copied and modified from include/asm-generic/rwsem.h + * by Paul Mackerras . + * + * The scheduling policy was copied and modified from lib/rwsem.c + * Written by David Howells (dhowells@redhat.com). + * + * This implementation incorporates the write lock stealing work of + * Michel Lespinasse . + * + * Copyright (C) 2013 Peter Hurley + * + * This file may be redistributed under the terms of the GNU General Public + * License v2. + */ + +#include +#include +#include +#include +#include + + +#ifdef CONFIG_DEBUG_LOCK_ALLOC +# define __acq(l, s, t, r, c, n, i) \ + lock_acquire(&(l)->dep_map, s, t, r, c, n, i) +# define __rel(l, n, i) \ + lock_release(&(l)->dep_map, n, i) +# ifdef CONFIG_PROVE_LOCKING +# define lockdep_acquire(l, s, t, i) __acq(l, s, t, 0, 2, NULL, i) +# define lockdep_acquire_nest(l, s, t, n, i) __acq(l, s, t, 0, 2, n, i) +# define lockdep_acquire_read(l, s, t, i) __acq(l, s, t, 1, 2, NULL, i) +# define lockdep_release(l, n, i) __rel(l, n, i) +# else +# define lockdep_acquire(l, s, t, i) __acq(l, s, t, 0, 1, NULL, i) +# define lockdep_acquire_nest(l, s, t, n, i) __acq(l, s, t, 0, 1, n, i) +# define lockdep_acquire_read(l, s, t, i) __acq(l, s, t, 1, 1, NULL, i) +# define lockdep_release(l, n, i) __rel(l, n, i) +# endif +#else +# define lockdep_acquire(l, s, t, i) do { } while (0) +# define lockdep_acquire_nest(l, s, t, n, i) do { } while (0) +# define lockdep_acquire_read(l, s, t, i) do { } while (0) +# define lockdep_release(l, n, i) do { } while (0) +#endif + +#ifdef CONFIG_LOCK_STAT +# define lock_stat(_lock, stat) lock_##stat(&(_lock)->dep_map, _RET_IP_) +#else +# define lock_stat(_lock, stat) do { } while (0) +#endif + + +#if BITS_PER_LONG == 64 +# define LDSEM_ACTIVE_MASK 0xffffffffL +#else +# define LDSEM_ACTIVE_MASK 0x0000ffffL +#endif + +#define LDSEM_UNLOCKED 0L +#define LDSEM_ACTIVE_BIAS 1L +#define LDSEM_WAIT_BIAS (-LDSEM_ACTIVE_MASK-1) +#define LDSEM_READ_BIAS LDSEM_ACTIVE_BIAS +#define LDSEM_WRITE_BIAS (LDSEM_WAIT_BIAS + LDSEM_ACTIVE_BIAS) + +struct ldsem_waiter { + struct list_head list; + struct task_struct *task; +}; + +static inline long ldsem_atomic_update(long delta, struct ld_semaphore *sem) +{ + return atomic_long_add_return(delta, (atomic_long_t *)&sem->count); +} + +static inline int ldsem_cmpxchg(long *old, long new, struct ld_semaphore *sem) +{ + long tmp = *old; + *old = atomic_long_cmpxchg(&sem->count, *old, new); + return *old == tmp; +} + +/* + * Initialize an ldsem: + */ +void __init_ldsem(struct ld_semaphore *sem, const char *name, + struct lock_class_key *key) +{ +#ifdef CONFIG_DEBUG_LOCK_ALLOC + /* + * Make sure we are not reinitializing a held semaphore: + */ + debug_check_no_locks_freed((void *)sem, sizeof(*sem)); + lockdep_init_map(&sem->dep_map, name, key, 0); +#endif + sem->count = LDSEM_UNLOCKED; + sem->wait_readers = 0; + raw_spin_lock_init(&sem->wait_lock); + INIT_LIST_HEAD(&sem->read_wait); + INIT_LIST_HEAD(&sem->write_wait); +} + +static void __ldsem_wake_readers(struct ld_semaphore *sem) +{ + struct ldsem_waiter *waiter, *next; + struct task_struct *tsk; + long adjust, count; + + /* Try to grant read locks to all readers on the read wait list. + * Note the 'active part' of the count is incremented by + * the number of readers before waking any processes up. + */ + adjust = sem->wait_readers * (LDSEM_ACTIVE_BIAS - LDSEM_WAIT_BIAS); + count = ldsem_atomic_update(adjust, sem); + do { + if (count > 0) + break; + if (ldsem_cmpxchg(&count, count - adjust, sem)) + return; + } while (1); + + list_for_each_entry_safe(waiter, next, &sem->read_wait, list) { + tsk = waiter->task; + smp_mb(); + waiter->task = NULL; + wake_up_process(tsk); + put_task_struct(tsk); + } + INIT_LIST_HEAD(&sem->read_wait); + sem->wait_readers = 0; +} + +static inline int writer_trylock(struct ld_semaphore *sem) +{ + /* only wake this writer if the active part of the count can be + * transitioned from 0 -> 1 + */ + long count = ldsem_atomic_update(LDSEM_ACTIVE_BIAS, sem); + do { + if ((count & LDSEM_ACTIVE_MASK) == LDSEM_ACTIVE_BIAS) + return 1; + if (ldsem_cmpxchg(&count, count - LDSEM_ACTIVE_BIAS, sem)) + return 0; + } while (1); +} + +static void __ldsem_wake_writer(struct ld_semaphore *sem) +{ + struct ldsem_waiter *waiter; + + waiter = list_entry(sem->write_wait.next, struct ldsem_waiter, list); + wake_up_process(waiter->task); +} + +/* + * handle the lock release when processes blocked on it that can now run + * - if we come here from up_xxxx(), then: + * - the 'active part' of count (&0x0000ffff) reached 0 (but may have changed) + * - the 'waiting part' of count (&0xffff0000) is -ve (and will still be so) + * - the spinlock must be held by the caller + * - woken process blocks are discarded from the list after having task zeroed + */ +static void __ldsem_wake(struct ld_semaphore *sem) +{ + if (!list_empty(&sem->write_wait)) + __ldsem_wake_writer(sem); + else if (!list_empty(&sem->read_wait)) + __ldsem_wake_readers(sem); +} + +static void ldsem_wake(struct ld_semaphore *sem) +{ + unsigned long flags; + + raw_spin_lock_irqsave(&sem->wait_lock, flags); + __ldsem_wake(sem); + raw_spin_unlock_irqrestore(&sem->wait_lock, flags); +} + +/* + * wait for the read lock to be granted + */ +static struct ld_semaphore __sched * +down_read_failed(struct ld_semaphore *sem, long count, long timeout) +{ + struct ldsem_waiter waiter; + struct task_struct *tsk = current; + long adjust = -LDSEM_ACTIVE_BIAS + LDSEM_WAIT_BIAS; + + /* set up my own style of waitqueue */ + raw_spin_lock_irq(&sem->wait_lock); + + /* Try to reverse the lock attempt but if the count has changed + * so that reversing fails, check if there are are no waiters, + * and early-out if not */ + do { + if (ldsem_cmpxchg(&count, count + adjust, sem)) + break; + if (count > 0) { + raw_spin_unlock_irq(&sem->wait_lock); + return sem; + } + } while (1); + + list_add_tail(&waiter.list, &sem->read_wait); + sem->wait_readers++; + + waiter.task = tsk; + get_task_struct(tsk); + + /* if there are no active locks, wake the new lock owner(s) */ + if ((count & LDSEM_ACTIVE_MASK) == 0) + __ldsem_wake(sem); + + raw_spin_unlock_irq(&sem->wait_lock); + + /* wait to be given the lock */ + for (;;) { + set_task_state(tsk, TASK_UNINTERRUPTIBLE); + + if (!waiter.task) + break; + if (!timeout) + break; + timeout = schedule_timeout(timeout); + } + + __set_task_state(tsk, TASK_RUNNING); + + if (!timeout) { + /* lock timed out but check if this task was just + * granted lock ownership - if so, pretend there + * was no timeout; otherwise, cleanup lock wait */ + raw_spin_lock_irq(&sem->wait_lock); + if (waiter.task) { + ldsem_atomic_update(-LDSEM_WAIT_BIAS, sem); + list_del(&waiter.list); + raw_spin_unlock_irq(&sem->wait_lock); + put_task_struct(waiter.task); + return NULL; + } + raw_spin_unlock_irq(&sem->wait_lock); + } + + return sem; +} + +/* + * wait for the write lock to be granted + */ +static struct ld_semaphore __sched * +down_write_failed(struct ld_semaphore *sem, long count, long timeout) +{ + struct ldsem_waiter waiter; + struct task_struct *tsk = current; + long adjust = -LDSEM_ACTIVE_BIAS; + int locked = 0; + + /* set up my own style of waitqueue */ + raw_spin_lock_irq(&sem->wait_lock); + + /* Try to reverse the lock attempt but if the count has changed + * so that reversing fails, check if the lock is now owned, + * and early-out if so */ + do { + if (ldsem_cmpxchg(&count, count + adjust, sem)) + break; + if ((count & LDSEM_ACTIVE_MASK) == LDSEM_ACTIVE_BIAS) { + raw_spin_unlock_irq(&sem->wait_lock); + return sem; + } + } while (1); + + list_add_tail(&waiter.list, &sem->write_wait); + + waiter.task = tsk; + + set_task_state(tsk, TASK_UNINTERRUPTIBLE); + for (;;) { + if (!timeout) + break; + raw_spin_unlock_irq(&sem->wait_lock); + timeout = schedule_timeout(timeout); + raw_spin_lock_irq(&sem->wait_lock); + set_task_state(tsk, TASK_UNINTERRUPTIBLE); + if ((locked = writer_trylock(sem))) + break; + } + + if (!locked) + ldsem_atomic_update(-LDSEM_WAIT_BIAS, sem); + list_del(&waiter.list); + raw_spin_unlock_irq(&sem->wait_lock); + + __set_task_state(tsk, TASK_RUNNING); + + /* lock wait may have timed out */ + if (!locked) + return NULL; + return sem; +} + + + +static inline int __ldsem_down_read_nested(struct ld_semaphore *sem, + int subclass, long timeout) +{ + long count; + + lockdep_acquire_read(sem, subclass, 0, _RET_IP_); + + count = ldsem_atomic_update(LDSEM_READ_BIAS, sem); + if (count <= 0) { + lock_stat(sem, contended); + if (!down_read_failed(sem, count, timeout)) { + lockdep_release(sem, 1, _RET_IP_); + return 0; + } + } + lock_stat(sem, acquired); + return 1; +} + +static inline int __ldsem_down_write_nested(struct ld_semaphore *sem, + int subclass, long timeout) +{ + long count; + + lockdep_acquire(sem, subclass, 0, _RET_IP_); + + count = ldsem_atomic_update(LDSEM_WRITE_BIAS, sem); + if ((count & LDSEM_ACTIVE_MASK) != LDSEM_ACTIVE_BIAS) { + lock_stat(sem, contended); + if (!down_write_failed(sem, count, timeout)) { + lockdep_release(sem, 1, _RET_IP_); + return 0; + } + } + lock_stat(sem, acquired); + return 1; +} + + +/* + * lock for reading -- returns 1 if successful, 0 if timed out + */ +int __sched ldsem_down_read(struct ld_semaphore *sem, long timeout) +{ + might_sleep(); + return __ldsem_down_read_nested(sem, 0, timeout); +} + +/* + * trylock for reading -- returns 1 if successful, 0 if contention + */ +int ldsem_down_read_trylock(struct ld_semaphore *sem) +{ + long count = sem->count; + + while (count >= 0) { + if (ldsem_cmpxchg(&count, count + LDSEM_READ_BIAS, sem)) { + lockdep_acquire_read(sem, 0, 1, _RET_IP_); + lock_stat(sem, acquired); + return 1; + } + } + return 0; +} + +/* + * lock for writing -- returns 1 if successful, 0 if timed out + */ +int __sched ldsem_down_write(struct ld_semaphore *sem, long timeout) +{ + might_sleep(); + return __ldsem_down_write_nested(sem, 0, timeout); +} + +/* + * trylock for writing -- returns 1 if successful, 0 if contention + */ +int ldsem_down_write_trylock(struct ld_semaphore *sem) +{ + long count = sem->count; + + while ((count & LDSEM_ACTIVE_MASK) == 0) { + if (ldsem_cmpxchg(&count, count + LDSEM_WRITE_BIAS, sem)) { + lockdep_acquire(sem, 0, 1, _RET_IP_); + lock_stat(sem, acquired); + return 1; + } + } + return 0; +} + +/* + * release a read lock + */ +void ldsem_up_read(struct ld_semaphore *sem) +{ + long count; + + lockdep_release(sem, 1, _RET_IP_); + + count = ldsem_atomic_update(-LDSEM_READ_BIAS, sem); + if (count < 0 && (count & LDSEM_ACTIVE_MASK) == 0) + ldsem_wake(sem); +} + +/* + * release a write lock + */ +void ldsem_up_write(struct ld_semaphore *sem) +{ + long count; + + lockdep_release(sem, 1, _RET_IP_); + + count = ldsem_atomic_update(-LDSEM_WRITE_BIAS, sem); + if (count < 0) + ldsem_wake(sem); +} + + +#ifdef CONFIG_DEBUG_LOCK_ALLOC + +int ldsem_down_read_nested(struct ld_semaphore *sem, int subclass, long timeout) +{ + might_sleep(); + return __ldsem_down_read_nested(sem, subclass, timeout); +} + +int ldsem_down_write_nested(struct ld_semaphore *sem, int subclass, + long timeout) +{ + might_sleep(); + return __ldsem_down_write_nested(sem, subclass, timeout); +} + +#endif diff --git a/include/linux/tty_ldisc.h b/include/linux/tty_ldisc.h index 58390c73df8..7b24bbd85ea 100644 --- a/include/linux/tty_ldisc.h +++ b/include/linux/tty_ldisc.h @@ -110,6 +110,52 @@ #include #include + +/* + * the semaphore definition + */ +struct ld_semaphore { + long count; + raw_spinlock_t wait_lock; + unsigned int wait_readers; + struct list_head read_wait; + struct list_head write_wait; +#ifdef CONFIG_DEBUG_LOCK_ALLOC + struct lockdep_map dep_map; +#endif +}; + +extern void __init_ldsem(struct ld_semaphore *sem, const char *name, + struct lock_class_key *key); + +#define init_ldsem(sem) \ +do { \ + static struct lock_class_key __key; \ + \ + __init_ldsem((sem), #sem, &__key); \ +} while (0) + + +extern int ldsem_down_read(struct ld_semaphore *sem, long timeout); +extern int ldsem_down_read_trylock(struct ld_semaphore *sem); +extern int ldsem_down_write(struct ld_semaphore *sem, long timeout); +extern int ldsem_down_write_trylock(struct ld_semaphore *sem); +extern void ldsem_up_read(struct ld_semaphore *sem); +extern void ldsem_up_write(struct ld_semaphore *sem); + +#ifdef CONFIG_DEBUG_LOCK_ALLOC +extern int ldsem_down_read_nested(struct ld_semaphore *sem, int subclass, + long timeout); +extern int ldsem_down_write_nested(struct ld_semaphore *sem, int subclass, + long timeout); +#else +# define ldsem_down_read_nested(sem, subclass, timeout) \ + ldsem_down_read(sem, timeout) +# define ldsem_down_write_nested(sem, subclass, timeout) \ + ldsem_down_write(sem, timeout) +#endif + + struct tty_ldisc_ops { int magic; char *name; -- cgit v1.2.3-70-g09d2 From c9e2e946fb0ba5d2398feb89558f98c5c28e23e3 Mon Sep 17 00:00:00 2001 From: Jingchang Lu Date: Fri, 7 Jun 2013 09:20:40 +0800 Subject: tty: serial: add Freescale lpuart driver support Add Freescale lpuart driver support. The lpuart device can be found on Vybrid VF610 and Layerscape LS-1 SoCs. Signed-off-by: Jingchang Lu Signed-off-by: Greg Kroah-Hartman --- .../devicetree/bindings/tty/serial/fsl-lpuart.txt | 14 + drivers/tty/serial/Kconfig | 14 + drivers/tty/serial/Makefile | 1 + drivers/tty/serial/fsl_lpuart.c | 874 +++++++++++++++++++++ include/uapi/linux/serial_core.h | 3 + 5 files changed, 906 insertions(+) create mode 100644 Documentation/devicetree/bindings/tty/serial/fsl-lpuart.txt create mode 100644 drivers/tty/serial/fsl_lpuart.c (limited to 'include') diff --git a/Documentation/devicetree/bindings/tty/serial/fsl-lpuart.txt b/Documentation/devicetree/bindings/tty/serial/fsl-lpuart.txt new file mode 100644 index 00000000000..6fd1dd1638d --- /dev/null +++ b/Documentation/devicetree/bindings/tty/serial/fsl-lpuart.txt @@ -0,0 +1,14 @@ +* Freescale low power universal asynchronous receiver/transmitter (lpuart) + +Required properties: +- compatible : Should be "fsl,-lpuart" +- reg : Address and length of the register set for the device +- interrupts : Should contain uart interrupt + +Example: + +uart0: serial@40027000 { + compatible = "fsl,vf610-lpuart"; + reg = <0x40027000 0x1000>; + interrupts = <0 61 0x00>; + }; diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index 95e66acca16..46dd1c72fed 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -1483,6 +1483,20 @@ config SERIAL_RP2_NR_UARTS If multiple cards are present, the default limit of 32 ports may need to be increased. +config SERIAL_FSL_LPUART + tristate "Freescale lpuart serial port support" + select SERIAL_CORE + help + Support for the on-chip lpuart on some Freescale SOCs. + +config SERIAL_FSL_LPUART_CONSOLE + bool "Console on Freescale lpuart serial port" + depends on SERIAL_FSL_LPUART=y + select SERIAL_CORE_CONSOLE + help + If you have enabled the lpuart serial port on the Freescale SoCs, + you can make it the console by answering Y to this option. + endmenu endif # TTY diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile index eedfec40e3d..cf650f0cd6e 100644 --- a/drivers/tty/serial/Makefile +++ b/drivers/tty/serial/Makefile @@ -85,3 +85,4 @@ obj-$(CONFIG_SERIAL_AR933X) += ar933x_uart.o obj-$(CONFIG_SERIAL_EFM32_UART) += efm32-uart.o obj-$(CONFIG_SERIAL_ARC) += arc_uart.o obj-$(CONFIG_SERIAL_RP2) += rp2.o +obj-$(CONFIG_SERIAL_FSL_LPUART) += fsl_lpuart.o diff --git a/drivers/tty/serial/fsl_lpuart.c b/drivers/tty/serial/fsl_lpuart.c new file mode 100644 index 00000000000..263cfaabe9e --- /dev/null +++ b/drivers/tty/serial/fsl_lpuart.c @@ -0,0 +1,874 @@ +/* + * Freescale lpuart serial port driver + * + * Copyright 2012-2013 Freescale Semiconductor, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + */ + +#if defined(CONFIG_SERIAL_FSL_LPUART_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ) +#define SUPPORT_SYSRQ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* All registers are 8-bit width */ +#define UARTBDH 0x00 +#define UARTBDL 0x01 +#define UARTCR1 0x02 +#define UARTCR2 0x03 +#define UARTSR1 0x04 +#define UARTCR3 0x06 +#define UARTDR 0x07 +#define UARTCR4 0x0a +#define UARTCR5 0x0b +#define UARTMODEM 0x0d +#define UARTPFIFO 0x10 +#define UARTCFIFO 0x11 +#define UARTSFIFO 0x12 +#define UARTTWFIFO 0x13 +#define UARTTCFIFO 0x14 +#define UARTRWFIFO 0x15 + +#define UARTBDH_LBKDIE 0x80 +#define UARTBDH_RXEDGIE 0x40 +#define UARTBDH_SBR_MASK 0x1f + +#define UARTCR1_LOOPS 0x80 +#define UARTCR1_RSRC 0x20 +#define UARTCR1_M 0x10 +#define UARTCR1_WAKE 0x08 +#define UARTCR1_ILT 0x04 +#define UARTCR1_PE 0x02 +#define UARTCR1_PT 0x01 + +#define UARTCR2_TIE 0x80 +#define UARTCR2_TCIE 0x40 +#define UARTCR2_RIE 0x20 +#define UARTCR2_ILIE 0x10 +#define UARTCR2_TE 0x08 +#define UARTCR2_RE 0x04 +#define UARTCR2_RWU 0x02 +#define UARTCR2_SBK 0x01 + +#define UARTSR1_TDRE 0x80 +#define UARTSR1_TC 0x40 +#define UARTSR1_RDRF 0x20 +#define UARTSR1_IDLE 0x10 +#define UARTSR1_OR 0x08 +#define UARTSR1_NF 0x04 +#define UARTSR1_FE 0x02 +#define UARTSR1_PE 0x01 + +#define UARTCR3_R8 0x80 +#define UARTCR3_T8 0x40 +#define UARTCR3_TXDIR 0x20 +#define UARTCR3_TXINV 0x10 +#define UARTCR3_ORIE 0x08 +#define UARTCR3_NEIE 0x04 +#define UARTCR3_FEIE 0x02 +#define UARTCR3_PEIE 0x01 + +#define UARTCR4_MAEN1 0x80 +#define UARTCR4_MAEN2 0x40 +#define UARTCR4_M10 0x20 +#define UARTCR4_BRFA_MASK 0x1f +#define UARTCR4_BRFA_OFF 0 + +#define UARTCR5_TDMAS 0x80 +#define UARTCR5_RDMAS 0x20 + +#define UARTMODEM_RXRTSE 0x08 +#define UARTMODEM_TXRTSPOL 0x04 +#define UARTMODEM_TXRTSE 0x02 +#define UARTMODEM_TXCTSE 0x01 + +#define UARTPFIFO_TXFE 0x80 +#define UARTPFIFO_FIFOSIZE_MASK 0x7 +#define UARTPFIFO_TXSIZE_OFF 4 +#define UARTPFIFO_RXFE 0x08 +#define UARTPFIFO_RXSIZE_OFF 0 + +#define UARTCFIFO_TXFLUSH 0x80 +#define UARTCFIFO_RXFLUSH 0x40 +#define UARTCFIFO_RXOFE 0x04 +#define UARTCFIFO_TXOFE 0x02 +#define UARTCFIFO_RXUFE 0x01 + +#define UARTSFIFO_TXEMPT 0x80 +#define UARTSFIFO_RXEMPT 0x40 +#define UARTSFIFO_RXOF 0x04 +#define UARTSFIFO_TXOF 0x02 +#define UARTSFIFO_RXUF 0x01 + +#define DRIVER_NAME "fsl-lpuart" +#define DEV_NAME "ttyLP" +#define UART_NR 6 + +struct lpuart_port { + struct uart_port port; + struct clk *clk; + unsigned int txfifo_size; + unsigned int rxfifo_size; +}; + +static struct of_device_id lpuart_dt_ids[] = { + { + .compatible = "fsl,vf610-lpuart", + }, + { /* sentinel */ } +}; +MODULE_DEVICE_TABLE(of, lpuart_dt_ids); + +static void lpuart_stop_tx(struct uart_port *port) +{ + unsigned char temp; + + temp = readb(port->membase + UARTCR2); + temp &= ~(UARTCR2_TIE | UARTCR2_TCIE); + writeb(temp, port->membase + UARTCR2); +} + +static void lpuart_stop_rx(struct uart_port *port) +{ + unsigned char temp; + + temp = readb(port->membase + UARTCR2); + writeb(temp & ~UARTCR2_RE, port->membase + UARTCR2); +} + +static void lpuart_enable_ms(struct uart_port *port) +{ +} + +static inline void lpuart_transmit_buffer(struct lpuart_port *sport) +{ + struct circ_buf *xmit = &sport->port.state->xmit; + + while (!uart_circ_empty(xmit) && + (readb(sport->port.membase + UARTTCFIFO) < sport->txfifo_size)) { + writeb(xmit->buf[xmit->tail], sport->port.membase + UARTDR); + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + sport->port.icount.tx++; + } + + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + uart_write_wakeup(&sport->port); + + if (uart_circ_empty(xmit)) + lpuart_stop_tx(&sport->port); +} + +static void lpuart_start_tx(struct uart_port *port) +{ + struct lpuart_port *sport = container_of(port, struct lpuart_port, port); + unsigned char temp; + + temp = readb(port->membase + UARTCR2); + writeb(temp | UARTCR2_TIE, port->membase + UARTCR2); + + if (readb(port->membase + UARTSR1) & UARTSR1_TDRE) + lpuart_transmit_buffer(sport); +} + +static irqreturn_t lpuart_txint(int irq, void *dev_id) +{ + struct lpuart_port *sport = dev_id; + struct circ_buf *xmit = &sport->port.state->xmit; + unsigned long flags; + + spin_lock_irqsave(&sport->port.lock, flags); + if (sport->port.x_char) { + writeb(sport->port.x_char, sport->port.membase + UARTDR); + goto out; + } + + if (uart_circ_empty(xmit) || uart_tx_stopped(&sport->port)) { + lpuart_stop_tx(&sport->port); + goto out; + } + + lpuart_transmit_buffer(sport); + + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + uart_write_wakeup(&sport->port); + +out: + spin_unlock_irqrestore(&sport->port.lock, flags); + return IRQ_HANDLED; +} + +static irqreturn_t lpuart_rxint(int irq, void *dev_id) +{ + struct lpuart_port *sport = dev_id; + unsigned int flg, ignored = 0; + struct tty_port *port = &sport->port.state->port; + unsigned long flags; + unsigned char rx, sr; + + spin_lock_irqsave(&sport->port.lock, flags); + + while (!(readb(sport->port.membase + UARTSFIFO) & UARTSFIFO_RXEMPT)) { + flg = TTY_NORMAL; + sport->port.icount.rx++; + /* + * to clear the FE, OR, NF, FE, PE flags, + * read SR1 then read DR + */ + sr = readb(sport->port.membase + UARTSR1); + rx = readb(sport->port.membase + UARTDR); + + if (uart_handle_sysrq_char(&sport->port, (unsigned char)rx)) + continue; + + if (sr & (UARTSR1_PE | UARTSR1_OR | UARTSR1_FE)) { + if (sr & UARTSR1_PE) + sport->port.icount.parity++; + else if (sr & UARTSR1_FE) + sport->port.icount.frame++; + + if (sr & UARTSR1_OR) + sport->port.icount.overrun++; + + if (sr & sport->port.ignore_status_mask) { + if (++ignored > 100) + goto out; + continue; + } + + sr &= sport->port.read_status_mask; + + if (sr & UARTSR1_PE) + flg = TTY_PARITY; + else if (sr & UARTSR1_FE) + flg = TTY_FRAME; + + if (sr & UARTSR1_OR) + flg = TTY_OVERRUN; + +#ifdef SUPPORT_SYSRQ + sport->port.sysrq = 0; +#endif + } + + tty_insert_flip_char(port, rx, flg); + } + +out: + spin_unlock_irqrestore(&sport->port.lock, flags); + + tty_flip_buffer_push(port); + return IRQ_HANDLED; +} + +static irqreturn_t lpuart_int(int irq, void *dev_id) +{ + struct lpuart_port *sport = dev_id; + unsigned char sts; + + sts = readb(sport->port.membase + UARTSR1); + + if (sts & UARTSR1_RDRF) + lpuart_rxint(irq, dev_id); + + if (sts & UARTSR1_TDRE && + !(readb(sport->port.membase + UARTCR5) & UARTCR5_TDMAS)) + lpuart_txint(irq, dev_id); + + return IRQ_HANDLED; +} + +/* return TIOCSER_TEMT when transmitter is not busy */ +static unsigned int lpuart_tx_empty(struct uart_port *port) +{ + return (readb(port->membase + UARTSR1) & UARTSR1_TC) ? + TIOCSER_TEMT : 0; +} + +static unsigned int lpuart_get_mctrl(struct uart_port *port) +{ + unsigned int temp = 0; + unsigned char reg; + + reg = readb(port->membase + UARTMODEM); + if (reg & UARTMODEM_TXCTSE) + temp |= TIOCM_CTS; + + if (reg & UARTMODEM_RXRTSE) + temp |= TIOCM_RTS; + + return temp; +} + +static void lpuart_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ + unsigned char temp; + + temp = readb(port->membase + UARTMODEM) & + ~(UARTMODEM_RXRTSE | UARTMODEM_TXCTSE); + + if (mctrl & TIOCM_RTS) + temp |= UARTMODEM_RXRTSE; + + if (mctrl & TIOCM_CTS) + temp |= UARTMODEM_TXCTSE; + + writeb(temp, port->membase + UARTMODEM); +} + +static void lpuart_break_ctl(struct uart_port *port, int break_state) +{ + unsigned char temp; + + temp = readb(port->membase + UARTCR2) & ~UARTCR2_SBK; + + if (break_state != 0) + temp |= UARTCR2_SBK; + + writeb(temp, port->membase + UARTCR2); +} + +static void lpuart_setup_watermark(struct lpuart_port *sport) +{ + unsigned char val, cr2; + + cr2 = readb(sport->port.membase + UARTCR2); + cr2 &= ~(UARTCR2_TIE | UARTCR2_TCIE | UARTCR2_TE | + UARTCR2_RIE | UARTCR2_RE); + writeb(cr2, sport->port.membase + UARTCR2); + + /* determine FIFO size and enable FIFO mode */ + val = readb(sport->port.membase + UARTPFIFO); + + sport->txfifo_size = 0x1 << (((val >> UARTPFIFO_TXSIZE_OFF) & + UARTPFIFO_FIFOSIZE_MASK) + 1); + + sport->rxfifo_size = 0x1 << (((val >> UARTPFIFO_RXSIZE_OFF) & + UARTPFIFO_FIFOSIZE_MASK) + 1); + + writeb(val | UARTPFIFO_TXFE | UARTPFIFO_RXFE, + sport->port.membase + UARTPFIFO); + + /* flush Tx and Rx FIFO */ + writeb(UARTCFIFO_TXFLUSH | UARTCFIFO_RXFLUSH, + sport->port.membase + UARTCFIFO); + + writeb(2, sport->port.membase + UARTTWFIFO); + writeb(1, sport->port.membase + UARTRWFIFO); +} + +static int lpuart_startup(struct uart_port *port) +{ + struct lpuart_port *sport = container_of(port, struct lpuart_port, port); + int ret; + unsigned long flags; + unsigned char temp; + + ret = devm_request_irq(port->dev, port->irq, lpuart_int, 0, + DRIVER_NAME, sport); + if (ret) + return ret; + + spin_lock_irqsave(&sport->port.lock, flags); + + lpuart_setup_watermark(sport); + + temp = readb(sport->port.membase + UARTCR2); + temp |= (UARTCR2_RIE | UARTCR2_TIE | UARTCR2_RE | UARTCR2_TE); + writeb(temp, sport->port.membase + UARTCR2); + + spin_unlock_irqrestore(&sport->port.lock, flags); + return 0; +} + +static void lpuart_shutdown(struct uart_port *port) +{ + struct lpuart_port *sport = container_of(port, struct lpuart_port, port); + unsigned char temp; + unsigned long flags; + + spin_lock_irqsave(&port->lock, flags); + + /* disable Rx/Tx and interrupts */ + temp = readb(port->membase + UARTCR2); + temp &= ~(UARTCR2_TE | UARTCR2_RE | + UARTCR2_TIE | UARTCR2_TCIE | UARTCR2_RIE); + writeb(temp, port->membase + UARTCR2); + + spin_unlock_irqrestore(&port->lock, flags); + + devm_free_irq(port->dev, port->irq, sport); +} + +static void +lpuart_set_termios(struct uart_port *port, struct ktermios *termios, + struct ktermios *old) +{ + struct lpuart_port *sport = container_of(port, struct lpuart_port, port); + unsigned long flags; + unsigned char cr1, old_cr1, old_cr2, cr4, bdh, modem; + unsigned int baud; + unsigned int old_csize = old ? old->c_cflag & CSIZE : CS8; + unsigned int sbr, brfa; + + cr1 = old_cr1 = readb(sport->port.membase + UARTCR1); + old_cr2 = readb(sport->port.membase + UARTCR2); + cr4 = readb(sport->port.membase + UARTCR4); + bdh = readb(sport->port.membase + UARTBDH); + modem = readb(sport->port.membase + UARTMODEM); + /* + * only support CS8 and CS7, and for CS7 must enable PE. + * supported mode: + * - (7,e/o,1) + * - (8,n,1) + * - (8,m/s,1) + * - (8,e/o,1) + */ + while ((termios->c_cflag & CSIZE) != CS8 && + (termios->c_cflag & CSIZE) != CS7) { + termios->c_cflag &= ~CSIZE; + termios->c_cflag |= old_csize; + old_csize = CS8; + } + + if ((termios->c_cflag & CSIZE) == CS8 || + (termios->c_cflag & CSIZE) == CS7) + cr1 = old_cr1 & ~UARTCR1_M; + + if (termios->c_cflag & CMSPAR) { + if ((termios->c_cflag & CSIZE) != CS8) { + termios->c_cflag &= ~CSIZE; + termios->c_cflag |= CS8; + } + cr1 |= UARTCR1_M; + } + + if (termios->c_cflag & CRTSCTS) { + modem |= (UARTMODEM_RXRTSE | UARTMODEM_TXCTSE); + } else { + termios->c_cflag &= ~CRTSCTS; + modem &= ~(UARTMODEM_RXRTSE | UARTMODEM_TXCTSE); + } + + if (termios->c_cflag & CSTOPB) + termios->c_cflag &= ~CSTOPB; + + /* parity must be enabled when CS7 to match 8-bits format */ + if ((termios->c_cflag & CSIZE) == CS7) + termios->c_cflag |= PARENB; + + if ((termios->c_cflag & PARENB)) { + if (termios->c_cflag & CMSPAR) { + cr1 &= ~UARTCR1_PE; + cr1 |= UARTCR1_M; + } else { + cr1 |= UARTCR1_PE; + if ((termios->c_cflag & CSIZE) == CS8) + cr1 |= UARTCR1_M; + if (termios->c_cflag & PARODD) + cr1 |= UARTCR1_PT; + else + cr1 &= ~UARTCR1_PT; + } + } + + /* ask the core to calculate the divisor */ + baud = uart_get_baud_rate(port, termios, old, 50, port->uartclk / 16); + + spin_lock_irqsave(&sport->port.lock, flags); + + sport->port.read_status_mask = 0; + if (termios->c_iflag & INPCK) + sport->port.read_status_mask |= (UARTSR1_FE | UARTSR1_PE); + if (termios->c_iflag & (BRKINT | PARMRK)) + sport->port.read_status_mask |= UARTSR1_FE; + + /* characters to ignore */ + sport->port.ignore_status_mask = 0; + if (termios->c_iflag & IGNPAR) + sport->port.ignore_status_mask |= UARTSR1_PE; + if (termios->c_iflag & IGNBRK) { + sport->port.ignore_status_mask |= UARTSR1_FE; + /* + * if we're ignoring parity and break indicators, + * ignore overruns too (for real raw support). + */ + if (termios->c_iflag & IGNPAR) + sport->port.ignore_status_mask |= UARTSR1_OR; + } + + /* update the per-port timeout */ + uart_update_timeout(port, termios->c_cflag, baud); + + /* wait transmit engin complete */ + while (!(readb(sport->port.membase + UARTSR1) & UARTSR1_TC)) + barrier(); + + /* disable transmit and receive */ + writeb(old_cr2 & ~(UARTCR2_TE | UARTCR2_RE), + sport->port.membase + UARTCR2); + + sbr = sport->port.uartclk / (16 * baud); + brfa = ((sport->port.uartclk - (16 * sbr * baud)) * 2) / baud; + bdh &= ~UARTBDH_SBR_MASK; + bdh |= (sbr >> 8) & 0x1F; + cr4 &= ~UARTCR4_BRFA_MASK; + brfa &= UARTCR4_BRFA_MASK; + writeb(cr4 | brfa, sport->port.membase + UARTCR4); + writeb(bdh, sport->port.membase + UARTBDH); + writeb(sbr & 0xFF, sport->port.membase + UARTBDL); + writeb(cr1, sport->port.membase + UARTCR1); + writeb(modem, sport->port.membase + UARTMODEM); + + /* restore control register */ + writeb(old_cr2, sport->port.membase + UARTCR2); + + spin_unlock_irqrestore(&sport->port.lock, flags); +} + +static const char *lpuart_type(struct uart_port *port) +{ + return "FSL_LPUART"; +} + +static void lpuart_release_port(struct uart_port *port) +{ + /* nothing to do */ +} + +static int lpuart_request_port(struct uart_port *port) +{ + return 0; +} + +/* configure/autoconfigure the port */ +static void lpuart_config_port(struct uart_port *port, int flags) +{ + if (flags & UART_CONFIG_TYPE) + port->type = PORT_LPUART; +} + +static int lpuart_verify_port(struct uart_port *port, struct serial_struct *ser) +{ + int ret = 0; + + if (ser->type != PORT_UNKNOWN && ser->type != PORT_LPUART) + ret = -EINVAL; + if (port->irq != ser->irq) + ret = -EINVAL; + if (ser->io_type != UPIO_MEM) + ret = -EINVAL; + if (port->uartclk / 16 != ser->baud_base) + ret = -EINVAL; + if (port->iobase != ser->port) + ret = -EINVAL; + if (ser->hub6 != 0) + ret = -EINVAL; + return ret; +} + +static struct uart_ops lpuart_pops = { + .tx_empty = lpuart_tx_empty, + .set_mctrl = lpuart_set_mctrl, + .get_mctrl = lpuart_get_mctrl, + .stop_tx = lpuart_stop_tx, + .start_tx = lpuart_start_tx, + .stop_rx = lpuart_stop_rx, + .enable_ms = lpuart_enable_ms, + .break_ctl = lpuart_break_ctl, + .startup = lpuart_startup, + .shutdown = lpuart_shutdown, + .set_termios = lpuart_set_termios, + .type = lpuart_type, + .request_port = lpuart_request_port, + .release_port = lpuart_release_port, + .config_port = lpuart_config_port, + .verify_port = lpuart_verify_port, +}; + +static struct lpuart_port *lpuart_ports[UART_NR]; + +#ifdef CONFIG_SERIAL_FSL_LPUART_CONSOLE +static void lpuart_console_putchar(struct uart_port *port, int ch) +{ + while (!(readb(port->membase + UARTSR1) & UARTSR1_TDRE)) + barrier(); + + writeb(ch, port->membase + UARTDR); +} + +static void +lpuart_console_write(struct console *co, const char *s, unsigned int count) +{ + struct lpuart_port *sport = lpuart_ports[co->index]; + unsigned char old_cr2, cr2; + + /* first save CR2 and then disable interrupts */ + cr2 = old_cr2 = readb(sport->port.membase + UARTCR2); + cr2 |= (UARTCR2_TE | UARTCR2_RE); + cr2 &= ~(UARTCR2_TIE | UARTCR2_TCIE | UARTCR2_RIE); + writeb(cr2, sport->port.membase + UARTCR2); + + uart_console_write(&sport->port, s, count, lpuart_console_putchar); + + /* wait for transmitter finish complete and restore CR2 */ + while (!(readb(sport->port.membase + UARTSR1) & UARTSR1_TC)) + barrier(); + + writeb(old_cr2, sport->port.membase + UARTCR2); +} + +/* + * if the port was already initialised (eg, by a boot loader), + * try to determine the current setup. + */ +static void __init +lpuart_console_get_options(struct lpuart_port *sport, int *baud, + int *parity, int *bits) +{ + unsigned char cr, bdh, bdl, brfa; + unsigned int sbr, uartclk, baud_raw; + + cr = readb(sport->port.membase + UARTCR2); + cr &= UARTCR2_TE | UARTCR2_RE; + if (!cr) + return; + + /* ok, the port was enabled */ + + cr = readb(sport->port.membase + UARTCR1); + + *parity = 'n'; + if (cr & UARTCR1_PE) { + if (cr & UARTCR1_PT) + *parity = 'o'; + else + *parity = 'e'; + } + + if (cr & UARTCR1_M) + *bits = 9; + else + *bits = 8; + + bdh = readb(sport->port.membase + UARTBDH); + bdh &= UARTBDH_SBR_MASK; + bdl = readb(sport->port.membase + UARTBDL); + sbr = bdh; + sbr <<= 8; + sbr |= bdl; + brfa = readb(sport->port.membase + UARTCR4); + brfa &= UARTCR4_BRFA_MASK; + + uartclk = clk_get_rate(sport->clk); + /* + * baud = mod_clk/(16*(sbr[13]+(brfa)/32) + */ + baud_raw = uartclk / (16 * (sbr + brfa / 32)); + + if (*baud != baud_raw) + printk(KERN_INFO "Serial: Console lpuart rounded baud rate" + "from %d to %d\n", baud_raw, *baud); +} + +static int __init lpuart_console_setup(struct console *co, char *options) +{ + struct lpuart_port *sport; + int baud = 115200; + int bits = 8; + int parity = 'n'; + int flow = 'n'; + + /* + * check whether an invalid uart number has been specified, and + * if so, search for the first available port that does have + * console support. + */ + if (co->index == -1 || co->index >= ARRAY_SIZE(lpuart_ports)) + co->index = 0; + + sport = lpuart_ports[co->index]; + if (sport == NULL) + return -ENODEV; + + if (options) + uart_parse_options(options, &baud, &parity, &bits, &flow); + else + lpuart_console_get_options(sport, &baud, &parity, &bits); + + lpuart_setup_watermark(sport); + + return uart_set_options(&sport->port, co, baud, parity, bits, flow); +} + +static struct uart_driver lpuart_reg; +static struct console lpuart_console = { + .name = DEV_NAME, + .write = lpuart_console_write, + .device = uart_console_device, + .setup = lpuart_console_setup, + .flags = CON_PRINTBUFFER, + .index = -1, + .data = &lpuart_reg, +}; + +#define LPUART_CONSOLE (&lpuart_console) +#else +#define LPUART_CONSOLE NULL +#endif + +static struct uart_driver lpuart_reg = { + .owner = THIS_MODULE, + .driver_name = DRIVER_NAME, + .dev_name = DEV_NAME, + .nr = ARRAY_SIZE(lpuart_ports), + .cons = LPUART_CONSOLE, +}; + +static int lpuart_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct lpuart_port *sport; + struct resource *res; + int ret; + + sport = devm_kzalloc(&pdev->dev, sizeof(*sport), GFP_KERNEL); + if (!sport) + return -ENOMEM; + + pdev->dev.coherent_dma_mask = 0; + + ret = of_alias_get_id(np, "serial"); + if (ret < 0) { + dev_err(&pdev->dev, "failed to get alias id, errno %d\n", ret); + return ret; + } + sport->port.line = ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + sport->port.mapbase = res->start; + sport->port.membase = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(sport->port.membase)) + return PTR_ERR(sport->port.membase); + + sport->port.dev = &pdev->dev; + sport->port.type = PORT_LPUART; + sport->port.iotype = UPIO_MEM; + sport->port.irq = platform_get_irq(pdev, 0); + sport->port.ops = &lpuart_pops; + sport->port.flags = UPF_BOOT_AUTOCONF; + + sport->clk = devm_clk_get(&pdev->dev, "ipg"); + if (IS_ERR(sport->clk)) { + ret = PTR_ERR(sport->clk); + dev_err(&pdev->dev, "failed to get uart clk: %d\n", ret); + return ret; + } + + ret = clk_prepare_enable(sport->clk); + if (ret) { + dev_err(&pdev->dev, "failed to enable uart clk: %d\n", ret); + return ret; + } + + sport->port.uartclk = clk_get_rate(sport->clk); + + lpuart_ports[sport->port.line] = sport; + + platform_set_drvdata(pdev, &sport->port); + + ret = uart_add_one_port(&lpuart_reg, &sport->port); + if (ret) { + clk_disable_unprepare(sport->clk); + return ret; + } + + return 0; +} + +static int lpuart_remove(struct platform_device *pdev) +{ + struct lpuart_port *sport = platform_get_drvdata(pdev); + + uart_remove_one_port(&lpuart_reg, &sport->port); + + clk_disable_unprepare(sport->clk); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int lpuart_suspend(struct device *dev) +{ + struct lpuart_port *sport = dev_get_drvdata(dev); + + uart_suspend_port(&lpuart_reg, &sport->port); + + return 0; +} + +static int lpuart_resume(struct device *dev) +{ + struct lpuart_port *sport = dev_get_drvdata(dev); + + uart_resume_port(&lpuart_reg, &sport->port); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(lpuart_pm_ops, lpuart_suspend, lpuart_resume); + +static struct platform_driver lpuart_driver = { + .probe = lpuart_probe, + .remove = lpuart_remove, + .driver = { + .name = "fsl-lpuart", + .owner = THIS_MODULE, + .of_match_table = lpuart_dt_ids, + .pm = &lpuart_pm_ops, + }, +}; + +static int __init lpuart_serial_init(void) +{ + int ret; + + pr_info("serial: Freescale lpuart driver\n"); + + ret = uart_register_driver(&lpuart_reg); + if (ret) + return ret; + + ret = platform_driver_register(&lpuart_driver); + if (ret) + uart_unregister_driver(&lpuart_reg); + + return 0; +} + +static void __exit lpuart_serial_exit(void) +{ + platform_driver_unregister(&lpuart_driver); + uart_unregister_driver(&lpuart_reg); +} + +module_init(lpuart_serial_init); +module_exit(lpuart_serial_exit); + +MODULE_DESCRIPTION("Freescale lpuart serial port driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/uapi/linux/serial_core.h b/include/uapi/linux/serial_core.h index 74c2bf7211f..c8eaeb5465e 100644 --- a/include/uapi/linux/serial_core.h +++ b/include/uapi/linux/serial_core.h @@ -226,4 +226,7 @@ /* Rocketport EXPRESS/INFINITY */ #define PORT_RP2 102 +/* Freescale lpuart */ +#define PORT_LPUART 103 + #endif /* _UAPILINUX_SERIAL_CORE_H */ -- cgit v1.2.3-70-g09d2 From f6c8dbe6e50c6e5121d7b6644718207daa008221 Mon Sep 17 00:00:00 2001 From: Peter Hurley Date: Sat, 15 Jun 2013 07:28:28 -0400 Subject: n_tty: Encapsulate minimum_to_wake within N_TTY minimum_to_wake is unique to N_TTY processing, and belongs in per-ldisc data. Add the ldisc method, ldisc_ops::fasync(), to notify line disciplines when signal-driven I/O is enabled or disabled. When enabled for N_TTY (by fcntl(F_SETFL, O_ASYNC)), blocking reader/polls will be woken for any readable input. When disabled, blocking reader/polls are not woken until the read buffer is full. Canonical mode (L_ICANON(tty), n_tty_data::icanon) is not affected by the minimum_to_wake setting. Signed-off-by: Peter Hurley Signed-off-by: Greg Kroah-Hartman --- drivers/tty/n_tty.c | 39 +++++++++++++++++++++++++++------------ drivers/tty/tty_io.c | 17 +++++++++-------- include/linux/tty.h | 1 - include/linux/tty_ldisc.h | 6 ++++++ 4 files changed, 42 insertions(+), 21 deletions(-) (limited to 'include') diff --git a/drivers/tty/n_tty.c b/drivers/tty/n_tty.c index cdcdb0ea061..f1806de69b1 100644 --- a/drivers/tty/n_tty.c +++ b/drivers/tty/n_tty.c @@ -89,6 +89,7 @@ struct n_tty_data { int read_head; int read_tail; int read_cnt; + int minimum_to_wake; unsigned char *echo_buf; unsigned int echo_pos; @@ -1455,7 +1456,7 @@ static void n_tty_receive_buf(struct tty_struct *tty, const unsigned char *cp, n_tty_set_room(tty); - if ((!ldata->icanon && (ldata->read_cnt >= tty->minimum_to_wake)) || + if ((!ldata->icanon && (ldata->read_cnt >= ldata->minimum_to_wake)) || L_EXTPROC(tty)) { kill_fasync(&tty->fasync, SIGIO, POLL_IN); if (waitqueue_active(&tty->read_wait)) @@ -1636,7 +1637,7 @@ static int n_tty_open(struct tty_struct *tty) tty->disc_data = ldata; reset_buffer_flags(tty->disc_data); ldata->column = 0; - tty->minimum_to_wake = 1; + ldata->minimum_to_wake = 1; tty->closing = 0; /* indicate buffer work may resume */ clear_bit(TTY_LDISC_HALTED, &tty->flags); @@ -1804,17 +1805,17 @@ do_it_again: minimum = MIN_CHAR(tty); if (minimum) { if (time) - tty->minimum_to_wake = 1; + ldata->minimum_to_wake = 1; else if (!waitqueue_active(&tty->read_wait) || - (tty->minimum_to_wake > minimum)) - tty->minimum_to_wake = minimum; + (ldata->minimum_to_wake > minimum)) + ldata->minimum_to_wake = minimum; } else { timeout = 0; if (time) { timeout = time; time = 0; } - tty->minimum_to_wake = minimum = 1; + ldata->minimum_to_wake = minimum = 1; } } @@ -1854,9 +1855,9 @@ do_it_again: TASK_RUNNING. */ set_current_state(TASK_INTERRUPTIBLE); - if (((minimum - (b - buf)) < tty->minimum_to_wake) && + if (((minimum - (b - buf)) < ldata->minimum_to_wake) && ((minimum - (b - buf)) >= 1)) - tty->minimum_to_wake = (minimum - (b - buf)); + ldata->minimum_to_wake = (minimum - (b - buf)); if (!input_available_p(tty, 0)) { if (test_bit(TTY_OTHER_CLOSED, &tty->flags)) { @@ -1973,7 +1974,7 @@ do_it_again: remove_wait_queue(&tty->read_wait, &wait); if (!waitqueue_active(&tty->read_wait)) - tty->minimum_to_wake = minimum; + ldata->minimum_to_wake = minimum; __set_current_state(TASK_RUNNING); size = b - buf; @@ -2105,6 +2106,7 @@ break_out: static unsigned int n_tty_poll(struct tty_struct *tty, struct file *file, poll_table *wait) { + struct n_tty_data *ldata = tty->disc_data; unsigned int mask = 0; poll_wait(file, &tty->read_wait, wait); @@ -2119,9 +2121,9 @@ static unsigned int n_tty_poll(struct tty_struct *tty, struct file *file, mask |= POLLHUP; if (!(mask & (POLLHUP | POLLIN | POLLRDNORM))) { if (MIN_CHAR(tty) && !TIME_CHAR(tty)) - tty->minimum_to_wake = MIN_CHAR(tty); + ldata->minimum_to_wake = MIN_CHAR(tty); else - tty->minimum_to_wake = 1; + ldata->minimum_to_wake = 1; } if (tty->ops->write && !tty_is_writelocked(tty) && tty_chars_in_buffer(tty) < WAKEUP_CHARS && @@ -2169,6 +2171,18 @@ static int n_tty_ioctl(struct tty_struct *tty, struct file *file, } } +static void n_tty_fasync(struct tty_struct *tty, int on) +{ + struct n_tty_data *ldata = tty->disc_data; + + if (!waitqueue_active(&tty->read_wait)) { + if (on) + ldata->minimum_to_wake = 1; + else if (!tty->fasync) + ldata->minimum_to_wake = N_TTY_BUF_SIZE; + } +} + struct tty_ldisc_ops tty_ldisc_N_TTY = { .magic = TTY_LDISC_MAGIC, .name = "n_tty", @@ -2182,7 +2196,8 @@ struct tty_ldisc_ops tty_ldisc_N_TTY = { .set_termios = n_tty_set_termios, .poll = n_tty_poll, .receive_buf = n_tty_receive_buf, - .write_wakeup = n_tty_write_wakeup + .write_wakeup = n_tty_write_wakeup, + .fasync = n_tty_fasync, }; /** diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c index 6464029e486..bd88007fa6e 100644 --- a/drivers/tty/tty_io.c +++ b/drivers/tty/tty_io.c @@ -2138,6 +2138,7 @@ static unsigned int tty_poll(struct file *filp, poll_table *wait) static int __tty_fasync(int fd, struct file *filp, int on) { struct tty_struct *tty = file_tty(filp); + struct tty_ldisc *ldisc; unsigned long flags; int retval = 0; @@ -2148,11 +2149,17 @@ static int __tty_fasync(int fd, struct file *filp, int on) if (retval <= 0) goto out; + ldisc = tty_ldisc_ref(tty); + if (ldisc) { + if (ldisc->ops->fasync) + ldisc->ops->fasync(tty, on); + tty_ldisc_deref(ldisc); + } + if (on) { enum pid_type type; struct pid *pid; - if (!waitqueue_active(&tty->read_wait)) - tty->minimum_to_wake = 1; + spin_lock_irqsave(&tty->ctrl_lock, flags); if (tty->pgrp) { pid = tty->pgrp; @@ -2165,13 +2172,7 @@ static int __tty_fasync(int fd, struct file *filp, int on) spin_unlock_irqrestore(&tty->ctrl_lock, flags); retval = __f_setown(filp, pid, type, 0); put_pid(pid); - if (retval) - goto out; - } else { - if (!tty->fasync && !waitqueue_active(&tty->read_wait)) - tty->minimum_to_wake = N_TTY_BUF_SIZE; } - retval = 0; out: return retval; } diff --git a/include/linux/tty.h b/include/linux/tty.h index 82ab69bc9b7..01ac30efd6a 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -272,7 +272,6 @@ struct tty_struct { #define N_TTY_BUF_SIZE 4096 unsigned char closing:1; - unsigned short minimum_to_wake; unsigned char *write_buf; int write_cnt; /* If the tty has a pending do_SAK, queue it here - akpm */ diff --git a/include/linux/tty_ldisc.h b/include/linux/tty_ldisc.h index 7b24bbd85ea..a1b04899982 100644 --- a/include/linux/tty_ldisc.h +++ b/include/linux/tty_ldisc.h @@ -100,6 +100,11 @@ * seek to perform this action quickly but should wait until * any pending driver I/O is completed. * + * void (*fasync)(struct tty_struct *, int on) + * + * Notify line discipline when signal-driven I/O is enabled or + * disabled. + * * void (*dcd_change)(struct tty_struct *tty, unsigned int status) * * Tells the discipline that the DCD pin has changed its status. @@ -189,6 +194,7 @@ struct tty_ldisc_ops { char *fp, int count); void (*write_wakeup)(struct tty_struct *); void (*dcd_change)(struct tty_struct *, unsigned int); + void (*fasync)(struct tty_struct *tty, int on); struct module *owner; -- cgit v1.2.3-70-g09d2