blob: 518d306ff1d6ec5cf840f406faad614fc42d399c [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 <ddk/device.h>
#include <ddk/driver.h>
#include <ddk/protocol/usb-old.h>
#include <ddk/usb/usb.h>
#include <usb/usb-request.h>
#include <zircon/device/midi.h>
#include <lib/sync/completion.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <threads.h>
#include "midi.h"
#include "usb-audio.h"
#define WRITE_REQ_COUNT 20
typedef struct {
zx_device_t* mxdev;
zx_device_t* usb_mxdev;
usb_protocol_t usb;
// pool of free USB requests
list_node_t free_write_reqs;
// mutex for synchronizing access to free_write_reqs and open
mtx_t mutex;
// completion signals free_write_reqs not empty
sync_completion_t free_write_completion;
bool open;
bool dead;
// the last signals we reported
zx_signals_t signals;
uint64_t parent_req_size;
} usb_midi_sink_t;
static void update_signals(usb_midi_sink_t* sink) {
zx_signals_t new_signals = 0;
if (sink->dead) {
new_signals |= (DEV_STATE_WRITABLE | DEV_STATE_ERROR);
} else if (!list_is_empty(&sink->free_write_reqs)) {
new_signals |= DEV_STATE_WRITABLE;
}
if (new_signals != sink->signals) {
device_state_clr_set(sink->mxdev,
sink->signals & ~new_signals,
new_signals & ~sink->signals);
sink->signals = new_signals;
}
}
static void usb_midi_sink_write_complete(usb_request_t* req, void* cookie) {
usb_midi_sink_t* sink = (usb_midi_sink_t*)cookie;
if (req->response.status == ZX_ERR_IO_NOT_PRESENT) {
usb_request_release(req);
return;
}
// FIXME what to do with error here?
mtx_lock(&sink->mutex);
zx_status_t status = usb_req_list_add_tail(&sink->free_write_reqs, req, sink->parent_req_size);
ZX_DEBUG_ASSERT(status == ZX_OK);
sync_completion_signal(&sink->free_write_completion);
update_signals(sink);
mtx_unlock(&sink->mutex);
}
static void usb_midi_sink_unbind(void* ctx) {
usb_midi_sink_t* sink = ctx;
sink->dead = true;
update_signals(sink);
sync_completion_signal(&sink->free_write_completion);
device_remove(sink->mxdev);
}
static void usb_midi_sink_free(usb_midi_sink_t* sink) {
usb_request_t* req;
while ((req = usb_req_list_remove_head(&sink->free_write_reqs, sink->parent_req_size))
!= NULL) {
usb_request_release(req);
}
free(sink);
}
static void usb_midi_sink_release(void* ctx) {
usb_midi_sink_t* sink = ctx;
usb_midi_sink_free(sink);
}
static zx_status_t usb_midi_sink_open(void* ctx, zx_device_t** dev_out, uint32_t flags) {
usb_midi_sink_t* sink = ctx;
zx_status_t result;
mtx_lock(&sink->mutex);
if (sink->open) {
result = ZX_ERR_ALREADY_BOUND;
} else {
sink->open = true;
result = ZX_OK;
}
mtx_unlock(&sink->mutex);
return result;
}
static zx_status_t usb_midi_sink_close(void* ctx, uint32_t flags) {
usb_midi_sink_t* sink = ctx;
mtx_lock(&sink->mutex);
sink->open = false;
mtx_unlock(&sink->mutex);
return ZX_OK;
}
static zx_status_t usb_midi_sink_write(void* ctx, const void* data, size_t length,
zx_off_t offset, size_t* actual) {
usb_midi_sink_t* sink = ctx;
if (sink->dead) {
return ZX_ERR_IO_NOT_PRESENT;
}
zx_status_t status = ZX_OK;
size_t out_actual = length;
const uint8_t* src = (uint8_t *)data;
while (length > 0) {
sync_completion_wait(&sink->free_write_completion, ZX_TIME_INFINITE);
if (sink->dead) {
return ZX_ERR_IO_NOT_PRESENT;
}
mtx_lock(&sink->mutex);
list_node_t* node = list_remove_head(&sink->free_write_reqs);
if (list_is_empty(&sink->free_write_reqs)) {
sync_completion_reset(&sink->free_write_completion);
}
mtx_unlock(&sink->mutex);
if (!node) {
// shouldn't happen!
status = ZX_ERR_INTERNAL;
goto out;
}
usb_req_internal_t* req_int = containerof(node, usb_req_internal_t, node);
usb_request_t* req = REQ_INTERNAL_TO_USB_REQ(req_int, sink->parent_req_size);
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);
usb_request_copy_to(req, buffer, 4, 0);
req->header.length = 4;
usb_request_queue(&sink->usb, req, usb_midi_sink_write_complete, sink);
src += message_length;
length -= message_length;
}
out:
update_signals(sink);
if (status == ZX_OK) {
*actual = out_actual;
}
return status;
}
static zx_status_t usb_midi_sink_ioctl(void* ctx, uint32_t op, const void* in_buf,
size_t in_len, void* out_buf, size_t out_len,
size_t* out_actual) {
switch (op) {
case IOCTL_MIDI_GET_DEVICE_TYPE: {
int* reply = out_buf;
if (out_len < sizeof(*reply)) return ZX_ERR_BUFFER_TOO_SMALL;
*reply = MIDI_TYPE_SINK;
*out_actual = sizeof(*reply);
return ZX_OK;
}
}
return ZX_ERR_NOT_SUPPORTED;
}
static zx_protocol_device_t usb_midi_sink_device_proto = {
.version = DEVICE_OPS_VERSION,
.unbind = usb_midi_sink_unbind,
.release = usb_midi_sink_release,
.open = usb_midi_sink_open,
.close = usb_midi_sink_close,
.write = usb_midi_sink_write,
.ioctl = usb_midi_sink_ioctl,
};
zx_status_t usb_midi_sink_create(zx_device_t* device, usb_protocol_t* usb, int index,
const usb_interface_descriptor_t* intf,
const usb_endpoint_descriptor_t* ep,
const size_t parent_req_size) {
usb_midi_sink_t* sink = calloc(1, sizeof(usb_midi_sink_t));
if (!sink) {
printf("Not enough memory for usb_midi_sink_t\n");
return ZX_ERR_NO_MEMORY;
}
list_initialize(&sink->free_write_reqs);
sink->usb_mxdev = device;
memcpy(&sink->usb, usb, sizeof(sink->usb));
int packet_size = usb_ep_max_packet(ep);
if (intf->bAlternateSetting != 0) {
usb_set_interface(usb, intf->bInterfaceNumber, intf->bAlternateSetting);
}
sink->parent_req_size = parent_req_size;
for (int i = 0; i < WRITE_REQ_COUNT; i++) {
usb_request_t* req;
zx_status_t status = usb_request_alloc(&req, usb_ep_max_packet(ep), ep->bEndpointAddress,
parent_req_size + sizeof(usb_req_internal_t));
if (status != ZX_OK) {
usb_midi_sink_free(sink);
return ZX_ERR_NO_MEMORY;
}
req->header.length = packet_size;
status = usb_req_list_add_head(&sink->free_write_reqs, req, parent_req_size);
ZX_DEBUG_ASSERT(status == ZX_OK);
}
sync_completion_signal(&sink->free_write_completion);
char name[ZX_DEVICE_NAME_MAX];
snprintf(name, sizeof(name), "usb-midi-sink-%d", index);
device_add_args_t args = {
.version = DEVICE_ADD_ARGS_VERSION,
.name = name,
.ctx = sink,
.ops = &usb_midi_sink_device_proto,
.proto_id = ZX_PROTOCOL_MIDI,
};
zx_status_t status = device_add(device, &args, &sink->mxdev);
if (status != ZX_OK) {
printf("device_add failed in usb_midi_sink_create\n");
usb_midi_sink_free(sink);
}
return status;
}