⛏️ index : haiku.git

/*
 * Copyright 2013, 2018, Jérôme Duval, jerome.duval@gmail.com.
 * Distributed under the terms of the MIT License.
 */


#include "VirtioPrivate.h"


static inline uint32
round_to_pagesize(uint32 size)
{
	return (size + B_PAGE_SIZE - 1) & ~(B_PAGE_SIZE - 1);
}


area_id
alloc_mem(void **virt, phys_addr_t *phy, size_t size, uint32 protection,
	const char *name)
{
	physical_entry pe;
	void * virtadr;
	area_id areaid;
	status_t rv;

	TRACE("allocating %ld bytes for %s\n", size, name);

	size = round_to_pagesize(size);
	areaid = create_area(name, &virtadr, B_ANY_KERNEL_ADDRESS, size,
		B_CONTIGUOUS, protection);
	if (areaid < B_OK) {
		ERROR("couldn't allocate area %s\n", name);
		return B_ERROR;
	}
	rv = get_memory_map(virtadr, size, &pe, 1);
	if (rv < B_OK) {
		delete_area(areaid);
		ERROR("couldn't get mapping for %s\n", name);
		return B_ERROR;
	}
	if (virt)
		*virt = virtadr;
	if (phy)
		*phy = pe.address;
	TRACE("area = %" B_PRId32 ", size = %ld, virt = %p, phy = %#" B_PRIxPHYSADDR "\n",
		areaid, size, virtadr, pe.address);
	return areaid;
}


class TransferDescriptor {
public:
								TransferDescriptor(VirtioQueue* queue,
									uint16 indirectMaxSize);
								~TransferDescriptor();

			status_t			InitCheck() { return fStatus; }

			uint16				Size() { return fDescriptorCount; }
			void				SetTo(uint16 size, void *cookie);
			void*				Cookie() { return fCookie; }
			void				Unset();
			struct vring_desc*	Indirect() { return fIndirect; }
			phys_addr_t			PhysAddr() { return fPhysAddr; }
private:
			status_t			fStatus;
			VirtioQueue*		fQueue;
			void*				fCookie;

			struct vring_desc* 	fIndirect;
			size_t 				fAreaSize;
			area_id				fArea;
			phys_addr_t 		fPhysAddr;
			uint16				fDescriptorCount;
};


TransferDescriptor::TransferDescriptor(VirtioQueue* queue, uint16 indirectMaxSize)
	: fQueue(queue),
	fCookie(NULL),
	fIndirect(NULL),
	fAreaSize(0),
	fArea(-1),
	fPhysAddr(0),
	fDescriptorCount(0)
{
	fStatus = B_OK;
	struct vring_desc* virtAddr;
	phys_addr_t physAddr;

	if (indirectMaxSize > 0) {
		fAreaSize = indirectMaxSize * sizeof(struct vring_desc);
		fArea = alloc_mem((void **)&virtAddr, &physAddr, fAreaSize,
			B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA, "virtqueue");
		if (fArea < B_OK) {
			fStatus = fArea;
			return;
		}
		memset(virtAddr, 0, fAreaSize);
		fIndirect = virtAddr;
		fPhysAddr = physAddr;

		for (uint16 i = 0; i < indirectMaxSize - 1; i++)
			fIndirect[i].next = i + 1;
		fIndirect[indirectMaxSize - 1].next = UINT16_MAX;
	}
}


TransferDescriptor::~TransferDescriptor()
{
	if (fArea > B_OK)
		delete_area(fArea);
}


void
TransferDescriptor::SetTo(uint16 size, void *cookie)
{
	fCookie = cookie;
	fDescriptorCount = size;
}


void
TransferDescriptor::Unset()
{
	fCookie = NULL;
	fDescriptorCount = 0;
}


//	#pragma mark -


VirtioQueue::VirtioQueue(VirtioDevice* device, uint16 queueNumber,
	uint16 ringSize)
	:
	fDevice(device),
	fQueueNumber(queueNumber),
	fRingSize(ringSize),
	fRingFree(ringSize),
	fRingHeadIndex(0),
	fRingUsedIndex(0),
	fStatus(B_OK),
	fIndirectMaxSize(0),
	fCallback(NULL),
	fCookie(NULL)
{
	fDescriptors = new(std::nothrow) TransferDescriptor*[fRingSize];
	if (fDescriptors == NULL) {
		fStatus = B_NO_MEMORY;
		return;
	}

	uint8* virtAddr;
	phys_addr_t physAddr;
	fAreaSize = vring_size(fRingSize, device->Alignment());
	fArea = alloc_mem((void **)&virtAddr, &physAddr, fAreaSize,
		B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA, "virtqueue");
	if (fArea < B_OK) {
		fStatus = fArea;
		return;
	}
	memset(virtAddr, 0, fAreaSize);
	vring_init(&fRing, fRingSize, virtAddr, device->Alignment());

	for (uint16 i = 0; i < fRingSize - 1; i++)
		fRing.desc[i].next = i + 1;
	fRing.desc[fRingSize - 1].next = UINT16_MAX;

	if ((fDevice->Features() & VIRTIO_FEATURE_RING_INDIRECT_DESC) != 0)
		fIndirectMaxSize = fRingSize;

	for (uint16 i = 0; i < fRingSize; i++) {
		fDescriptors[i] = new TransferDescriptor(this, fIndirectMaxSize);
		if (fDescriptors[i] == NULL || fDescriptors[i]->InitCheck() != B_OK) {
			fStatus = B_NO_MEMORY;
			return;
		}
	}

	DisableInterrupt();

	device->SetupQueue(fQueueNumber, physAddr,
		physAddr + ((addr_t)fRing.avail - (addr_t)fRing.desc),
		physAddr + ((addr_t)fRing.used - (addr_t)fRing.desc));
}


VirtioQueue::~VirtioQueue()
{
	delete_area(fArea);
	for (uint16 i = 0; i < fRingSize; i++) {
		delete fDescriptors[i];
	}
	delete[] fDescriptors;
}


status_t
VirtioQueue::SetupInterrupt(virtio_callback_func handler, void *cookie)
{
	fCallback = handler;
	fCookie = cookie;

	return B_OK;
}



void
VirtioQueue::DisableInterrupt()
{
	if ((fDevice->Features() & VIRTIO_FEATURE_RING_EVENT_IDX) == 0)
		fRing.avail->flags |= VRING_AVAIL_F_NO_INTERRUPT;
}


void
VirtioQueue::EnableInterrupt()
{
	if ((fDevice->Features() & VIRTIO_FEATURE_RING_EVENT_IDX) == 0)
		fRing.avail->flags &= ~VRING_AVAIL_F_NO_INTERRUPT;
}


void
VirtioQueue::NotifyHost()
{
	fDevice->NotifyQueue(fQueueNumber);
}


status_t
VirtioQueue::Interrupt()
{
	CALLED();

	DisableInterrupt();

	if (fCallback != NULL)
		fCallback(Device()->DriverCookie(), fCookie);

	EnableInterrupt();
	return B_OK;
}


bool
VirtioQueue::Dequeue(void** _cookie, uint32* _usedLength)
{
	TRACE("Dequeue() fRingUsedIndex: %u\n", fRingUsedIndex);

	if (fRingUsedIndex == fRing.used->idx)
		return false;

	uint16 usedIndex = fRingUsedIndex++ & (fRingSize - 1);
	TRACE("Dequeue() usedIndex: %u\n", usedIndex);
	struct vring_used_elem *element = &fRing.used->ring[usedIndex];
	uint16 descriptorIndex = element->id;
	if (_usedLength != NULL)
		*_usedLength = element->len;

	void* cookie = fDescriptors[descriptorIndex]->Cookie();
	if (_cookie != NULL)
		*_cookie = cookie;

	uint16 size = fDescriptors[descriptorIndex]->Size();
	if (size == 0)
		panic("VirtioQueue::Dequeue() size is zero\n");
	fDescriptors[descriptorIndex]->Unset();
	fRingFree += size;
	size--;

	uint16 index = descriptorIndex;
	if ((fRing.desc[index].flags & VRING_DESC_F_INDIRECT) == 0) {
		while ((fRing.desc[index].flags & VRING_DESC_F_NEXT) != 0) {
			index = fRing.desc[index].next;
			size--;
		}
	}

	if (size > 0)
		panic("VirtioQueue::Dequeue() descriptors left %d\n", size);

	fRing.desc[index].next = fRingHeadIndex;
	fRingHeadIndex = descriptorIndex;
	TRACE("Dequeue() fRingHeadIndex: %u\n", fRingHeadIndex);

	return true;
}


status_t
VirtioQueue::QueueRequest(const physical_entry* vector, size_t readVectorCount,
	size_t writtenVectorCount, void *cookie)
{
	CALLED();
	size_t count = readVectorCount + writtenVectorCount;
	if (count < 1)
		return B_BAD_VALUE;
	if ((fDevice->Features() & VIRTIO_FEATURE_RING_INDIRECT_DESC) != 0) {
		return QueueRequestIndirect(vector, readVectorCount,
			writtenVectorCount, cookie);
	}

	if (count > fRingFree)
		return B_BUSY;

	uint16 insertIndex = fRingHeadIndex;
	fDescriptors[insertIndex]->SetTo(count, cookie);

	// enqueue
	uint16 index = QueueVector(insertIndex, fRing.desc, vector,
		readVectorCount, writtenVectorCount);

	fRingHeadIndex = index;
	fRingFree -= count;

	UpdateAvailable(insertIndex);

	NotifyHost();

	return B_OK;
}


status_t
VirtioQueue::QueueRequestIndirect(const physical_entry* vector,
	size_t readVectorCount,	size_t writtenVectorCount,
	void *cookie)
{
	CALLED();
	size_t count = readVectorCount + writtenVectorCount;
	if (count > fRingFree || count > fIndirectMaxSize)
		return B_BUSY;

	uint16 insertIndex = fRingHeadIndex;
	fDescriptors[insertIndex]->SetTo(1, cookie);

	// enqueue
	uint16 index = QueueVector(0, fDescriptors[insertIndex]->Indirect(),
		vector, readVectorCount, writtenVectorCount);

	fRing.desc[insertIndex].addr = fDescriptors[insertIndex]->PhysAddr();
	fRing.desc[insertIndex].len = index * sizeof(struct vring_desc);
	fRing.desc[insertIndex].flags = VRING_DESC_F_INDIRECT;
	fRingHeadIndex = fRing.desc[insertIndex].next;
	fRingFree--;

	UpdateAvailable(insertIndex);

	NotifyHost();

	return B_OK;
}


void
VirtioQueue::UpdateAvailable(uint16 index)
{
	CALLED();
	uint16 available = fRing.avail->idx & (fRingSize - 1);
	fRing.avail->ring[available] = index;
	fRing.avail->idx++;
}


uint16
VirtioQueue::QueueVector(uint16 insertIndex, struct vring_desc *desc,
	const physical_entry* vector, size_t readVectorCount,
	size_t writtenVectorCount)
{
	CALLED();
	uint16 index = insertIndex;
	size_t total = readVectorCount + writtenVectorCount;
	for (size_t i = 0; i < total; i++, index = desc[index].next) {
		desc[index].addr = vector[i].address;
		desc[index].len =  vector[i].size;
		desc[index].flags = 0;
		if (i < total - 1)
			desc[index].flags |= VRING_DESC_F_NEXT;
		if (i >= readVectorCount)
			desc[index].flags |= VRING_DESC_F_WRITE;
	}

	return index;
}