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:
@@ -5,6 +5,7 @@ UsePrivateHeaders kernel ;
|
||||
KernelAddon tty : kernel drivers dev :
|
||||
driver.cpp
|
||||
tty.cpp
|
||||
line_buffer.cpp
|
||||
master.cpp
|
||||
slave.cpp
|
||||
;
|
||||
|
||||
@@ -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 */
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user