blob: 5d6b49a2b84107b85f0786a34c46e568932b895f [file] [log] [blame]
// 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 "garnet/bin/guest/vmm/device/virtio_magma.h"
#include <fcntl.h>
#include <unistd.h>
#include <lib/fxl/logging.h>
#include "garnet/bin/guest/vmm/device/virtio_queue.h"
zx_status_t VirtioMagma::Init(std::string device_path) {
device_path_ = device_path;
device_fd_ = fbl::unique_fd(open(device_path_.c_str(), O_RDONLY));
if (!device_fd_.is_valid()) {
FXL_LOG(ERROR) << "Failed do open device at " << device_path_ << ": "
<< strerror(errno);
return ZX_ERR_INTERNAL;
}
return ZX_OK;
}
VirtioMagma::~VirtioMagma() {
// TODO: flush and close all host magma connections
}
#define COMMAND_CASE(cmd_suffix, struct_prefix, method_name, used) \
case VIRTIO_MAGMA_CMD_##cmd_suffix: { \
auto request = reinterpret_cast<const virtio_magma_##struct_prefix##_t*>( \
request_desc.addr); \
auto response = reinterpret_cast<virtio_magma_##struct_prefix##_resp_t*>( \
response_desc.addr); \
if (response_desc.len >= sizeof(*response)) { \
method_name(request, response); \
(used) = sizeof(*response); \
} else { \
FXL_LOG(ERROR) << "MAGMA command " \
<< "(" << command_type << ") " \
<< "response descriptor too small"; \
} \
} break
void VirtioMagma::HandleCommand(VirtioChain* chain) {
VirtioDescriptor request_desc;
if (!chain->NextDescriptor(&request_desc)) {
FXL_LOG(ERROR) << "Failed to read request descriptor";
return;
}
const auto request_header =
reinterpret_cast<virtio_magma_ctrl_hdr_t*>(request_desc.addr);
const uint32_t command_type = request_header->type;
if (chain->HasDescriptor()) {
VirtioDescriptor response_desc{};
if (!chain->NextDescriptor(&response_desc)) {
FXL_LOG(ERROR) << "Failed to read descriptor";
return;
}
if (response_desc.writable) {
switch (command_type) {
COMMAND_CASE(QUERY, query, Query, *chain->Used());
COMMAND_CASE(CREATE_CONNECTION, create_connection, CreateConnection,
*chain->Used());
COMMAND_CASE(RELEASE_CONNECTION, release_connection, ReleaseConnection,
*chain->Used());
default: {
FXL_LOG(ERROR) << "Unsupported MAGMA command "
<< "(" << command_type << ")";
auto response =
reinterpret_cast<virtio_magma_ctrl_hdr_t*>(response_desc.addr);
response->type = VIRTIO_MAGMA_RESP_ERR_INVALID_COMMAND;
*chain->Used() = sizeof(*response);
} break;
}
} else {
FXL_LOG(ERROR) << "MAGMA command "
<< "(" << command_type << ") "
<< "response descriptor is not writable";
}
} else {
FXL_LOG(ERROR) << "MAGMA command "
<< "(" << command_type << ") "
<< "does not contain a response descriptor";
}
chain->Return();
}
void VirtioMagma::OnCommandAvailable() {
VirtioChain chain;
while (out_queue_->NextChain(&chain)) {
HandleCommand(&chain);
}
}
void VirtioMagma::OnQueueReady() {}
void VirtioMagma::Query(const virtio_magma_query_t* request,
virtio_magma_query_resp_t* response) {
FXL_DCHECK(request->hdr.type == VIRTIO_MAGMA_CMD_QUERY);
if (request->field_id == MAGMA_QUERY_DEVICE_ID ||
request->field_id >= MAGMA_QUERY_VENDOR_PARAM_0) {
uint64_t field_value_out = 0;
magma_status_t status =
magma_query(device_fd_.get(), request->field_id, &field_value_out);
response->hdr.type = VIRTIO_MAGMA_RESP_QUERY;
response->field_value_out = field_value_out;
response->status_return = status;
} else {
response->hdr.type = VIRTIO_MAGMA_RESP_ERR_INVALID_ARGUMENT;
}
}
void VirtioMagma::CreateConnection(
const virtio_magma_create_connection_t* request,
virtio_magma_create_connection_resp_t* response) {
FXL_DCHECK(request->hdr.type == VIRTIO_MAGMA_CMD_CREATE_CONNECTION);
magma_connection_t connection;
magma_status_t status =
magma_create_connection(device_fd_.get(), &connection);
if (status != MAGMA_STATUS_OK) {
response->connection_return = -1;
response->hdr.type = VIRTIO_MAGMA_RESP_ERR_HOST_DISCONNECTED;
return;
}
FXL_LOG(INFO) << "magma connection created (" << next_connection_id_ << ")";
connections_.insert({next_connection_id_, connection});
response->connection_return = next_connection_id_++;
response->hdr.type = VIRTIO_MAGMA_RESP_CREATE_CONNECTION;
}
void VirtioMagma::ReleaseConnection(
const virtio_magma_release_connection_t* request,
virtio_magma_release_connection_resp_t* response) {
FXL_DCHECK(request->hdr.type == VIRTIO_MAGMA_CMD_RELEASE_CONNECTION);
auto connection = connections_.find(request->connection);
if (connection == connections_.end()) {
FXL_LOG(ERROR) << "invalid connection (" << request->connection << ")";
return;
}
FXL_LOG(INFO) << "magma connection released (" << request->connection << ")";
connections_.erase(connection);
}