Work in progress. Basic data exchange seems to work.

Only slightly tested (moved data to and from pty <-> tty).


git-svn-id: file:///srv/svn/repos/haiku/trunk/current@9087 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Axel Dörfler
2004-09-28 04:16:34 +00:00
parent 410c1c159f
commit fa42f1c2b9
8 changed files with 781 additions and 27 deletions
+1
View File
@@ -5,6 +5,7 @@ UsePrivateHeaders kernel ;
KernelAddon tty : kernel drivers dev :
driver.cpp
tty.cpp
line_buffer.cpp
master.cpp
slave.cpp
;
+5 -7
View File
@@ -40,8 +40,8 @@ init_driver(void)
{
TRACE((DRIVER_NAME ": init_driver()\n"));
// create driver name array
// create driver name array and initialize basic TTY structures
char letter = 'p';
int8 digit = 0;
@@ -59,15 +59,13 @@ init_driver(void)
if (++digit > 15)
digit = 0, letter++;
reset_tty(&gMasterTTYs[i], i);
reset_tty(&gSlaveTTYs[i], i);
}
sDeviceNames[kNumTTYs * 2] = NULL;
// initialize TTY structures
memset(gMasterTTYs, 0, kNumTTYs * sizeof(struct tty));
memset(gSlaveTTYs, 0, kNumTTYs * sizeof(struct tty));
return B_OK;
}
@@ -0,0 +1,114 @@
/*
** Copyright 2004, Axel Dörfler, [email protected]. All rights reserved.
** Distributed under the terms of the Haiku License.
*/
#include "line_buffer.h"
#include <KernelExport.h>
#include <stdlib.h>
status_t
clear_line_buffer(struct line_buffer &buffer)
{
buffer.in = 0;
buffer.first = 0;
}
status_t
init_line_buffer(struct line_buffer &buffer, size_t size)
{
clear_line_buffer(buffer);
buffer.buffer = (char *)malloc(size);
if (buffer.buffer == NULL)
return B_NO_MEMORY;
buffer.size = size;
return B_OK;
}
status_t
uninit_line_buffer(struct line_buffer &buffer)
{
free(buffer.buffer);
return B_OK;
}
int32
line_buffer_readable(struct line_buffer &buffer)
{
return buffer.in;
}
int32
line_buffer_writable(struct line_buffer &buffer)
{
return buffer.size - buffer.in;
}
ssize_t
line_buffer_user_read(struct line_buffer &buffer, char *data, size_t length)
{
size_t available = buffer.in;
if (length > available)
length = available;
if (length == 0)
return 0;
ssize_t bytesRead = length;
if (buffer.first + length < buffer.size) {
// simple copy
if (user_memcpy(data, buffer.buffer + buffer.first, length) != B_OK)
bytesRead = B_BAD_ADDRESS;
} else {
// need to copy both ends
size_t upper = buffer.size - buffer.first;
size_t lower = length - upper;
if (user_memcpy(data, buffer.buffer + buffer.first, upper) != B_OK
|| user_memcpy(data + upper, buffer.buffer, lower) != B_OK)
bytesRead = B_BAD_ADDRESS;
}
if (bytesRead > 0)
buffer.first = (buffer.first + bytesRead) % buffer.size;
return bytesRead;
}
status_t
line_buffer_putc(struct line_buffer &buffer, char c)
{
if (buffer.in == buffer.size)
return B_NO_MEMORY;
buffer.buffer[(buffer.first + buffer.in++) % buffer.size] = c;
}
#if 0
status_t
line_buffer_getc(struct line_buffer &buffer, char *_c)
{
}
status_t
line_buffer_ungetc(struct line_buffer &buffer, char *c)
{
}
#endif
@@ -0,0 +1,29 @@
/*
** Copyright 2004, Axel Dörfler, [email protected]. All rights reserved.
** Distributed under the terms of the Haiku License.
*/
#ifndef LINE_BUFFER_H
#define LINE_BUFFER_H
#include <OS.h>
struct line_buffer {
int32 first;
size_t in;
size_t size;
char *buffer;
};
status_t init_line_buffer(struct line_buffer &buffer, size_t size);
status_t uninit_line_buffer(struct line_buffer &buffer);
status_t reset_line_buffer(struct line_buffer &buffer);
int32 line_buffer_readable(struct line_buffer &buffer);
int32 line_buffer_writable(struct line_buffer &buffer);
ssize_t line_buffer_user_read(struct line_buffer &buffer, char *data, size_t length);
status_t line_buffer_getc(struct line_buffer &buffer, char *_c);
status_t line_buffer_putc(struct line_buffer &buffer, char c);
status_t line_buffer_ungetc(struct line_buffer &buffer, char *c);
#endif /* LINE_BUFFER_H */
+45 -7
View File
@@ -19,6 +19,7 @@
struct master_cookie {
struct tty *tty;
struct tty *slave;
uint32 open_mode;
};
@@ -26,21 +27,45 @@ struct master_cookie {
struct tty gMasterTTYs[kNumTTYs];
static status_t
master_service(struct tty *tty, uint32 op)
{
// nothing here yet
return B_OK;
}
// #pragma mark -
static status_t
master_open(const char *name, uint32 flags, void **_cookie)
{
int32 index = get_tty_index(name);
dprintf("TTY index = %ld (name = %s)\n", index, name);
if (atomic_or(&gMasterTTYs[index].open_count, 1) != 0) {
// we're already open!
return B_BUSY;
}
status_t status = tty_open(&gMasterTTYs[index], &master_service);
if (status < B_OK) {
// initializing TTY failed, reset open counter
atomic_and(&gMasterTTYs[index].open_count, 0);
return status;
}
master_cookie *cookie = (master_cookie *)malloc(sizeof(struct master_cookie));
if (cookie == NULL)
if (cookie == NULL) {
atomic_and(&gMasterTTYs[index].open_count, 0);
tty_close(&gMasterTTYs[index]);
return B_NO_MEMORY;
}
cookie->tty = &gMasterTTYs[index];
cookie->slave = &gSlaveTTYs[index];
cookie->open_mode = flags;
*_cookie = cookie;
return B_OK;
@@ -62,7 +87,10 @@ master_close(void *_cookie)
static status_t
master_free_cookie(void *_cookie)
{
free(_cookie);
master_cookie *cookie = (master_cookie *)_cookie;
tty_close(cookie->tty);
free(cookie);
return B_OK;
}
@@ -70,38 +98,48 @@ master_free_cookie(void *_cookie)
static status_t
master_ioctl(void *_cookie, uint32 op, void *buffer, size_t length)
{
master_cookie *cookie = (master_cookie *)_cookie;
TRACE(("master_ioctl: cookie %p, op %lu, buffer %p, length %lu\n", _cookie, op, buffer, length));
return B_BAD_VALUE;
return tty_ioctl(cookie->tty, op, buffer, length);
}
static status_t
master_read(void *_cookie, off_t offset, void *buffer, size_t *_length)
{
master_cookie *cookie = (master_cookie *)_cookie;
TRACE(("master_read: cookie %p, offset %Ld, buffer %p, length %lu\n", _cookie, offset, buffer, *_length));
return B_BAD_VALUE;
return tty_input_read(cookie->tty, buffer, _length, cookie->open_mode);
}
static status_t
master_write(void *_cookie, off_t offset, const void *buffer, size_t *_length)
{
master_cookie *cookie = (master_cookie *)_cookie;
TRACE(("master_write: cookie %p, offset %Ld, buffer %p, length %lu\n", _cookie, offset, buffer, *_length));
return B_BAD_VALUE;
return tty_write_to_tty(cookie->tty, cookie->slave, buffer, _length, cookie->open_mode, true);
}
static status_t
master_select(void *_cookie, uint8 event, uint32 ref, selectsync *sync)
{
return B_OK;
master_cookie *cookie = (master_cookie *)_cookie;
return tty_select(cookie->tty, event, ref, sync);
}
static status_t
master_deselect(void *_cookie, uint8 event, selectsync *sync)
{
return B_OK;
master_cookie *cookie = (master_cookie *)_cookie;
return tty_deselect(cookie->tty, event, sync);
}
+37 -12
View File
@@ -19,6 +19,7 @@
struct slave_cookie {
struct tty *tty;
struct tty *master;
uint32 open_mode;
};
@@ -31,16 +32,32 @@ slave_open(const char *name, uint32 flags, void **_cookie)
{
int32 index = get_tty_index(name);
if (gSlaveTTYs[index].open_count == 0)
// we may only be used if our master has already been opened
if (gMasterTTYs[index].open_count == 0)
return B_IO_ERROR;
atomic_add(&gSlaveTTYs[index].open_count, 1);
if (atomic_add(&gSlaveTTYs[index].open_count, 1) == 0) {
// ToDo: this is broken and must be synchronized with all
// other open calls to that slave
// Also, tty_open() should probably be changed and always called in open().
status_t status = tty_open(&gSlaveTTYs[index], NULL);
if (status < B_OK) {
// initializing TTY failed, reset open counter
atomic_add(&gSlaveTTYs[index].open_count, -1);
return status;
}
}
slave_cookie *cookie = (slave_cookie *)malloc(sizeof(struct slave_cookie));
if (cookie == NULL)
if (cookie == NULL) {
atomic_add(&gSlaveTTYs[index].open_count, -1);
tty_close(&gSlaveTTYs[index]);
return B_NO_MEMORY;
}
cookie->tty = &gSlaveTTYs[index];
cookie->master = &gMasterTTYs[index];
cookie->open_mode = flags;
*_cookie = cookie;
return B_OK;
@@ -50,11 +67,6 @@ slave_open(const char *name, uint32 flags, void **_cookie)
static status_t
slave_close(void *_cookie)
{
slave_cookie *cookie = (slave_cookie *)_cookie;
TRACE(("slave_close: cookie %p\n", _cookie));
atomic_add(&cookie->tty->open_count, -1);
return B_OK;
}
@@ -62,7 +74,14 @@ slave_close(void *_cookie)
static status_t
slave_free_cookie(void *_cookie)
{
free(_cookie);
slave_cookie *cookie = (slave_cookie *)_cookie;
TRACE(("slave_close: cookie %p\n", _cookie));
if (atomic_add(&cookie->tty->open_count, -1) == 1)
tty_close(cookie->tty);
free(cookie);
return B_OK;
}
@@ -70,24 +89,30 @@ slave_free_cookie(void *_cookie)
static status_t
slave_ioctl(void *_cookie, uint32 op, void *buffer, size_t length)
{
slave_cookie *cookie = (slave_cookie *)_cookie;
TRACE(("slave_ioctl: cookie %p, op %lu, buffer %p, length %lu\n", _cookie, op, buffer, length));
return B_BAD_VALUE;
return tty_ioctl(cookie->tty, op, buffer, length);
}
static status_t
slave_read(void *_cookie, off_t offset, void *buffer, size_t *_length)
{
slave_cookie *cookie = (slave_cookie *)_cookie;
TRACE(("slave_read: cookie %p, offset %Ld, buffer %p, length %lu\n", _cookie, offset, buffer, *_length));
return B_BAD_VALUE;
return tty_input_read(cookie->tty, buffer, _length, cookie->open_mode);
}
static status_t
slave_write(void *_cookie, off_t offset, const void *buffer, size_t *_length)
{
slave_cookie *cookie = (slave_cookie *)_cookie;
TRACE(("slave_write: cookie %p, offset %Ld, buffer %p, length %lu\n", _cookie, offset, buffer, *_length));
return B_BAD_VALUE;
return tty_write_to_tty(cookie->tty, cookie->master, buffer, _length, cookie->open_mode, false);
}
+514
View File
@@ -3,9 +3,17 @@
** Distributed under the terms of the Haiku License.
*/
// This file could be moved into a generic tty module.
// The whole hardware signaling stuff is missing, though - it's currently
// tailored for pseudo-TTYs. Have a look at Be's TTY includes (drivers/tty/*)
#include "tty_private.h"
#include <util/kernel_cpp.h>
#include <signal.h>
#include <string.h>
#define TTY_TRACE
#ifdef TTY_TRACE
@@ -15,6 +23,217 @@
#endif
class WriterLocker {
public:
WriterLocker(struct tty *source, struct tty *target, bool echo, bool sourceIsMaster);
~WriterLocker();
status_t AcquireWriter(bool dontBlock);
void ReportWritten(size_t written);
private:
void Lock();
void Unlock();
struct tty *fSource, *fTarget;
size_t fSourceBytes, fTargetBytes;
bool fEcho;
};
class ReaderLocker {
public:
ReaderLocker(struct tty *tty);
~ReaderLocker();
status_t AcquireReader(bool dontBlock);
void ReportRead(size_t bytesRead);
private:
void Lock();
void Unlock();
struct tty *fTTY;
size_t fBytes;
};
class MutexLocker {
public:
MutexLocker(struct mutex *mutex)
: fMutex(mutex)
{
mutex_lock(mutex);
}
~MutexLocker()
{
mutex_unlock(fMutex);
}
private:
struct mutex *fMutex;
};
WriterLocker::WriterLocker(struct tty *source, struct tty *target, bool echo, bool sourceIsMaster)
:
fSource(source),
fTarget(target),
fEcho(echo)
{
if (echo) {
if (!sourceIsMaster) {
// just switch the two, we have to lock both of them anyway
fSource = target;
fTarget = source;
}
}
Lock();
}
WriterLocker::~WriterLocker()
{
Unlock();
}
void
WriterLocker::Lock()
{
mutex_lock(&fSource->lock);
if (fEcho)
mutex_lock(&fTarget->lock);
}
void
WriterLocker::Unlock()
{
if (fEcho)
mutex_unlock(&fTarget->lock);
mutex_unlock(&fSource->lock);
}
status_t
WriterLocker::AcquireWriter(bool dontBlock)
{
// Release TTY lock in order not to block. We only need to do this
// if we could have to wait for the buffer to become writable.
if (!dontBlock)
Unlock();
status_t status = acquire_sem_etc(fSource->write_sem, 1, (dontBlock ? B_TIMEOUT : 0) | B_CAN_INTERRUPT, 0);
if (status == B_OK && fEcho) {
// We need to hold two write semaphores in order to echo the output to
// the local TTY as well. We need to make sure that these semaphores
// are always acquired in the same order to prevent deadlocks
status = acquire_sem_etc(fTarget->write_sem, 1, (dontBlock ? B_TIMEOUT : 0) | B_CAN_INTERRUPT, 0);
if (status != B_OK)
release_sem(fSource->write_sem);
}
// reacquire TTY lock
if (!dontBlock)
Lock();
if (status == B_OK) {
fSourceBytes = line_buffer_writable(fSource->input_buffer);
if (fEcho)
fTargetBytes = line_buffer_writable(fTarget->input_buffer);
}
return status;
}
void
WriterLocker::ReportWritten(size_t written)
{
if (written < fSourceBytes)
release_sem_etc(fSource->write_sem, 1, fTarget ? B_DO_NOT_RESCHEDULE : 0);
if (fEcho && written < fTargetBytes)
release_sem(fTarget->write_sem);
// there is now probably something to read, too
if (written > 0) {
release_sem(fTarget->read_sem);
if (fEcho)
release_sem(fSource->read_sem);
}
}
// #pragma mark -
ReaderLocker::ReaderLocker(struct tty *tty)
:
fTTY(tty)
{
Lock();
}
ReaderLocker::~ReaderLocker()
{
Unlock();
}
status_t
ReaderLocker::AcquireReader(bool dontBlock)
{
// Release TTY lock in order not to block. We only need to do this
// if we could have to wait for the buffer to become readable.
if (!dontBlock)
Unlock();
status_t status = acquire_sem_etc(fTTY->read_sem, 1, (dontBlock ? B_TIMEOUT : 0) | B_CAN_INTERRUPT, 0);
// reacquire TTY lock
if (!dontBlock)
Lock();
if (status == B_OK)
fBytes = line_buffer_readable(fTTY->input_buffer);
return status;
}
void
ReaderLocker::ReportRead(size_t bytesRead)
{
if (bytesRead < fBytes && bytesRead != 0)
release_sem(fTTY->read_sem);
// there may be space to write too again
if (bytesRead > 0)
release_sem(fTTY->write_sem);
}
void
ReaderLocker::Lock()
{
mutex_lock(&fTTY->lock);
}
void
ReaderLocker::Unlock()
{
mutex_unlock(&fTTY->lock);
}
// #pragma mark -
int32
get_tty_index(const char *name)
{
@@ -28,3 +247,298 @@ get_tty_index(const char *name)
return (name[3] - 'p') * digit;
}
void
reset_termios(struct termios &termios)
{
memset(&termios, 0, sizeof(struct termios));
termios.c_iflag = 0;
termios.c_oflag = OPOST;
termios.c_cflag = B19200 | CS8 | CREAD | HUPCL;
// enable receiver, hang up on last close
termios.c_lflag = 0;
// control characters
termios.c_cc[VINTR] = CTRL('C');
termios.c_cc[VQUIT] = CTRL('\\');
termios.c_cc[VERASE] = CTRL('H');
termios.c_cc[VKILL] = CTRL('U');
termios.c_cc[VEOF] = CTRL('D');
termios.c_cc[VSTART] = CTRL('S');
termios.c_cc[VSTOP] = CTRL('Q');
}
void
reset_tty(struct tty *tty, int32 index)
{
reset_termios(tty->termios);
tty->open_count = 0;
tty->index = index;
tty->pgrp_id = 0;
tty->lock.sem = tty->read_sem = tty->write_sem = -1;
// the semaphores are only created when the TTY is actually in use
// some initial window size - the TTY in question should set these values
tty->window_size.ws_col = 80;
tty->window_size.ws_row = 25;
tty->window_size.ws_xpixel = tty->window_size.ws_col * 8;
tty->window_size.ws_ypixel = tty->window_size.ws_row * 8;
}
status_t
tty_output_getc(struct tty *tty, int *_c)
{
}
static void
tty_input_putc_locked(struct tty *tty, int c)
{
line_buffer_putc(tty->input_buffer, c);
}
status_t
tty_input_putc(struct tty *tty, int c)
{
status_t status = acquire_sem_etc(tty->write_sem, 1, B_CAN_INTERRUPT, 0);
if (status != B_OK)
return status;
MutexLocker locker(&tty->lock);
tty_input_putc_locked(tty, c);
if (line_buffer_writable(tty->input_buffer))
release_sem(tty->write_sem);
}
// #pragma mark -
// device functions
status_t
tty_close(struct tty *tty)
{
mutex_destroy(&tty->lock);
delete_sem(tty->write_sem);
delete_sem(tty->read_sem);
uninit_line_buffer(tty->input_buffer);
return B_OK;
}
status_t
tty_open(struct tty *tty, tty_service_func func)
{
status_t status;
if (init_line_buffer(tty->input_buffer, TTY_BUFFER_SIZE) < B_OK)
return B_NO_MEMORY;
if ((status = mutex_init(&tty->lock, "tty lock")) < B_OK)
goto err1;
tty->write_sem = create_sem(1, "tty write");
if (tty->write_sem < B_OK) {
status = tty->write_sem;
goto err2;
}
tty->read_sem = create_sem(0, "tty read");
if (tty->read_sem < B_OK) {
status = tty->read_sem;
goto err3;
}
tty->service_func = func;
return B_OK;
err3:
delete_sem(tty->write_sem);
err2:
mutex_destroy(&tty->lock);
err1:
uninit_line_buffer(tty->input_buffer);
}
status_t
tty_ioctl(struct tty *tty, uint32 op, void *buffer, size_t length)
{
TRACE(("tty_ioctl: tty %p, op %lu, buffer %p, length %lu\n", &tty, op, buffer, length));
MutexLocker locker(&tty->lock);
switch (op) {
/* get and set TTY attributes */
case TCGETA:
TRACE(("tty: get attributes\n"));
return user_memcpy(buffer, &tty->termios, sizeof(struct termios));
case TCSETA:
case TCSETAW:
case TCSETAF:
TRACE(("tty: set attributes\n"));
return user_memcpy(&tty->termios, buffer, sizeof(struct termios));
/* get and set process group ID */
case TIOCGPGRP:
TRACE(("tty: get pgrp_id\n"));
return user_memcpy(buffer, &tty->pgrp_id, sizeof(pid_t));
case TIOCSPGRP:
case 'pgid':
TRACE(("tty: set pgrp_id\n"));
return user_memcpy(&tty->pgrp_id, buffer, sizeof(pid_t));
/* get and set window size */
case TIOCGWINSZ:
TRACE(("tty: set window size\n"));
return user_memcpy(buffer, &tty->window_size, sizeof(struct winsize));
case TIOCSWINSZ:
{
uint16 oldColumns = tty->window_size.ws_col, oldRows = tty->window_size.ws_row;
TRACE(("tty: set window size\n"));
if (user_memcpy(&tty->window_size, buffer, sizeof(struct winsize)) < B_OK)
return B_BAD_ADDRESS;
// send a signal only if the window size has changed
if ((oldColumns != tty->window_size.ws_col || oldRows != tty->window_size.ws_row)
&& tty->pgrp_id != 0)
send_signal(-tty->pgrp_id, SIGWINCH);
return B_OK;
}
}
TRACE(("tty: unsupported opcode %lu\n", op));
return B_BAD_VALUE;
}
status_t
tty_input_read(struct tty *tty, void *buffer, size_t *_length, uint32 mode)
{
bool dontBlock = (mode & O_NONBLOCK) != 0;
size_t length = *_length;
size_t bytesLeft = length;
if (length == 0)
return B_OK;
ReaderLocker locker(tty);
while (bytesLeft > 0) {
status_t status = locker.AcquireReader(dontBlock);
if (status != B_OK) {
*_length -= bytesLeft;
return status;
}
ssize_t bytesRead = line_buffer_user_read(tty->input_buffer, (char *)buffer, bytesLeft);
if (bytesRead < B_OK) {
*_length = 0;
locker.ReportRead(0);
return bytesRead;
}
bytesLeft -= bytesRead;
locker.ReportRead(bytesRead);
}
return B_OK;
}
status_t
tty_write_to_tty(struct tty *source, struct tty *target, const void *buffer, size_t *_length,
uint32 mode, bool sourceIsMaster)
{
const char *data = (const char *)buffer;
size_t length = *_length;
size_t bytesWritten = 0;
bool dontBlock = (mode & O_NONBLOCK) != 0;
bool echo = (source->termios.c_lflag & ECHO) != 0;
// ToDo: "buffer" is not yet copied or accessed in a safe way!
if (length == 0)
return B_OK;
WriterLocker locker(source, target, echo, sourceIsMaster);
while (bytesWritten < length) {
status_t status = locker.AcquireWriter(dontBlock);
if (status != B_OK) {
*_length = bytesWritten;
return status;
}
size_t writable = line_buffer_writable(target->input_buffer);
if (writable > length)
writable = length;
if (echo) {
size_t locallyWritable = line_buffer_writable(source->input_buffer);
if (locallyWritable < writable)
writable = locallyWritable;
}
if (writable == 0) {
locker.ReportWritten(0);
continue;
}
while (writable > bytesWritten) {
char c = data[0];
if (c == '\n' && (source->termios.c_oflag & (OPOST | ONLCR)) == OPOST | ONLCR) {
// post-process output and transfrom '\n' to '\r\n'
tty_input_putc_locked(target, '\r');
if (echo)
tty_input_putc_locked(source, '\r');
}
tty_input_putc_locked(target, c);
if (echo)
tty_input_putc_locked(source, c);
data++;
bytesWritten++;
}
locker.ReportWritten(bytesWritten);
}
return B_OK;
}
status_t
tty_select(struct tty *tty, uint8 event, uint32 ref, selectsync *sync)
{
return B_OK;
}
status_t
tty_deselect(struct tty *tty, uint8 event, selectsync *sync)
{
return B_OK;
}
+36 -1
View File
@@ -8,12 +8,33 @@
#include <KernelExport.h>
#include <Drivers.h>
#include <lock.h>
#include <termios.h>
#include "line_buffer.h"
#define TTY_BUFFER_SIZE 4096
// The only nice Rico'ism :)
#define CTRL(c) ((c) - 0100)
typedef status_t (*tty_service_func)(struct tty *tty, uint32 op);
// not yet used...
struct tty {
int32 open_count;
int32 open_count;
int32 index;
struct mutex lock;
sem_id read_sem;
sem_id write_sem;
pid_t pgrp_id;
line_buffer input_buffer;
tty_service_func service_func;
struct termios termios;
struct winsize window_size;
};
static const uint32 kNumTTYs = 64;
@@ -25,6 +46,20 @@ extern device_hooks gMasterTTYHooks;
extern device_hooks gSlaveTTYHooks;
// functions available for master/slave TTYs
extern int32 get_tty_index(const char *name);
extern void reset_tty(struct tty *tty, int32 index);
extern status_t tty_input_putc(struct tty *tty, int c);
extern status_t tty_input_read(struct tty *tty, void *buffer, size_t *_length, uint32 mode);
extern status_t tty_output_getc(struct tty *tty, int *_c);
extern status_t tty_write_to_tty(struct tty *tty, struct tty *dest, const void *buffer,
size_t *_length, uint32 mode, bool sourceIsMaster);
extern status_t tty_open(struct tty *tty, tty_service_func func);
extern status_t tty_close(struct tty *tty);
extern status_t tty_ioctl(struct tty *tty, uint32 op, void *buffer, size_t length);
extern status_t tty_select(struct tty *tty, uint8 event, uint32 ref, selectsync *sync);
extern status_t tty_deselect(struct tty *tty, uint8 event, selectsync *sync);
#endif /* TTY_PRIVATE_H */