blob: 3c04181247560affd4660bc9f8cb834e7e004b3a [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "controller.h"
#include <lib/ddk/binding_driver.h>
#include <lib/ddk/driver.h>
#include <lib/ddk/hw/inout.h>
#include <lib/fit/defer.h>
#include <lib/zx/time.h>
#include "src/ui/input/drivers/pc-ps2/commands.h"
#include "src/ui/input/drivers/pc-ps2/device.h"
#include "src/ui/input/drivers/pc-ps2/registers.h"
#ifdef PS2_TEST
uint8_t TEST_inp(uint16_t port);
void TEST_outp(uint16_t port, uint8_t data);
#define inp TEST_inp
#define outp TEST_outp
#endif // PS2_TEST
namespace i8042 {
// Delay between checking status register for in/out buf full, in microseconds.
constexpr zx::duration kStatusPollDelay = zx::usec(10);
// Number of |kStatusPollDelayUs| to wait before giving up.
constexpr size_t kStatusPollTimeout = 500;
// Maximum number of bytes we need to read to flush the internal i8042 buffer.
constexpr size_t kMaxBufferLength = 32;
zx_status_t Controller::Bind(void *ctx, zx_device_t *parent) {
auto dev = std::make_unique<Controller>(parent);
zx_status_t status = dev->DdkAdd(ddk::DeviceAddArgs("i8042").set_flags(DEVICE_ADD_NON_BINDABLE));
if (status == ZX_OK) {
// The DDK will manage our memory.
[[maybe_unused]] auto unused = dev.release();
}
return status;
}
void Controller::DdkInit(ddk::InitTxn txn) {
init_thread_ = std::thread([this, txn = std::move(txn),
dispatcher = fdf_dispatcher_get_async_dispatcher(
fdf_dispatcher_get_current_dispatcher())]() mutable {
zx_status_t status;
auto cancel = fit::defer([&txn, &status]() {
zxlogf(ERROR, "init status: %s", zx_status_get_string(status));
txn.Reply(status);
});
if (get_ioport_resource(parent()) != ZX_HANDLE_INVALID) {
// TODO(simonshields): We should use ACPI to get these resources.
status = zx_ioports_request(get_ioport_resource(parent()), kCommandReg, 1);
if (status != ZX_OK) {
return;
}
status = zx_ioports_request(get_ioport_resource(parent()), kDataReg, 1);
if (status != ZX_OK) {
return;
}
}
// First, disable both devices, and flush to get the hardware back to a known-good state.
auto ctrl_status = SendControllerCommand(kCmdPort1Disable, {});
if (ctrl_status.is_error()) {
status = ctrl_status.status_value();
zxlogf(INFO, "Port 1 disable failed: %s", ctrl_status.status_string());
return;
}
ctrl_status = SendControllerCommand(kCmdPort2Disable, {});
if (ctrl_status.is_error()) {
zxlogf(INFO, "Port 2 disable failed: %s", ctrl_status.status_string());
status = ctrl_status.status_value();
return;
}
Flush();
auto cfg = SendControllerCommand(kCmdReadCtl, {});
if (cfg.is_error() || cfg->empty()) {
status = cfg.is_error() ? cfg.status_value() : ZX_ERR_IO;
zxlogf(INFO, "reading Control failed: %s", zx_status_get_string(status));
return;
}
ControlReg ctrl;
ctrl.set_reg_value(cfg.value()[0]);
if (ctrl.auxdis()) {
zxlogf(INFO, "Second port present!");
// We have a second port.
has_port2_ = true;
}
ctrl.set_kbdint(0).set_auxint(0).set_xlate(0);
ctrl_status =
SendControllerCommand(kCmdWriteCtl, cpp20::span<uint8_t>(ctrl.reg_value_ptr(), 1));
if (ctrl_status.is_error()) {
zxlogf(INFO, "Writing control failed: %s", zx_status_get_string(status));
status = ctrl_status.status_value();
return;
}
auto test_result = SendControllerCommand(kCmdSelfTest, {});
if (test_result.is_error() || test_result->empty()) {
status = test_result.is_error() ? test_result.status_value() : ZX_ERR_IO;
zxlogf(INFO, "Send self-test failed: %s", zx_status_get_string(status));
return;
}
uint8_t data = test_result.value()[0];
if (data != 0x55) {
zxlogf(ERROR, "Controller self-test failed: 0x%0x", data);
status = ZX_ERR_INTERNAL;
return;
}
test_result = SendControllerCommand(kCmdPort1Test, {});
if (test_result.is_error() || test_result->empty()) {
status = test_result.is_error() ? test_result.status_value() : ZX_ERR_IO;
zxlogf(INFO, "Send port 1 self-test failed: %s", zx_status_get_string(status));
return;
}
data = test_result.value()[0];
if (data != 0x00) {
zxlogf(ERROR, "Port 1 self-test failed: 0x%0x", data);
status = ZX_ERR_INTERNAL;
return;
}
if (has_port2_) {
test_result = SendControllerCommand(kCmdPort2Test, {});
if (test_result.is_error() || test_result->empty()) {
status = test_result.is_error() ? test_result.status_value() : ZX_ERR_IO;
zxlogf(INFO, "Send port 2 self-test failed: %s, disabling", zx_status_get_string(status));
has_port2_ = false;
}
}
if (has_port2_) {
data = test_result.value()[0];
if (data != 0x00) {
zxlogf(ERROR, "Port 2 self-test failed: 0x%0x", data);
has_port2_ = false;
}
}
// Turn on translation, and re-enable the devices.
ctrl.set_xlate(true);
ctrl.set_kbddis(false).set_kbdint(true);
if (has_port2_) {
ctrl.set_auxdis(false).set_auxint(true);
}
ctrl_status =
SendControllerCommand(kCmdWriteCtl, cpp20::span<uint8_t>(ctrl.reg_value_ptr(), 1));
if (ctrl_status.is_error()) {
status = ctrl_status.status_value();
zxlogf(INFO, "Re-enabling devices failed: %s", zx_status_get_string(status));
return;
}
cancel.cancel();
// Failure here won't fail everything else.
zx_status_t bind_status = I8042Device::Bind(this, dispatcher, Port::kPort1);
if (bind_status != ZX_OK) {
zxlogf(WARNING, "Failed to bind Port 1: %s", zx_status_get_string(bind_status));
}
if (has_port2_) {
bind_status = I8042Device::Bind(this, dispatcher, Port::kPort2);
if (bind_status != ZX_OK) {
zxlogf(WARNING, "Failed to bind Port 2: %s", zx_status_get_string(bind_status));
}
}
sync_completion_signal(&added_children_);
txn.Reply(ZX_OK);
});
}
void Controller::DdkUnbind(ddk::UnbindTxn txn) {
JoinInitThread();
txn.Reply();
}
void Controller::DdkSuspend(ddk::SuspendTxn txn) {
JoinInitThread();
txn.Reply(ZX_OK, txn.requested_state());
}
zx::result<std::vector<uint8_t>> Controller::SendControllerCommand(
Command command, cpp20::span<const uint8_t> data) {
if (data.size() != command.param_count) {
zxlogf(ERROR, "%s: Wrong parameter count: wanted %u, got %zu", __func__, command.param_count,
data.size());
return zx::error(ZX_ERR_INVALID_ARGS);
}
if (!WaitWrite()) {
return zx::error(ZX_ERR_TIMED_OUT);
}
outp(kCommandReg, command.cmd);
// Write parameters.
for (size_t i = 0; i < command.param_count; i++) {
if (!WaitWrite()) {
return zx::error(ZX_ERR_TIMED_OUT);
}
outp(kDataReg, data[i]);
}
// Read back result.
std::vector<uint8_t> ret;
ret.reserve(command.response_count);
for (size_t i = 0; i < command.response_count; i++) {
if (!WaitRead()) {
zxlogf(INFO, "%s: timeout reading response, got %zu bytes", __func__, i);
break;
}
ret.emplace_back(ReadData());
}
return zx::ok(std::move(ret));
}
zx::result<std::vector<uint8_t>> Controller::SendDeviceCommand(Command command, Port port) {
if (command.param_count != 0) {
zxlogf(ERROR, "Sending parameters to device not supported.");
return zx::error(ZX_ERR_NOT_SUPPORTED);
}
// When writing to port 2, we need to tell the controller we're addressing it.
if (port == Port::kPort2) {
if (!WaitWrite()) {
return zx::error(ZX_ERR_TIMED_OUT);
}
outp(kCommandReg, kCmdWriteAux.cmd);
}
if (!WaitWrite()) {
return zx::error(ZX_ERR_TIMED_OUT);
}
outp(kDataReg, command.cmd);
std::vector<uint8_t> ret;
ret.reserve(command.response_count);
for (size_t i = 0; i < command.response_count; i++) {
if (!WaitRead()) {
zxlogf(DEBUG, "Read failed, got %zu bytes", i);
break;
}
ret.emplace_back(ReadData());
}
return zx::ok(ret);
}
StatusReg Controller::ReadStatus() {
StatusReg reg;
uint8_t data = inp(kStatusReg);
reg.set_reg_value(data);
return reg;
}
uint8_t Controller::ReadData() { return inp(kDataReg); }
void Controller::JoinInitThread() {
if (init_thread_.joinable()) {
init_thread_.join();
}
}
bool Controller::WaitWrite() {
size_t i = 0;
while (ReadStatus().ibf() && i < kStatusPollTimeout) {
zx::nanosleep(zx::deadline_after(kStatusPollDelay));
i++;
}
return i != kStatusPollTimeout;
}
bool Controller::WaitRead() {
size_t i = 0;
while (!ReadStatus().obf() && i < kStatusPollTimeout) {
zx::nanosleep(zx::deadline_after(kStatusPollDelay));
i++;
}
return i != kStatusPollTimeout;
}
void Controller::Flush() {
size_t i = 0;
while (ReadStatus().obf() && i < kMaxBufferLength) {
ReadData();
i++;
usleep(10);
}
}
} // namespace i8042
static zx_driver_ops_t i8042_driver_ops = {
.version = DRIVER_OPS_VERSION,
.bind = i8042::Controller::Bind,
};
ZIRCON_DRIVER(i8042, i8042_driver_ops, "zircon", "0.1");