| // Copyright 2018 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 "serial.h" |
| |
| #include <fuchsia/hardware/serial/llcpp/fidl.h> |
| #include <lib/fidl-utils/bind.h> |
| #include <lib/zx/handle.h> |
| #include <lib/zx/time.h> |
| #include <zircon/status.h> |
| #include <zircon/threads.h> |
| |
| #include <memory> |
| |
| #include <ddk/debug.h> |
| #include <fbl/auto_lock.h> |
| #include <fbl/function.h> |
| |
| #include "src/devices/serial/drivers/serial/serial_bind.h" |
| |
| namespace serial { |
| |
| namespace fuchsia = ::llcpp::fuchsia; |
| |
| enum { |
| WAIT_ITEM_SOCKET, |
| WAIT_ITEM_EVENT, |
| }; |
| |
| namespace { |
| |
| constexpr serial_notify_t kNoCallback = {nullptr, nullptr}; |
| |
| constexpr size_t kUartBufferSize = 1024; |
| |
| constexpr zx_signals_t kEventReadableSignal = ZX_USER_SIGNAL_0; |
| constexpr zx_signals_t kEventWritableSignal = ZX_USER_SIGNAL_1; |
| constexpr zx_signals_t kEventCancelSignal = ZX_USER_SIGNAL_2; |
| |
| } // namespace |
| |
| // This thread handles data transfer in both directions |
| zx_status_t SerialDevice::WorkerThread() { |
| uint8_t in_buffer[kUartBufferSize]; |
| uint8_t out_buffer[kUartBufferSize]; |
| size_t in_buffer_offset = 0; // offset of first byte in in_buffer (if any) |
| size_t out_buffer_offset = 0; // offset of first byte in out_buffer (if any) |
| size_t in_buffer_count = 0; // number of bytes in in_buffer |
| size_t out_buffer_count = 0; // number of bytes in out_buffer |
| zx_wait_item_t items[2]; |
| |
| items[WAIT_ITEM_SOCKET].handle = socket_.get(); |
| items[WAIT_ITEM_EVENT].handle = event_.get(); |
| bool peer_closed = false; |
| |
| // loop until client socket is closed and we have no more data to write |
| while (!peer_closed || out_buffer_count > 0) { |
| // attempt pending socket write |
| if (in_buffer_count > 0) { |
| size_t actual; |
| zx_status_t status = socket_.write(0, in_buffer + in_buffer_offset, in_buffer_count, &actual); |
| if (status == ZX_OK) { |
| in_buffer_count -= actual; |
| if (in_buffer_count > 0) { |
| in_buffer_offset += actual; |
| } else { |
| in_buffer_offset = 0; |
| } |
| } else if (status != ZX_ERR_SHOULD_WAIT && status != ZX_ERR_PEER_CLOSED) { |
| zxlogf(ERROR, "platform_serial_thread: zx::socket::write returned %s", |
| zx_status_get_string(status)); |
| break; |
| } |
| } |
| |
| // attempt pending serial write |
| if (out_buffer_count > 0) { |
| size_t actual; |
| zx_status_t status = serial_.Write(out_buffer + out_buffer_offset, out_buffer_count, &actual); |
| if (status == ZX_OK) { |
| out_buffer_count -= actual; |
| if (out_buffer_count > 0) { |
| out_buffer_offset += actual; |
| } else { |
| // out_buffer empty now, reset to beginning |
| out_buffer_offset = 0; |
| } |
| } else if (status != ZX_ERR_SHOULD_WAIT && status != ZX_ERR_PEER_CLOSED) { |
| zxlogf(ERROR, "platform_serial_thread: serial_impl_write returned %s", |
| zx_status_get_string(status)); |
| break; |
| } |
| } |
| |
| // wait for serial or socket to be readable |
| items[WAIT_ITEM_SOCKET].waitfor = ZX_SOCKET_READABLE | ZX_SOCKET_PEER_CLOSED; |
| items[WAIT_ITEM_EVENT].waitfor = kEventReadableSignal | kEventCancelSignal; |
| // also wait for writability if we have pending data to write |
| if (in_buffer_count > 0) { |
| items[WAIT_ITEM_SOCKET].waitfor |= ZX_SOCKET_WRITABLE; |
| } |
| if (out_buffer_count > 0) { |
| items[WAIT_ITEM_EVENT].waitfor |= kEventWritableSignal; |
| } |
| |
| zx_status_t status = zx::handle::wait_many(items, countof(items), zx::time::infinite()); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "platform_serial_thread: zx_object_wait_many returned %s", |
| zx_status_get_string(status)); |
| break; |
| } |
| |
| if (items[WAIT_ITEM_EVENT].pending & kEventCancelSignal) { |
| break; |
| } |
| |
| if (items[WAIT_ITEM_EVENT].pending & kEventReadableSignal) { |
| size_t length; |
| size_t buffer_read_start_idx = in_buffer_offset + in_buffer_count; |
| status = serial_.Read(in_buffer + buffer_read_start_idx, |
| sizeof(in_buffer) - buffer_read_start_idx, &length); |
| |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "platform_serial_thread: serial_impl_read returned %s", |
| zx_status_get_string(status)); |
| break; |
| } |
| in_buffer_count += length; |
| } |
| |
| if (items[WAIT_ITEM_SOCKET].pending & ZX_SOCKET_READABLE) { |
| size_t length; |
| size_t buffer_read_start_idx = out_buffer_offset + out_buffer_count; |
| status = socket_.read(0, out_buffer + buffer_read_start_idx, |
| sizeof(out_buffer) - buffer_read_start_idx, &length); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "serial_out_thread: zx::socket::read returned %s", |
| zx_status_get_string(status)); |
| break; |
| } |
| out_buffer_count += length; |
| } |
| if (items[WAIT_ITEM_SOCKET].pending & ZX_SOCKET_PEER_CLOSED) { |
| peer_closed = true; |
| } |
| } |
| |
| serial_.Enable(false); |
| serial_.SetNotifyCallback(&kNoCallback); |
| |
| event_.reset(); |
| socket_.reset(); |
| |
| fbl::AutoLock al(&lock_); |
| open_ = false; |
| |
| return 0; |
| } |
| |
| void SerialDevice::StateCallback(serial_state_t state) { |
| // update our event handle signals with latest state from the serial driver |
| zx_signals_t event_set = 0; |
| zx_signals_t event_clear = 0; |
| zx_signals_t device_set = 0; |
| zx_signals_t device_clear = 0; |
| |
| if (state & SERIAL_STATE_READABLE) { |
| event_set |= kEventReadableSignal; |
| device_set |= DEV_STATE_READABLE; |
| } else { |
| event_clear |= kEventReadableSignal; |
| device_clear |= DEV_STATE_READABLE; |
| } |
| if (state & SERIAL_STATE_WRITABLE) { |
| event_set |= kEventWritableSignal; |
| device_set |= DEV_STATE_WRITABLE; |
| } else { |
| event_clear |= kEventWritableSignal; |
| device_clear |= DEV_STATE_WRITABLE; |
| } |
| |
| if (socket_ != ZX_HANDLE_INVALID) { |
| // another driver bound to us |
| event_.signal(event_clear, event_set); |
| } else { |
| // someone opened us via /dev file system |
| ClearAndSetState(device_clear, device_set); |
| } |
| } |
| |
| zx_status_t SerialDevice::SerialGetInfo(serial_port_info_t* info) { return serial_.GetInfo(info); } |
| |
| zx_status_t SerialDevice::SerialConfig(uint32_t baud_rate, uint32_t flags) { |
| return serial_.Config(baud_rate, flags); |
| } |
| |
| zx_status_t SerialDevice::SerialOpenSocket(zx::socket* out_handle) { |
| fbl::AutoLock al(&lock_); |
| if (open_) { |
| return ZX_ERR_ALREADY_BOUND; |
| } |
| |
| zx::socket socket(ZX_HANDLE_INVALID); |
| zx_status_t status = zx::socket::create(ZX_SOCKET_STREAM, &socket_, &socket); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| status = zx::event::create(0, &event_); |
| if (status != ZX_OK) { |
| socket.reset(); |
| return status; |
| } |
| |
| const serial_notify_t callback = {[](void* arg, serial_state_t state) { |
| static_cast<SerialDevice*>(arg)->StateCallback(state); |
| }, |
| this}; |
| serial_.SetNotifyCallback(&callback); |
| |
| status = serial_.Enable(true); |
| if (status != ZX_OK) { |
| socket.reset(); |
| return status; |
| } |
| |
| int thrd_rc = thrd_create_with_name( |
| &thread_, [](void* arg) { return static_cast<SerialDevice*>(arg)->WorkerThread(); }, this, |
| "platform_serial_thread"); |
| if (thrd_rc != thrd_success) { |
| socket.reset(); |
| return thrd_status_to_zx_status(thrd_rc); |
| } |
| |
| *out_handle = std::move(socket); |
| open_ = true; |
| return ZX_OK; |
| } |
| |
| zx_status_t SerialDevice::DdkOpen(zx_device_t** dev_out, uint32_t flags) { |
| fbl::AutoLock al(&lock_); |
| |
| if (open_) { |
| return ZX_ERR_ALREADY_BOUND; |
| } |
| |
| const serial_notify_t callback = {[](void* arg, serial_state_t state) { |
| static_cast<SerialDevice*>(arg)->StateCallback(state); |
| }, |
| this}; |
| serial_.SetNotifyCallback(&callback); |
| |
| zx_status_t status = serial_.Enable(true); |
| if (status == ZX_OK) { |
| open_ = true; |
| } |
| |
| return status; |
| } |
| |
| zx_status_t SerialDevice::DdkClose(uint32_t flags) { |
| fbl::AutoLock al(&lock_); |
| |
| if (open_) { |
| serial_.Enable(false); |
| serial_.SetNotifyCallback(&kNoCallback); |
| open_ = false; |
| return ZX_OK; |
| } else { |
| zxlogf(ERROR, "SerialDevice::DdkClose called when not open"); |
| return ZX_ERR_BAD_STATE; |
| } |
| } |
| |
| zx_status_t SerialDevice::DdkRead(void* buf, size_t count, zx_off_t off, size_t* actual) { |
| fbl::AutoLock al(&lock_); |
| |
| if (!open_) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| return serial_.Read(buf, count, actual); |
| } |
| |
| zx_status_t SerialDevice::DdkWrite(const void* buf, size_t count, zx_off_t off, size_t* actual) { |
| fbl::AutoLock al(&lock_); |
| |
| if (!open_) { |
| return ZX_ERR_BAD_STATE; |
| } |
| |
| return serial_.Write(buf, count, actual); |
| } |
| |
| void SerialDevice::GetClass(GetClassCompleter::Sync& completer) { |
| completer.Reply(static_cast<fuchsia::hardware::serial::Class>(serial_class_)); |
| } |
| |
| void SerialDevice::SetConfig(fuchsia::hardware::serial::Config config, |
| SetConfigCompleter::Sync& completer) { |
| using fuchsia::hardware::serial::CharacterWidth; |
| using fuchsia::hardware::serial::FlowControl; |
| using fuchsia::hardware::serial::Parity; |
| using fuchsia::hardware::serial::StopWidth; |
| uint32_t flags = 0; |
| switch (config.character_width) { |
| case CharacterWidth::BITS_5: |
| flags |= SERIAL_DATA_BITS_5; |
| break; |
| case CharacterWidth::BITS_6: |
| flags |= SERIAL_DATA_BITS_6; |
| break; |
| case CharacterWidth::BITS_7: |
| flags |= SERIAL_DATA_BITS_7; |
| break; |
| case CharacterWidth::BITS_8: |
| flags |= SERIAL_DATA_BITS_8; |
| break; |
| } |
| |
| switch (config.stop_width) { |
| case StopWidth::BITS_1: |
| flags |= SERIAL_STOP_BITS_1; |
| break; |
| case StopWidth::BITS_2: |
| flags |= SERIAL_STOP_BITS_2; |
| break; |
| } |
| |
| switch (config.parity) { |
| case Parity::NONE: |
| flags |= SERIAL_PARITY_NONE; |
| break; |
| case Parity::EVEN: |
| flags |= SERIAL_PARITY_EVEN; |
| break; |
| case Parity::ODD: |
| flags |= SERIAL_PARITY_ODD; |
| break; |
| } |
| |
| switch (config.control_flow) { |
| case FlowControl::NONE: |
| flags |= SERIAL_FLOW_CTRL_NONE; |
| break; |
| case FlowControl::CTS_RTS: |
| flags |= SERIAL_FLOW_CTRL_CTS_RTS; |
| break; |
| } |
| |
| zx_status_t status = SerialConfig(config.baud_rate, flags); |
| completer.Reply(status); |
| } |
| |
| zx_status_t SerialDevice::DdkMessage(fidl_incoming_msg_t* msg, fidl_txn_t* txn) { |
| DdkTransaction transaction(txn); |
| fuchsia::hardware::serial::Device::Dispatch(this, msg, &transaction); |
| return transaction.Status(); |
| } |
| |
| void SerialDevice::DdkRelease() { |
| serial_.Enable(false); |
| serial_.SetNotifyCallback(&kNoCallback); |
| |
| // Clear all read/write signals, cancel the thread. |
| if (socket_ != ZX_HANDLE_INVALID) { |
| event_.signal(kEventReadableSignal | kEventWritableSignal, kEventCancelSignal); |
| thrd_join(thread_, nullptr); |
| } |
| |
| event_.reset(); |
| socket_.reset(); |
| delete this; |
| } |
| |
| zx_status_t SerialDevice::Create(void* ctx, zx_device_t* dev) { |
| fbl::AllocChecker ac; |
| std::unique_ptr<SerialDevice> sdev(new (&ac) SerialDevice(dev)); |
| |
| if (!ac.check()) { |
| zxlogf(ERROR, "SerialDevice::Create: no memory to allocate serial device!"); |
| return ZX_ERR_NO_MEMORY; |
| } |
| |
| zx_status_t status; |
| if ((status = sdev->Init()) != ZX_OK) { |
| return status; |
| } |
| |
| if ((status = sdev->Bind()) != ZX_OK) { |
| zxlogf(ERROR, "SerialDevice::Create: Bind failed"); |
| sdev.release()->DdkRelease(); |
| return status; |
| } |
| |
| // devmgr is now in charge of the device. |
| __UNUSED auto* dummy = sdev.release(); |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t SerialDevice::Init() { |
| if (!serial_.is_valid()) { |
| zxlogf(ERROR, "SerialDevice::Init: ZX_PROTOCOL_SERIAL_IMPL not available"); |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| |
| serial_port_info_t info; |
| zx_status_t status = serial_.GetInfo(&info); |
| if (status != ZX_OK) { |
| zxlogf(ERROR, "SerialDevice::Init: SerialImpl::GetInfo failed %d", status); |
| return status; |
| } |
| serial_class_ = info.serial_class; |
| |
| return ZX_OK; |
| } |
| |
| zx_status_t SerialDevice::Bind() { |
| zx_device_prop_t props[] = { |
| {BIND_PROTOCOL, 0, ZX_PROTOCOL_SERIAL}, |
| {BIND_SERIAL_CLASS, 0, serial_class_}, |
| }; |
| |
| return DdkAdd(ddk::DeviceAddArgs("serial").set_props(props)); |
| } |
| |
| static constexpr zx_driver_ops_t serial_driver_ops = []() { |
| zx_driver_ops_t ops = {}; |
| ops.version = DRIVER_OPS_VERSION; |
| ops.bind = SerialDevice::Create; |
| return ops; |
| }(); |
| |
| } // namespace serial |
| |
| ZIRCON_DRIVER(serial, serial::serial_driver_ops, "zircon", "0.1"); |