sortix-mirror/kernel/mouse/ps2.cpp

263 lines
5.9 KiB
C++

/*
* Copyright (c) 2015, 2021 Jonas 'Sortie' Termansen.
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
* mouse/ps2.cpp
* PS2 Mouse.
*/
#include <errno.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include <sortix/poll.h>
#include <sortix/stat.h>
#include <sortix/kernel/ioctx.h>
#include <sortix/kernel/kernel.h>
#include <sortix/kernel/kthread.h>
#include <sortix/kernel/ps2.h>
#include "ps2.h"
namespace Sortix {
static const uint8_t DEVICE_RESET_OK = 0xAA;
static const uint8_t DEVICE_ECHO = 0xEE;
static const uint8_t DEVICE_ACK = 0xFA;
static const uint8_t DEVICE_RESEND = 0xFE;
static const uint8_t DEVICE_ERROR = 0xFF;
static const uint8_t DEVICE_CMD_ENABLE_SCAN = 0xF4;
static const uint8_t DEVICE_CMD_DISABLE_SCAN = 0xF5;
static const uint8_t DEVICE_CMD_IDENTIFY = 0xF2;
static const uint8_t DEVICE_CMD_RESET = 0xFF;
static const size_t DEVICE_RETRIES = 5;
PS2Mouse::PS2Mouse()
{
this->queue = NULL;
this->queuelength = 0;
this->queueoffset = 0;
this->queueused = 0;
this->owner = NULL;
this->ownerptr = NULL;
this->mlock = KTHREAD_MUTEX_INITIALIZER;
}
PS2Mouse::~PS2Mouse()
{
delete[] queue;
}
void PS2Mouse::PS2DeviceInitialize(void* send_ctx, bool (*send)(void*, uint8_t),
uint8_t* id, size_t id_size)
{
if ( sizeof(this->id) < id_size )
id_size = sizeof(this->id);
this->send_ctx = send_ctx;
this->send = send;
this->state = STATE_INIT;
this->tries = 0;
memcpy(this->id, id, id_size);
this->id_size = id_size;
PS2DeviceOnByte(DEVICE_RESEND);
}
void PS2Mouse::PS2DeviceOnByte(uint8_t byte)
{
ScopedLock lock(&mlock);
if ( state == STATE_INIT )
{
state = STATE_ENABLE_SCAN;
tries = DEVICE_RETRIES;
byte = DEVICE_RESEND;
}
if ( state == STATE_ENABLE_SCAN )
{
if ( byte == DEVICE_RESEND && tries-- )
{
if ( send(send_ctx, DEVICE_CMD_ENABLE_SCAN) )
return;
}
state = STATE_NORMAL;
tries = DEVICE_RETRIES;
if ( byte == DEVICE_ACK )
return;
byte = DEVICE_RESEND;
}
if ( state == STATE_NORMAL )
{
PushByte(byte);
lock.Reset();
NotifyOwner();
return;
}
}
void PS2Mouse::NotifyOwner()
{
if ( !owner )
return;
owner->OnMouseByte(this, ownerptr);
}
void PS2Mouse::SetOwner(PS2MouseDevice* owner, void* user)
{
this->owner = owner;
this->ownerptr = user;
if ( queueused )
NotifyOwner();
}
bool PS2Mouse::PushByte(uint8_t byte)
{
// Check if we need to allocate or resize the circular queue.
if ( queueused == queuelength )
{
size_t newqueuelength = queuelength ? 2 * queuelength : 32UL;
if ( 1024 < newqueuelength )
return false;
uint8_t* newqueue = new uint8_t[newqueuelength];
if ( !newqueue )
return false;
size_t elemsize = sizeof(*queue);
size_t leadingavai = queuelength - queueoffset;
size_t leading = leadingavai < queueused ? leadingavai : queueused;
size_t trailing = queueused - leading;
if ( queue )
{
memcpy(newqueue, queue + queueoffset, leading * elemsize);
memcpy(newqueue + leading, queue, trailing * elemsize);
delete[] queue;
}
queue = newqueue;
queuelength = newqueuelength;
queueoffset = 0;
}
queue[(queueoffset + queueused++) % queuelength] = byte;
return true;
}
uint8_t PS2Mouse::PopByte()
{
if ( !queueused )
return 0;
uint8_t byte = queue[queueoffset];
queueoffset = (queueoffset + 1) % queuelength;
queueused--;
return byte;
}
uint8_t PS2Mouse::Read()
{
ScopedLock lock(&mlock);
return PopByte();
}
size_t PS2Mouse::GetPending() const
{
ScopedLock lock(&mlock);
return queueused;
}
bool PS2Mouse::HasPending() const
{
ScopedLock lock(&mlock);
return queueused;
}
PS2MouseDevice::PS2MouseDevice(dev_t dev, mode_t mode, uid_t owner, gid_t group,
PS2Mouse* mouse)
{
inode_type = INODE_TYPE_TTY;
this->dev = dev;
this->ino = (ino_t) this;
this->type = S_IFCHR;
this->stat_mode = (mode & S_SETABLE) | this->type;
this->stat_uid = owner;
this->stat_gid = group;
this->mouse = mouse;
this->devlock = KTHREAD_MUTEX_INITIALIZER;
this->datacond = KTHREAD_COND_INITIALIZER;
mouse->SetOwner(this, NULL);
}
PS2MouseDevice::~PS2MouseDevice()
{
delete mouse;
}
ssize_t PS2MouseDevice::read(ioctx_t* ctx, uint8_t* buf, size_t count)
{
ScopedLockSignal lock(&devlock);
if ( !lock.IsAcquired() )
return errno = EINTR, -1;
size_t sofar = 0;
while ( sofar < count )
{
if ( !mouse->HasPending() )
{
if ( sofar != 0 )
break;
do
{
if ( !kthread_cond_wait_signal(&datacond, &devlock) )
return sofar ? sofar : -1;
} while ( !mouse->HasPending() );
}
uint8_t byte = mouse->Read();
if ( !ctx->copy_to_dest(buf + sofar, &byte, 1) )
return sofar ? sofar : -1;
sofar++;
}
return (ssize_t) sofar;
}
short PS2MouseDevice::PollEventStatus()
{
short status = 0;
if ( mouse->HasPending() )
status |= POLLIN | POLLRDNORM;
return status;
}
int PS2MouseDevice::poll(ioctx_t* /*ctx*/, PollNode* node)
{
ScopedLock lock(&devlock);
short ret_status = PollEventStatus() & node->events;
if ( ret_status )
{
node->master->revents |= ret_status;
return 0;
}
poll_channel.Register(node);
return errno = EAGAIN, -1;
}
void PS2MouseDevice::OnMouseByte(PS2Mouse* /*mouse*/, void* /*user*/)
{
ScopedLock lock(&devlock);
poll_channel.Signal(PollEventStatus());
kthread_cond_signal(&datacond);
}
} // namespace Sortix