![Greg Kroah-Hartman](/assets/img/avatar_default.png)
Now that the SPDX tag is in all tty files, that identifies the license in a specific and legally-defined manner. So the extra GPL text wording can be removed as it is no longer needed at all. This is done on a quest to remove the 700+ different ways that files in the kernel describe the GPL license text. And there's unneeded stuff like the address (sometimes incorrect) for the FSF which is never needed. No copyright headers or other non-license-description text was removed. Cc: Jiri Slaby <jslaby@suse.com> Cc: James Hogan <jhogan@kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
666 lines
16 KiB
C
666 lines
16 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* dashtty.c - tty driver for Dash channels interface.
|
|
*
|
|
* Copyright (C) 2007,2008,2012 Imagination Technologies
|
|
*/
|
|
|
|
#include <linux/atomic.h>
|
|
#include <linux/completion.h>
|
|
#include <linux/console.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/export.h>
|
|
#include <linux/init.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/moduleparam.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/serial.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/string.h>
|
|
#include <linux/timer.h>
|
|
#include <linux/tty.h>
|
|
#include <linux/tty_driver.h>
|
|
#include <linux/tty_flip.h>
|
|
#include <linux/uaccess.h>
|
|
|
|
#include <asm/da.h>
|
|
|
|
/* Channel error codes */
|
|
#define CONAOK 0
|
|
#define CONERR 1
|
|
#define CONBAD 2
|
|
#define CONPRM 3
|
|
#define CONADR 4
|
|
#define CONCNT 5
|
|
#define CONCBF 6
|
|
#define CONCBE 7
|
|
#define CONBSY 8
|
|
|
|
/* Default channel for the console */
|
|
#define CONSOLE_CHANNEL 1
|
|
|
|
#define NUM_TTY_CHANNELS 6
|
|
|
|
/* Auto allocate */
|
|
#define DA_TTY_MAJOR 0
|
|
|
|
/* A speedy poll rate helps the userland debug process connection response.
|
|
* But, if you set it too high then no other userland processes get much
|
|
* of a look in.
|
|
*/
|
|
#define DA_TTY_POLL (HZ / 50)
|
|
|
|
/*
|
|
* A short put delay improves latency but has a high throughput overhead
|
|
*/
|
|
#define DA_TTY_PUT_DELAY (HZ / 100)
|
|
|
|
static atomic_t num_channels_need_poll = ATOMIC_INIT(0);
|
|
|
|
static struct timer_list poll_timer;
|
|
|
|
static struct tty_driver *channel_driver;
|
|
|
|
static struct timer_list put_timer;
|
|
static struct task_struct *dashtty_thread;
|
|
|
|
/*
|
|
* The console_poll parameter determines whether the console channel should be
|
|
* polled for input.
|
|
* By default the console channel isn't polled at all, in order to avoid the
|
|
* overhead, but that means it isn't possible to have a login on /dev/console.
|
|
*/
|
|
static bool console_poll;
|
|
module_param(console_poll, bool, S_IRUGO);
|
|
|
|
#define RX_BUF_SIZE 1024
|
|
|
|
enum {
|
|
INCHR = 1,
|
|
OUTCHR,
|
|
RDBUF,
|
|
WRBUF,
|
|
RDSTAT
|
|
};
|
|
|
|
/**
|
|
* struct dashtty_port - Wrapper struct for dashtty tty_port.
|
|
* @port: TTY port data
|
|
* @rx_lock: Lock for rx_buf.
|
|
* This protects between the poll timer and user context.
|
|
* It's also held during read SWITCH operations.
|
|
* @rx_buf: Read buffer
|
|
* @xmit_lock: Lock for xmit_*, and port.xmit_buf.
|
|
* This protects between user context and kernel thread.
|
|
* It's also held during write SWITCH operations.
|
|
* @xmit_cnt: Size of xmit buffer contents
|
|
* @xmit_head: Head of xmit buffer where data is written
|
|
* @xmit_tail: Tail of xmit buffer where data is read
|
|
* @xmit_empty: Completion for xmit buffer being empty
|
|
*/
|
|
struct dashtty_port {
|
|
struct tty_port port;
|
|
spinlock_t rx_lock;
|
|
void *rx_buf;
|
|
struct mutex xmit_lock;
|
|
unsigned int xmit_cnt;
|
|
unsigned int xmit_head;
|
|
unsigned int xmit_tail;
|
|
struct completion xmit_empty;
|
|
};
|
|
|
|
static struct dashtty_port dashtty_ports[NUM_TTY_CHANNELS];
|
|
|
|
static atomic_t dashtty_xmit_cnt = ATOMIC_INIT(0);
|
|
static wait_queue_head_t dashtty_waitqueue;
|
|
|
|
/*
|
|
* Low-level DA channel access routines
|
|
*/
|
|
static int chancall(int in_bios_function, int in_channel,
|
|
int in_arg2, void *in_arg3,
|
|
void *in_arg4)
|
|
{
|
|
register int bios_function asm("D1Ar1") = in_bios_function;
|
|
register int channel asm("D0Ar2") = in_channel;
|
|
register int arg2 asm("D1Ar3") = in_arg2;
|
|
register void *arg3 asm("D0Ar4") = in_arg3;
|
|
register void *arg4 asm("D1Ar5") = in_arg4;
|
|
register int bios_call asm("D0Ar6") = 3;
|
|
register int result asm("D0Re0");
|
|
|
|
asm volatile (
|
|
"MSETL [A0StP++], %6,%4,%2\n\t"
|
|
"ADD A0StP, A0StP, #8\n\t"
|
|
"SWITCH #0x0C30208\n\t"
|
|
"GETD %0, [A0StP+#-8]\n\t"
|
|
"SUB A0StP, A0StP, #(4*6)+8\n\t"
|
|
: "=d" (result) /* outs */
|
|
: "d" (bios_function),
|
|
"d" (channel),
|
|
"d" (arg2),
|
|
"d" (arg3),
|
|
"d" (arg4),
|
|
"d" (bios_call) /* ins */
|
|
: "memory");
|
|
|
|
return result;
|
|
}
|
|
|
|
/*
|
|
* Attempts to fetch count bytes from channel and returns actual count.
|
|
*/
|
|
static int fetch_data(unsigned int channel)
|
|
{
|
|
struct dashtty_port *dport = &dashtty_ports[channel];
|
|
int received = 0;
|
|
|
|
spin_lock_bh(&dport->rx_lock);
|
|
/* check the port isn't being shut down */
|
|
if (!dport->rx_buf)
|
|
goto unlock;
|
|
if (chancall(RDBUF, channel, RX_BUF_SIZE,
|
|
(void *)dport->rx_buf, &received) == CONAOK) {
|
|
if (received) {
|
|
int space;
|
|
unsigned char *cbuf;
|
|
|
|
space = tty_prepare_flip_string(&dport->port, &cbuf,
|
|
received);
|
|
|
|
if (space <= 0)
|
|
goto unlock;
|
|
|
|
memcpy(cbuf, dport->rx_buf, space);
|
|
tty_flip_buffer_push(&dport->port);
|
|
}
|
|
}
|
|
unlock:
|
|
spin_unlock_bh(&dport->rx_lock);
|
|
|
|
return received;
|
|
}
|
|
|
|
/**
|
|
* find_channel_to_poll() - Returns number of the next channel to poll.
|
|
* Returns: The number of the next channel to poll, or -1 if none need
|
|
* polling.
|
|
*/
|
|
static int find_channel_to_poll(void)
|
|
{
|
|
static int last_polled_channel;
|
|
int last = last_polled_channel;
|
|
int chan;
|
|
struct dashtty_port *dport;
|
|
|
|
for (chan = last + 1; ; ++chan) {
|
|
if (chan >= NUM_TTY_CHANNELS)
|
|
chan = 0;
|
|
|
|
dport = &dashtty_ports[chan];
|
|
if (dport->rx_buf) {
|
|
last_polled_channel = chan;
|
|
return chan;
|
|
}
|
|
|
|
if (chan == last)
|
|
break;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* put_channel_data() - Write out a block of channel data.
|
|
* @chan: DA channel number.
|
|
*
|
|
* Write a single block of data out to the debug adapter. If the circular buffer
|
|
* is wrapped then only the first block is written.
|
|
*
|
|
* Returns: 1 if the remote buffer was too full to accept data.
|
|
* 0 otherwise.
|
|
*/
|
|
static int put_channel_data(unsigned int chan)
|
|
{
|
|
struct dashtty_port *dport;
|
|
struct tty_struct *tty;
|
|
int number_written;
|
|
unsigned int count = 0;
|
|
|
|
dport = &dashtty_ports[chan];
|
|
mutex_lock(&dport->xmit_lock);
|
|
if (dport->xmit_cnt) {
|
|
count = min((unsigned int)(SERIAL_XMIT_SIZE - dport->xmit_tail),
|
|
dport->xmit_cnt);
|
|
chancall(WRBUF, chan, count,
|
|
dport->port.xmit_buf + dport->xmit_tail,
|
|
&number_written);
|
|
dport->xmit_cnt -= number_written;
|
|
if (!dport->xmit_cnt) {
|
|
/* reset pointers to avoid wraps */
|
|
dport->xmit_head = 0;
|
|
dport->xmit_tail = 0;
|
|
complete(&dport->xmit_empty);
|
|
} else {
|
|
dport->xmit_tail += number_written;
|
|
if (dport->xmit_tail >= SERIAL_XMIT_SIZE)
|
|
dport->xmit_tail -= SERIAL_XMIT_SIZE;
|
|
}
|
|
atomic_sub(number_written, &dashtty_xmit_cnt);
|
|
}
|
|
mutex_unlock(&dport->xmit_lock);
|
|
|
|
/* if we've made more data available, wake up tty */
|
|
if (count && number_written) {
|
|
tty = tty_port_tty_get(&dport->port);
|
|
if (tty) {
|
|
tty_wakeup(tty);
|
|
tty_kref_put(tty);
|
|
}
|
|
}
|
|
|
|
/* did the write fail? */
|
|
return count && !number_written;
|
|
}
|
|
|
|
/**
|
|
* put_data() - Kernel thread to write out blocks of channel data to DA.
|
|
* @arg: Unused.
|
|
*
|
|
* This kernel thread runs while @dashtty_xmit_cnt != 0, and loops over the
|
|
* channels to write out any buffered data. If any of the channels stall due to
|
|
* the remote buffer being full, a hold off happens to allow the debugger to
|
|
* drain the buffer.
|
|
*/
|
|
static int put_data(void *arg)
|
|
{
|
|
unsigned int chan, stall;
|
|
|
|
__set_current_state(TASK_RUNNING);
|
|
while (!kthread_should_stop()) {
|
|
/*
|
|
* For each channel see if there's anything to transmit in the
|
|
* port's xmit_buf.
|
|
*/
|
|
stall = 0;
|
|
for (chan = 0; chan < NUM_TTY_CHANNELS; ++chan)
|
|
stall += put_channel_data(chan);
|
|
|
|
/*
|
|
* If some of the buffers are full, hold off for a short while
|
|
* to allow them to empty.
|
|
*/
|
|
if (stall)
|
|
msleep(25);
|
|
|
|
wait_event_interruptible(dashtty_waitqueue,
|
|
atomic_read(&dashtty_xmit_cnt));
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* This gets called every DA_TTY_POLL and polls the channels for data
|
|
*/
|
|
static void dashtty_timer(struct timer_list *poll_timer)
|
|
{
|
|
int channel;
|
|
|
|
/* If there are no ports open do nothing and don't poll again. */
|
|
if (!atomic_read(&num_channels_need_poll))
|
|
return;
|
|
|
|
channel = find_channel_to_poll();
|
|
|
|
/* Did we find a channel to poll? */
|
|
if (channel >= 0)
|
|
fetch_data(channel);
|
|
|
|
mod_timer(poll_timer, jiffies + DA_TTY_POLL);
|
|
}
|
|
|
|
static void add_poll_timer(struct timer_list *poll_timer)
|
|
{
|
|
timer_setup(poll_timer, dashtty_timer, TIMER_PINNED);
|
|
poll_timer->expires = jiffies + DA_TTY_POLL;
|
|
|
|
/*
|
|
* Always attach the timer to the boot CPU. The DA channels are per-CPU
|
|
* so all polling should be from a single CPU.
|
|
*/
|
|
add_timer_on(poll_timer, 0);
|
|
}
|
|
|
|
static int dashtty_port_activate(struct tty_port *port, struct tty_struct *tty)
|
|
{
|
|
struct dashtty_port *dport = container_of(port, struct dashtty_port,
|
|
port);
|
|
void *rx_buf;
|
|
|
|
/* Allocate the buffer we use for writing data */
|
|
if (tty_port_alloc_xmit_buf(port) < 0)
|
|
goto err;
|
|
|
|
/* Allocate the buffer we use for reading data */
|
|
rx_buf = kzalloc(RX_BUF_SIZE, GFP_KERNEL);
|
|
if (!rx_buf)
|
|
goto err_free_xmit;
|
|
|
|
spin_lock_bh(&dport->rx_lock);
|
|
dport->rx_buf = rx_buf;
|
|
spin_unlock_bh(&dport->rx_lock);
|
|
|
|
/*
|
|
* Don't add the poll timer if we're opening a console. This
|
|
* avoids the overhead of polling the Dash but means it is not
|
|
* possible to have a login on /dev/console.
|
|
*
|
|
*/
|
|
if (console_poll || dport != &dashtty_ports[CONSOLE_CHANNEL])
|
|
if (atomic_inc_return(&num_channels_need_poll) == 1)
|
|
add_poll_timer(&poll_timer);
|
|
|
|
return 0;
|
|
err_free_xmit:
|
|
tty_port_free_xmit_buf(port);
|
|
err:
|
|
return -ENOMEM;
|
|
}
|
|
|
|
static void dashtty_port_shutdown(struct tty_port *port)
|
|
{
|
|
struct dashtty_port *dport = container_of(port, struct dashtty_port,
|
|
port);
|
|
void *rx_buf;
|
|
unsigned int count;
|
|
|
|
/* stop reading */
|
|
if (console_poll || dport != &dashtty_ports[CONSOLE_CHANNEL])
|
|
if (atomic_dec_and_test(&num_channels_need_poll))
|
|
del_timer_sync(&poll_timer);
|
|
|
|
mutex_lock(&dport->xmit_lock);
|
|
count = dport->xmit_cnt;
|
|
mutex_unlock(&dport->xmit_lock);
|
|
if (count) {
|
|
/*
|
|
* There's still data to write out, so wake and wait for the
|
|
* writer thread to drain the buffer.
|
|
*/
|
|
del_timer(&put_timer);
|
|
wake_up_interruptible(&dashtty_waitqueue);
|
|
wait_for_completion(&dport->xmit_empty);
|
|
}
|
|
|
|
/* Null the read buffer (timer could still be running!) */
|
|
spin_lock_bh(&dport->rx_lock);
|
|
rx_buf = dport->rx_buf;
|
|
dport->rx_buf = NULL;
|
|
spin_unlock_bh(&dport->rx_lock);
|
|
/* Free the read buffer */
|
|
kfree(rx_buf);
|
|
|
|
/* Free the write buffer */
|
|
tty_port_free_xmit_buf(port);
|
|
}
|
|
|
|
static const struct tty_port_operations dashtty_port_ops = {
|
|
.activate = dashtty_port_activate,
|
|
.shutdown = dashtty_port_shutdown,
|
|
};
|
|
|
|
static int dashtty_install(struct tty_driver *driver, struct tty_struct *tty)
|
|
{
|
|
return tty_port_install(&dashtty_ports[tty->index].port, driver, tty);
|
|
}
|
|
|
|
static int dashtty_open(struct tty_struct *tty, struct file *filp)
|
|
{
|
|
return tty_port_open(tty->port, tty, filp);
|
|
}
|
|
|
|
static void dashtty_close(struct tty_struct *tty, struct file *filp)
|
|
{
|
|
return tty_port_close(tty->port, tty, filp);
|
|
}
|
|
|
|
static void dashtty_hangup(struct tty_struct *tty)
|
|
{
|
|
int channel;
|
|
struct dashtty_port *dport;
|
|
|
|
channel = tty->index;
|
|
dport = &dashtty_ports[channel];
|
|
|
|
/* drop any data in the xmit buffer */
|
|
mutex_lock(&dport->xmit_lock);
|
|
if (dport->xmit_cnt) {
|
|
atomic_sub(dport->xmit_cnt, &dashtty_xmit_cnt);
|
|
dport->xmit_cnt = 0;
|
|
dport->xmit_head = 0;
|
|
dport->xmit_tail = 0;
|
|
complete(&dport->xmit_empty);
|
|
}
|
|
mutex_unlock(&dport->xmit_lock);
|
|
|
|
tty_port_hangup(tty->port);
|
|
}
|
|
|
|
/**
|
|
* dashtty_put_timer() - Delayed wake up of kernel thread.
|
|
* @ignored: unused
|
|
*
|
|
* This timer function wakes up the kernel thread if any data exists in the
|
|
* buffers. It is used to delay the expensive writeout until the writer has
|
|
* stopped writing.
|
|
*/
|
|
static void dashtty_put_timer(struct timer_list *unused)
|
|
{
|
|
if (atomic_read(&dashtty_xmit_cnt))
|
|
wake_up_interruptible(&dashtty_waitqueue);
|
|
}
|
|
|
|
static int dashtty_write(struct tty_struct *tty, const unsigned char *buf,
|
|
int total)
|
|
{
|
|
int channel, count, block;
|
|
struct dashtty_port *dport;
|
|
|
|
/* Determine the channel */
|
|
channel = tty->index;
|
|
dport = &dashtty_ports[channel];
|
|
|
|
/*
|
|
* Write to output buffer.
|
|
*
|
|
* The reason that we asynchronously write the buffer is because if we
|
|
* were to write the buffer synchronously then because DA channels are
|
|
* per-CPU the buffer would be written to the channel of whatever CPU
|
|
* we're running on.
|
|
*
|
|
* What we actually want to happen is have all input and output done on
|
|
* one CPU.
|
|
*/
|
|
mutex_lock(&dport->xmit_lock);
|
|
/* work out how many bytes we can write to the xmit buffer */
|
|
total = min(total, (int)(SERIAL_XMIT_SIZE - dport->xmit_cnt));
|
|
atomic_add(total, &dashtty_xmit_cnt);
|
|
dport->xmit_cnt += total;
|
|
/* write the actual bytes (may need splitting if it wraps) */
|
|
for (count = total; count; count -= block) {
|
|
block = min(count, (int)(SERIAL_XMIT_SIZE - dport->xmit_head));
|
|
memcpy(dport->port.xmit_buf + dport->xmit_head, buf, block);
|
|
dport->xmit_head += block;
|
|
if (dport->xmit_head >= SERIAL_XMIT_SIZE)
|
|
dport->xmit_head -= SERIAL_XMIT_SIZE;
|
|
buf += block;
|
|
}
|
|
count = dport->xmit_cnt;
|
|
/* xmit buffer no longer empty? */
|
|
if (count)
|
|
reinit_completion(&dport->xmit_empty);
|
|
mutex_unlock(&dport->xmit_lock);
|
|
|
|
if (total) {
|
|
/*
|
|
* If the buffer is full, wake up the kthread, otherwise allow
|
|
* some more time for the buffer to fill up a bit before waking
|
|
* it.
|
|
*/
|
|
if (count == SERIAL_XMIT_SIZE) {
|
|
del_timer(&put_timer);
|
|
wake_up_interruptible(&dashtty_waitqueue);
|
|
} else {
|
|
mod_timer(&put_timer, jiffies + DA_TTY_PUT_DELAY);
|
|
}
|
|
}
|
|
return total;
|
|
}
|
|
|
|
static int dashtty_write_room(struct tty_struct *tty)
|
|
{
|
|
struct dashtty_port *dport;
|
|
int channel;
|
|
int room;
|
|
|
|
channel = tty->index;
|
|
dport = &dashtty_ports[channel];
|
|
|
|
/* report the space in the xmit buffer */
|
|
mutex_lock(&dport->xmit_lock);
|
|
room = SERIAL_XMIT_SIZE - dport->xmit_cnt;
|
|
mutex_unlock(&dport->xmit_lock);
|
|
|
|
return room;
|
|
}
|
|
|
|
static int dashtty_chars_in_buffer(struct tty_struct *tty)
|
|
{
|
|
struct dashtty_port *dport;
|
|
int channel;
|
|
int chars;
|
|
|
|
channel = tty->index;
|
|
dport = &dashtty_ports[channel];
|
|
|
|
/* report the number of bytes in the xmit buffer */
|
|
mutex_lock(&dport->xmit_lock);
|
|
chars = dport->xmit_cnt;
|
|
mutex_unlock(&dport->xmit_lock);
|
|
|
|
return chars;
|
|
}
|
|
|
|
static const struct tty_operations dashtty_ops = {
|
|
.install = dashtty_install,
|
|
.open = dashtty_open,
|
|
.close = dashtty_close,
|
|
.hangup = dashtty_hangup,
|
|
.write = dashtty_write,
|
|
.write_room = dashtty_write_room,
|
|
.chars_in_buffer = dashtty_chars_in_buffer,
|
|
};
|
|
|
|
static int __init dashtty_init(void)
|
|
{
|
|
int ret;
|
|
int nport;
|
|
struct dashtty_port *dport;
|
|
|
|
if (!metag_da_enabled())
|
|
return -ENODEV;
|
|
|
|
channel_driver = tty_alloc_driver(NUM_TTY_CHANNELS,
|
|
TTY_DRIVER_REAL_RAW);
|
|
if (IS_ERR(channel_driver))
|
|
return PTR_ERR(channel_driver);
|
|
|
|
channel_driver->driver_name = "metag_da";
|
|
channel_driver->name = "ttyDA";
|
|
channel_driver->major = DA_TTY_MAJOR;
|
|
channel_driver->minor_start = 0;
|
|
channel_driver->type = TTY_DRIVER_TYPE_SERIAL;
|
|
channel_driver->subtype = SERIAL_TYPE_NORMAL;
|
|
channel_driver->init_termios = tty_std_termios;
|
|
channel_driver->init_termios.c_cflag |= CLOCAL;
|
|
|
|
tty_set_operations(channel_driver, &dashtty_ops);
|
|
for (nport = 0; nport < NUM_TTY_CHANNELS; nport++) {
|
|
dport = &dashtty_ports[nport];
|
|
tty_port_init(&dport->port);
|
|
dport->port.ops = &dashtty_port_ops;
|
|
spin_lock_init(&dport->rx_lock);
|
|
mutex_init(&dport->xmit_lock);
|
|
/* the xmit buffer starts empty, i.e. completely written */
|
|
init_completion(&dport->xmit_empty);
|
|
complete(&dport->xmit_empty);
|
|
}
|
|
|
|
timer_setup(&put_timer, dashtty_put_timer, 0);
|
|
|
|
init_waitqueue_head(&dashtty_waitqueue);
|
|
dashtty_thread = kthread_create(put_data, NULL, "ttyDA");
|
|
if (IS_ERR(dashtty_thread)) {
|
|
pr_err("Couldn't create dashtty thread\n");
|
|
ret = PTR_ERR(dashtty_thread);
|
|
goto err_destroy_ports;
|
|
}
|
|
/*
|
|
* Bind the writer thread to the boot CPU so it can't migrate.
|
|
* DA channels are per-CPU and we want all channel I/O to be on a single
|
|
* predictable CPU.
|
|
*/
|
|
kthread_bind(dashtty_thread, 0);
|
|
wake_up_process(dashtty_thread);
|
|
|
|
ret = tty_register_driver(channel_driver);
|
|
|
|
if (ret < 0) {
|
|
pr_err("Couldn't install dashtty driver: err %d\n",
|
|
ret);
|
|
goto err_stop_kthread;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_stop_kthread:
|
|
kthread_stop(dashtty_thread);
|
|
err_destroy_ports:
|
|
for (nport = 0; nport < NUM_TTY_CHANNELS; nport++) {
|
|
dport = &dashtty_ports[nport];
|
|
tty_port_destroy(&dport->port);
|
|
}
|
|
put_tty_driver(channel_driver);
|
|
return ret;
|
|
}
|
|
device_initcall(dashtty_init);
|
|
|
|
#ifdef CONFIG_DA_CONSOLE
|
|
|
|
static void dash_console_write(struct console *co, const char *s,
|
|
unsigned int count)
|
|
{
|
|
int actually_written;
|
|
|
|
chancall(WRBUF, CONSOLE_CHANNEL, count, (void *)s, &actually_written);
|
|
}
|
|
|
|
static struct tty_driver *dash_console_device(struct console *c, int *index)
|
|
{
|
|
*index = c->index;
|
|
return channel_driver;
|
|
}
|
|
|
|
struct console dash_console = {
|
|
.name = "ttyDA",
|
|
.write = dash_console_write,
|
|
.device = dash_console_device,
|
|
.flags = CON_PRINTBUFFER,
|
|
.index = 1,
|
|
};
|
|
|
|
#endif
|