blob: 3f865d7845744885dde2e352ce83d5352eaf2ae4 [file] [log] [blame]
// Copyright 2016 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 "usb-midi-sink.h"
#include <lib/sync/completion.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <threads.h>
#include <ddktl/fidl.h>
#include <fbl/auto_lock.h>
#include <usb/usb-request.h>
#include <usb/usb.h>
#include "midi.h"
#include "usb-audio.h"
constexpr size_t WRITE_REQ_COUNT = 20;
namespace audio {
namespace usb {
void UsbMidiSink::UpdateSignals() {
zx_signals_t new_signals = 0;
if (dead_) {
new_signals |= (DEV_STATE_WRITABLE | DEV_STATE_ERROR);
} else if (!free_write_reqs_.is_empty()) {
new_signals |= DEV_STATE_WRITABLE;
}
if (new_signals != signals_) {
ClearAndSetState(signals_ & ~new_signals, new_signals & ~signals_);
signals_ = new_signals;
}
}
void UsbMidiSink::WriteComplete(usb_request_t* req) {
if (req->response.status == ZX_ERR_IO_NOT_PRESENT) {
usb_request_release(req);
return;
}
fbl::AutoLock lock(&mutex_);
free_write_reqs_.push(UsbRequest(req, parent_req_size_));
sync_completion_signal(&free_write_completion_);
UpdateSignals();
}
void UsbMidiSink::DdkUnbind() {
fbl::AutoLock al(&mutex_);
dead_ = true;
UpdateSignals();
sync_completion_signal(&free_write_completion_);
DdkRemove();
}
void UsbMidiSink::DdkRelease() { delete this; }
zx_status_t UsbMidiSink::DdkOpen(zx_device_t** dev_out, uint32_t flags) {
zx_status_t result;
fbl::AutoLock lock(&mutex_);
if (open_) {
result = ZX_ERR_ALREADY_BOUND;
} else {
open_ = true;
result = ZX_OK;
}
return result;
}
zx_status_t UsbMidiSink::DdkClose(uint32_t flags) {
fbl::AutoLock lock(&mutex_);
open_ = false;
return ZX_OK;
}
zx_status_t UsbMidiSink::DdkWrite(const void* data, size_t length, zx_off_t offset,
size_t* actual) {
{
fbl::AutoLock al(&mutex_);
if (dead_) {
return ZX_ERR_IO_NOT_PRESENT;
}
}
zx_status_t status = ZX_OK;
size_t out_actual = length;
auto src = static_cast<const uint8_t*>(data);
while (length > 0) {
sync_completion_wait(&free_write_completion_, ZX_TIME_INFINITE);
{
fbl::AutoLock al(&mutex_);
if (dead_) {
return ZX_ERR_IO_NOT_PRESENT;
}
}
std::optional<UsbRequest> req;
{
fbl::AutoLock lock(&mutex_);
req = free_write_reqs_.pop();
if (free_write_reqs_.is_empty()) {
sync_completion_reset(&free_write_completion_);
}
}
if (!req) {
// shouldn't happen!
fbl::AutoLock al(&mutex_);
UpdateSignals();
return ZX_ERR_INTERNAL;
}
size_t message_length = get_midi_message_length(*src);
if (message_length < 1 || message_length > length)
return ZX_ERR_INVALID_ARGS;
uint8_t buffer[4];
buffer[0] = (src[0] & 0xF0) >> 4;
buffer[1] = src[0];
buffer[2] = (message_length > 1 ? src[1] : 0);
buffer[3] = (message_length > 2 ? src[2] : 0);
req->CopyTo(buffer, 4, 0);
req->request()->header.length = 4;
usb_request_complete_t complete = {
.callback = [](void* ctx,
usb_request_t* req) { static_cast<UsbMidiSink*>(ctx)->WriteComplete(req); },
.ctx = this,
};
usb_.RequestQueue(req->take(), &complete);
src += message_length;
length -= message_length;
}
fbl::AutoLock al(&mutex_);
UpdateSignals();
if (status == ZX_OK) {
*actual = out_actual;
}
return status;
}
void UsbMidiSink::GetDirection(GetDirectionCompleter::Sync completer) {
completer.Reply(llcpp::fuchsia::hardware::midi::Direction::SINK);
}
void UsbMidiSink::Read(uint64_t count, ReadCompleter::Sync completer) {
auto result = llcpp::fuchsia::hardware::midi::Device_Read_Result();
result.set_err(ZX_ERR_NOT_SUPPORTED);
completer.Reply(std::move(result));
}
void UsbMidiSink::Write(fidl::VectorView<uint8_t> data, WriteCompleter::Sync completer) {
const uint8_t* src = data.data();
auto length = data.count();
zx_status_t status = ZX_OK;
fbl::AutoLock al(&mutex_);
if (dead_) {
status = ZX_ERR_IO_NOT_PRESENT;
goto error;
}
while (length > 0) {
sync_completion_wait(&free_write_completion_, ZX_TIME_INFINITE);
if (dead_) {
status = ZX_ERR_IO_NOT_PRESENT;
goto error;
}
std::optional<UsbRequest> req;
req = free_write_reqs_.pop();
if (free_write_reqs_.is_empty()) {
sync_completion_reset(&free_write_completion_);
}
if (!req) {
// shouldn't happen!
status = ZX_ERR_INTERNAL;
goto error;
}
size_t message_length = get_midi_message_length(*src);
if (message_length < 1 || message_length > length) {
status = ZX_ERR_INVALID_ARGS;
goto error;
}
uint8_t buffer[4];
buffer[0] = (src[0] & 0xF0) >> 4;
buffer[1] = src[0];
buffer[2] = (message_length > 1 ? src[1] : 0);
buffer[3] = (message_length > 2 ? src[2] : 0);
req->CopyTo(buffer, 4, 0);
req->request()->header.length = 4;
usb_request_complete_t complete = {
.callback = [](void* ctx,
usb_request_t* req) { static_cast<UsbMidiSink*>(ctx)->WriteComplete(req); },
.ctx = this,
};
usb_.RequestQueue(req->take(), &complete);
src += message_length;
length -= message_length;
}
error:
UpdateSignals();
auto result = llcpp::fuchsia::hardware::midi::Device_Write_Result();
result.set_err(status);
completer.Reply(std::move(result));
}
zx_status_t UsbMidiSink::DdkMessage(fidl_msg_t* msg, fidl_txn_t* txn) {
DdkTransaction transaction(txn);
llcpp::fuchsia::hardware::midi::Device::Dispatch(this, msg, &transaction);
return transaction.Status();
}
zx_status_t UsbMidiSink::Create(zx_device_t* parent, const UsbDevice& usb, int index,
const usb_interface_descriptor_t* intf,
const usb_endpoint_descriptor_t* ep, const size_t req_size) {
auto dev = std::make_unique<UsbMidiSink>(parent, usb, req_size);
auto status = dev->Init(index, intf, ep);
if (status != ZX_OK) {
return status;
}
// devmgr is now in charge of the device.
[[maybe_unused]] auto* _ = dev.release();
return ZX_OK;
}
zx_status_t UsbMidiSink::Init(int index, const usb_interface_descriptor_t* intf,
const usb_endpoint_descriptor_t* ep) {
int packet_size = usb_ep_max_packet(ep);
if (intf->bAlternateSetting != 0) {
usb_.SetInterface(intf->bInterfaceNumber, intf->bAlternateSetting);
}
for (size_t i = 0; i < WRITE_REQ_COUNT; i++) {
std::optional<UsbRequest> req;
auto status =
UsbRequest::Alloc(&req, usb_ep_max_packet(ep), ep->bEndpointAddress, parent_req_size_);
if (status != ZX_OK) {
return status;
}
req->request()->header.length = packet_size;
free_write_reqs_.push(std::move(*req));
}
sync_completion_signal(&free_write_completion_);
char name[ZX_DEVICE_NAME_MAX];
snprintf(name, sizeof(name), "usb-midi-sink-%d", index);
return DdkAdd(name);
}
} // namespace usb
} // namespace audio