shoebill/core/adb.c

405 lines
12 KiB
C

/*
* Copyright (c) 2013, Peter Rutenbar <pruten@gmail.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include "../core/shoebill.h"
#include <assert.h>
// via1 ORB bits abcd efgh
// cd -> adb FSM state
// e -> adb timeout occurred / service request (?)
// f/g/h nvram stuff
#define VIA_REGB_ADB_STATUS 8
/*
Reset:
-> OS writes reset command to sr, sets state 0
<- chip sets ADB status line, raises interrupt
-> OS dummy-reads sr, sets state 2
<- chip sets ADB status line, raises interrupt
-> OS dummy-reads sr, sets state 3
Talk:
-> OS writes talk command to sr, sets state 0
<- chip responds with an interrupt. Status=0 -> poll(unsolicited?), status=1 -> response?
-> OS sets state=1
<- chip responds with an interrupt. Status=0 -> timeout, status=1 -> not-timedout?
-> OS reads first byte from sr, sets state=2
<- chip responds with interrupt. Status=0 -> service request, service=1 no-service-reques
-> OS reads second byte from sr, sets state=3
Listen:
-> OS writes listen command to sr, sets state 0
<- chip
???
Keyboard:
-> OS sends EXISTS (TALK) for id 2, reg 3
<- (if no keyboard: timeout. If keyboard: respond with some data)
-> OS sends FLUSH
<- Keyboard responds
-> OS sends LISTEN for id 2, reg 3 (data: 0x2201 - sets the service request enable bit)
<- Keyboard respnods
-> OS sends TALK for id 2 reg 2
<- Keyboard responds with some keyboard setup data
-> OS sends TALK for id 2 reg 0
<- if there's a key, keyboard returns key data. Otherwise, response times out.
*/
void adb_start_service_request()
{
//printf("adb_start_service_request: pending_requests = 0x%02x\n", shoe.adb.pending_service_requests);
if (shoe.adb.pending_service_requests) {
shoe.adb.service_request = 1;
shoe.adb.poll = shoe.adb.pending_poll;
shoe.adb.pending_poll = 0;
via_raise_interrupt(1, IFR_SHIFT_REG);
}
}
void adb_request_service_request(uint8_t id)
{
shoe.adb.pending_service_requests |= (1 << id);
shoe.adb.pending_poll = 1;
if (shoe.adb.state == 3) {
adb_start_service_request();
}
}
static void keyboard_talk(uint8_t reg)
{
shoe.adb.timeout = 0;
switch (reg) {
case 0:
if (shoe.key.key_i > 0) {
shoe.adb.data[0] = shoe.key.keys[0].code_b;
shoe.adb.data[1] = shoe.key.keys[0].code_a;
shoe.adb.data_len = 2;
shoe.key.key_i--;
memmove(&shoe.key.keys[0], &shoe.key.keys[1], shoe.key.key_i * sizeof(shoe.key.keys[0]));
}
else
shoe.adb.timeout = 1;
return ;
case 2:
// All the modifier keys are up
shoe.adb.data[0] = 0b01111111;
shoe.adb.data[1] = 0b11100111;
shoe.adb.data_len = 2;
return ;
case 1:
shoe.adb.timeout = 1;
return ;
case 3:
shoe.adb.data[0] = 0;
shoe.adb.data[1] = 0;
shoe.adb.data_len = 2;
return ;
}
}
static void mouse_talk(uint8_t reg)
{
shoe.adb.timeout = 0;
printf("mouse_talk: reg=%u\n", reg);
switch (reg) {
case 0:
if (shoe.mouse.changed) {
const int32_t hi_delta_limit = 32;
const int32_t low_delta_limit = -32;
int32_t x = shoe.mouse.delta_x;
int32_t y = shoe.mouse.delta_y;
//printf("mouse_talk: x=%d, y=%d button=%u\n", shoe.mouse.delta_x, shoe.mouse.delta_y, shoe.mouse.button_down);
if (x > hi_delta_limit) x = hi_delta_limit;
if (x < low_delta_limit) x = low_delta_limit;
if (y > hi_delta_limit) y = hi_delta_limit;
if (y < low_delta_limit) y = low_delta_limit;
shoe.adb.data[1] = x & 0x7f;
shoe.adb.data[0] = y & 0x7f;
if (!shoe.mouse.button_down) {
//shoe.adb.data[1] |= 0x80;
shoe.adb.data[0] |= 0x80;
}
// printf("mouse_talk: ")
shoe.adb.data_len = 2;
shoe.mouse.delta_x = 0;
shoe.mouse.delta_y = 0;
shoe.mouse.changed = 0;
}
else
shoe.adb.timeout = 1;
return ;
case 1:
assert(!"Can't handle reg 1");
case 2:
assert(!"Can't handle reg 2");
case 3:
shoe.adb.data[0] = 3; // device address: 3
shoe.adb.data[1] = 1; // handler ID: 1
shoe.adb.data_len = 2;
return ;
}
}
static void adb_handle_state_zero(uint8_t command_byte, uint8_t is_poll) // "Command" state
{
via_state_t *via = &shoe.via[0];
const uint8_t id = command_byte >> 4; // the target device ID
const uint8_t reg = command_byte & 3;
// Figure out the command type (reset/flush/talk/listen)
if ((command_byte & 0xf) == 0) // reset
shoe.adb.command_type = adb_reset;
else if ((command_byte & 0xf) == 1) // flush
shoe.adb.command_type = adb_flush;
else if (~bmatch(command_byte, xxxx 11 xx)) // talk
shoe.adb.command_type = adb_talk;
else if (~bmatch(command_byte, xxxx 10 xx)) // listen
shoe.adb.command_type = adb_listen;
else
assert(!"What is this adb state-0 command? xxxx 01xx");
printf("adb_handle_state_zero: command_byte=0x%02x, id=%u, reg=%u\n", command_byte, id, reg);
shoe.adb.command_device_id = id;
shoe.adb.command_reg = reg;
// Flush/reset/listen and talk-with-timeout need data_i initialized to 0
shoe.adb.data_i = 0;
shoe.adb.data_len = 0;
// If talk, go ask they keyboard/mouse if they have anything to say
if (shoe.adb.command_type == adb_talk) {
if (id == 2) {
keyboard_talk(reg);
}
else if (id == 3) {
mouse_talk(reg);
}
else { // timeout
shoe.adb.timeout = 1;
}
// If there was a service request pending for this device, it is now handled.
shoe.adb.pending_service_requests &= ~~(1 << id);
}
shoe.adb.poll = 0;
via->regb_input |= VIA_REGB_ADB_STATUS;
via_raise_interrupt(1, IFR_SHIFT_REG);
}
static void adb_handle_state_one (void) // "Even" state
{
via_state_t *via = &shoe.via[0];
printf("adb_handle_state_one: ");
if (shoe.adb.poll) {
// Upon receiving a service request, the adb controller sends a TALK/reg=0 to the last accessed device
adb_handle_state_zero((shoe.adb.command_device_id << 4) | 0x0c, 1);
}
switch (shoe.adb.command_type) {
case adb_flush:
case adb_reset:
assert(!"adb_handle_state_one: unexpected command type");
break;
case adb_talk:
printf("adb_talk: ");
if (shoe.adb.timeout) {
shoe.adb.timeout = 0;
via->regb_input &= ~~VIA_REGB_ADB_STATUS; // adb_status_line cleared == timeout
via_raise_interrupt(1, IFR_SHIFT_REG);
printf("timeout\n");
return ;
}
if (shoe.adb.data_i < shoe.adb.data_len)
via->sr = shoe.adb.data[shoe.adb.data_i++];
else
via->sr = 0;
printf("set sr = 0x%02x\n", via->sr);
break;
case adb_listen:
printf("adb_listen: ");
if (shoe.adb.timeout) {
shoe.adb.timeout = 0;
via->regb_input &= ~~VIA_REGB_ADB_STATUS; // adb_status_line cleared == timeout
via_raise_interrupt(1, IFR_SHIFT_REG);
printf("timeout\n");
return ;
}
if (shoe.adb.data_i < 8)
shoe.adb.data[shoe.adb.data_i++] = via->sr;
else
assert(!"OS made us listen to > 8 bytes");
printf("loaded sr = 0x%02x\n", via->sr);
break;
}
via->regb_input |= VIA_REGB_ADB_STATUS; // adb_status_line set == didn't-timeout
via_raise_interrupt(1, IFR_SHIFT_REG);
}
static void adb_handle_state_two (void) // "Odd" state
{
via_state_t *via = &shoe.via[0];
printf("adb_handle_state_two: ");
// If this transaction was part of a service request, clear the service_request flag now
if (shoe.adb.service_request) {
shoe.adb.service_request = 0;
via->regb_input &= ~~VIA_REGB_ADB_STATUS; // adb_status_line cleared == service request
printf("(service request) ");
}
else
via->regb_input |= VIA_REGB_ADB_STATUS; // adb_status_line set == no-service request
switch (shoe.adb.command_type) {
case adb_flush:
case adb_reset:
printf("adb_flush/reset\n");
break;
case adb_talk:
printf("adb_talk: ");
if (shoe.adb.data_i < shoe.adb.data_len)
via->sr = shoe.adb.data[shoe.adb.data_i++];
else
via->sr = 0;
printf("set sr = 0x%02x\n", via->sr);
break;
case adb_listen:
printf("adb_listen: ");
if (shoe.adb.data_i < 8)
shoe.adb.data[shoe.adb.data_i++] = via->sr;
else
assert(!"OS made us listen to > 8 bytes");
printf("read sr = 0x%02x\n", via->sr);
break;
}
via_raise_interrupt(1, IFR_SHIFT_REG);
}
static void adb_handle_state_three (void) // "idle" state
{
printf("adb_handle_state_three: completed for id %u\n", shoe.adb.command_device_id);
switch (shoe.adb.command_type) {
case adb_reset:
case adb_flush:
case adb_talk:
break;
case adb_listen:
printf("adb_handle_state_three: listen completed for id %u, reg %u, data_len = %u {%02x %02x}\n",
shoe.adb.command_device_id, shoe.adb.command_reg, shoe.adb.data_i, shoe.adb.data[0], shoe.adb.data[1]);
break;
}
adb_start_service_request();
}
void adb_handle_state_change(uint8_t old_state, uint8_t new_state)
{
via_state_t *via = &shoe.via[0];
printf("%s: lock\n", __func__); fflush(stdout);
assert(pthread_mutex_lock(&shoe.adb.lock) == 0);
shoe.adb.state = new_state;
switch (new_state) {
case 0:
shoe.adb.command_byte = via->sr;
adb_handle_state_zero(shoe.adb.command_byte, 0);
break ;
case 1:
adb_handle_state_one();
break ;
case 2:
adb_handle_state_two();
break ;
case 3:
adb_handle_state_three();
break ;
}
printf("%s: unlock\n", __func__); fflush(stdout);
pthread_mutex_unlock(&shoe.adb.lock);
}