mirror of
https://github.com/genodelabs/genode.git
synced 2025-04-08 20:05:54 +00:00
os: Add support for reading VirtIO responses.
Some more advanced devices like VirtIO GPU do expect they can receive responses to VirtIO commands they issue via VirtIO queue. Such responses are not sent via a separate device writeable queue. Instead the driver is expected to queue some additional descriptors and buffers which the device can then use to provide the reply. This patch adds support for such write-data-read-response opeartion to Genode VirtIO::Queue implementation. The implementation is pretty simple and does not support any fancy features like receiving the response asynchronously. Instead the operation will use caller provided callback to wait for the device to process the command. Once this callback returns the write-data-read-response VirtIO::Queue function will invoke another callback passing received response as argument.
This commit is contained in:
parent
0bb0ac079a
commit
2ec9e69fd4
@ -217,6 +217,35 @@ class Virtio::Queue
|
||||
void *_buffer_local_addr(Descriptor const *d) {
|
||||
return (void *)(_buffer_local_base + (d->addr - _buffer_phys_base)); }
|
||||
|
||||
template <typename REPLY_TYPE, typename REPLY_FN>
|
||||
bool _read_response_data(REPLY_FN const &reply_fn)
|
||||
{
|
||||
static_assert(TRAITS::has_data_payload);
|
||||
static_assert(!TRAITS::device_write_only);
|
||||
|
||||
if (!has_used_buffers())
|
||||
return false;
|
||||
|
||||
Genode::uint16_t const idx = _last_used_idx % _queue_size;
|
||||
auto const *desc = &_desc_table[idx];
|
||||
char const *desc_data = (char *)_buffer_local_addr(desc);
|
||||
|
||||
if (!(desc->flags & Descriptor::Flags::NEXT))
|
||||
return false;
|
||||
|
||||
if (desc->next >= _queue_size)
|
||||
return false;
|
||||
|
||||
desc = &_desc_table[desc->next];
|
||||
desc_data = (char *)_buffer_local_addr(desc);
|
||||
|
||||
if (reply_fn(*reinterpret_cast<REPLY_TYPE const *>(desc_data))) {
|
||||
_last_used_idx++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
struct Invalid_buffer_size : Genode::Exception { };
|
||||
@ -352,6 +381,104 @@ class Virtio::Queue
|
||||
return header;
|
||||
}
|
||||
|
||||
template <typename REPLY_TYPE, typename WAIT_REPLY_FN, typename REPLY_FN>
|
||||
bool write_data_read_reply(Header_type const &header,
|
||||
char const *data,
|
||||
Genode::size_t data_size,
|
||||
WAIT_REPLY_FN const &wait_for_reply,
|
||||
REPLY_FN const &read_reply,
|
||||
bool request_irq = false)
|
||||
{
|
||||
static_assert(!TRAITS::device_write_only);
|
||||
static_assert(TRAITS::has_data_payload);
|
||||
|
||||
int req_desc_count = 1 + (sizeof(header) + data_size) / _buffer_size;
|
||||
|
||||
if (req_desc_count > _avail_capacity())
|
||||
return false;
|
||||
|
||||
/*
|
||||
* This restriction could be lifted by chaining multiple descriptors to receive
|
||||
* the reply. Its probably better however to just ensure buffers are large enough
|
||||
* when configuring the queue instead of adding more complexity to this function.
|
||||
*/
|
||||
if (sizeof(REPLY_TYPE) > _buffer_size)
|
||||
return false;
|
||||
|
||||
Genode::uint16_t avail_idx = _avail->idx;
|
||||
auto *desc = &_desc_table[avail_idx % _queue_size];
|
||||
avail_idx++;
|
||||
|
||||
Genode::memcpy(_buffer_local_addr(desc), (void *)&header, sizeof(header));
|
||||
desc->len = sizeof(header);
|
||||
|
||||
Genode::size_t len = 0;
|
||||
|
||||
if (data != nullptr && data_size > 0) {
|
||||
Genode::size_t len = Genode::min(_buffer_size - sizeof(header), data_size);
|
||||
Genode::memcpy((char *)_buffer_local_addr(desc) + desc->len, data, len);
|
||||
desc->len += len;
|
||||
len = data_size + sizeof(header) - desc->len;
|
||||
}
|
||||
|
||||
if (len == 0) {
|
||||
desc->flags = 0;
|
||||
desc->next = 0;
|
||||
} else {
|
||||
desc->flags = Descriptor::Flags::NEXT;
|
||||
desc->next = avail_idx % _queue_size;
|
||||
|
||||
Genode::size_t data_offset = desc->len;
|
||||
do {
|
||||
desc = &_desc_table[avail_idx % _queue_size];
|
||||
avail_idx++;
|
||||
|
||||
Genode::size_t write_len = Genode::min(_buffer_size, len);
|
||||
Genode::memcpy((char *)_buffer_local_addr(desc),
|
||||
data + data_offset, write_len);
|
||||
|
||||
desc->len = write_len;
|
||||
desc->flags = len > 0 ? Descriptor::Flags::NEXT : 0;
|
||||
desc->next = len > 0 ? (avail_idx % _queue_size) : 0;
|
||||
|
||||
len -= write_len;
|
||||
data_offset += desc->len;
|
||||
} while (len > 0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Chain additional descriptor for receiving response.
|
||||
*/
|
||||
desc->flags = Descriptor::Flags::NEXT;
|
||||
desc->next = avail_idx % _queue_size;
|
||||
|
||||
desc = &_desc_table[avail_idx % _queue_size];
|
||||
desc->len = sizeof(REPLY_TYPE);
|
||||
desc->flags = Descriptor::Flags::WRITE;
|
||||
desc->next = 0;
|
||||
|
||||
_avail->flags = request_irq ? 0 : Avail::Flags::NO_INTERRUPT;
|
||||
_avail->idx = avail_idx;
|
||||
|
||||
wait_for_reply();
|
||||
|
||||
/* Make sure wait call did what it was supposed to do. */
|
||||
if (!has_used_buffers())
|
||||
return false;
|
||||
|
||||
return _read_response_data<REPLY_TYPE>(read_reply);
|
||||
}
|
||||
|
||||
template <typename REPLY_TYPE, typename WAIT_REPLY_FN, typename REPLY_FN>
|
||||
bool write_data_read_reply(Header_type const &header,
|
||||
WAIT_REPLY_FN const &wait_for_reply,
|
||||
REPLY_FN const &read_reply,
|
||||
bool request_irq = false)
|
||||
{
|
||||
return write_data_read_reply<REPLY_TYPE>(
|
||||
header, nullptr, 0, wait_for_reply, read_reply, request_irq);
|
||||
}
|
||||
|
||||
void print(Genode::Output& output) const
|
||||
{
|
||||
Genode::print(output, "avail idx: ");
|
||||
|
Loading…
x
Reference in New Issue
Block a user