3#include "pedigree/kernel/LockGuard.h"
4#include "pedigree/kernel/Log.h"
5#include "pedigree/kernel/Service.h"
6#include "pedigree/kernel/ServiceFeatures.h"
7#include "pedigree/kernel/ServiceManager.h"
8#include "pedigree/kernel/machine/Pci.h"
9#include "pedigree/kernel/panic.h"
10#include "pedigree/kernel/process/Scheduler.h"
11#include "pedigree/kernel/process/Thread.h"
12#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
13#include "pedigree/kernel/processor/VirtualAddressSpace.h"
14#include "pedigree/kernel/time/Time.h"
15#include "pedigree/kernel/utilities/String.h"
16#include "pedigree/kernel/utilities/utility.h"
19constexpr uint32_t Create2d = 0x0101;
20constexpr uint32_t SetScanout = 0x0103;
21constexpr uint32_t Flush = 0x0104;
22constexpr uint32_t Transfer2d = 0x0105;
23constexpr uint32_t AttachBacking = 0x0106;
24constexpr uint32_t GetDisplayInfo = 0x0100;
25constexpr uint32_t OkNoData = 0x1100;
26constexpr uint32_t OkDisplayInfo = 0x1101;
27constexpr uint32_t Fence = 1;
28constexpr uint32_t BgrxFormat = 2;
29constexpr uint32_t ResourceId = 1;
30constexpr size_t MaxWidth = 1920;
31constexpr size_t MaxHeight = 1200;
32constexpr size_t RefreshMilliseconds = 50;
33constexpr size_t ResponseOffset = 512;
60struct CreateResource {
68struct ResourceRectangle {
75struct TransferResource {
89struct AttachResource {
95static_assert(
sizeof(Header) == 24,
"virtio GPU control header has the wrong layout");
96static_assert(
sizeof(DisplayInfo) == 408,
"virtio GPU display response has the wrong layout");
98size_t pagesFor(
size_t bytes) {
100 return (bytes + pageSize - 1) / pageSize;
104VirtioGpu::GpuFramebuffer::GpuFramebuffer(
VirtioGpu* gpu)
108 if (!m_Gpu || offset >= getHeight() * getBytesPerLine()) {
109 return ~physical_uintptr_t(0);
113 static_cast<uint8_t*
>(m_Gpu->m_Pixels.
virtualAddress()) + (offset & ~(pageSize - 1));
114 physical_uintptr_t physical = 0;
117 return ~physical_uintptr_t(0);
122void VirtioGpu::GpuFramebuffer::closeCallbacks() {
126void VirtioGpu::GpuFramebuffer::detach() {
135 if (!m_Callbacks.tryAcquire(callback) || !m_Gpu) {
138 m_Gpu->redraw(x, y, width, height);
141VirtioGpu::VirtioGpu(
Device* pci)
146 m_Commands(
"virtio-gpu commands"),
147 m_Backing(
"virtio-gpu backing list"),
148 m_Pixels(
"virtio-gpu framebuffer"),
150 m_Framebuffer(nullptr),
152 m_RefreshThread(nullptr),
164VirtioGpu::~VirtioGpu() {
168bool VirtioGpu::transact(
const void* request,
size_t requestLength, uint32_t expectedResponse,
169 void* response,
size_t responseLength) {
171 if (!m_Ready || __atomic_load_n(&m_Failed, __ATOMIC_ACQUIRE) ||
172 __atomic_load_n(&m_Stopping, __ATOMIC_ACQUIRE) || !request ||
173 requestLength <
sizeof(Header) || responseLength <
sizeof(Header) ||
174 responseLength > m_Commands.
size() - ResponseOffset) {
178 uint64_t requestPhysical = 0;
179 uint8_t* requestBytes =
nullptr;
180 if (requestLength <= ResponseOffset) {
181 requestBytes =
static_cast<uint8_t*
>(m_Commands.
virtualAddress());
182 MemoryCopy(requestBytes, request, requestLength);
184 }
else if (request == m_Backing.
virtualAddress() && requestLength <= m_Backing.
size()) {
191 auto* header =
reinterpret_cast<Header*
>(requestBytes);
192 header->flags = Fence;
193 header->fenceId = ++m_NextFence;
194 auto* responseBytes =
static_cast<uint8_t*
>(m_Commands.
virtualAddress()) + ResponseOffset;
195 ByteSet(responseBytes, 0, responseLength);
198 {requestPhysical,
static_cast<uint32_t
>(requestLength),
false},
199 {m_Commands.
physicalAddress() + ResponseOffset,
static_cast<uint32_t
>(responseLength),
true},
201 if (!m_ControlQueue.submit(buffers, 2,
this)) {
202 __atomic_store_n(&m_Failed,
true, __ATOMIC_RELEASE);
203 ERROR(
"virtio-gpu: control queue submission failed");
206 m_Transport.notify(0);
208 const uint64_t deadline = Time::getTicks() + 500 * Time::Multiplier::Millisecond;
210 while (Time::getTicks() < deadline) {
211 if (m_ControlQueue.pop(completion)) {
214 Time::delay(Time::Multiplier::Millisecond);
216 if (completion.cookie !=
this || completion.length < responseLength ||
217 completion.length > responseLength) {
218 __atomic_store_n(&m_Failed,
true, __ATOMIC_RELEASE);
219 ERROR(
"virtio-gpu: control command timed out or returned an invalid length");
224 auto* result =
reinterpret_cast<const Header*
>(responseBytes);
225 if (result->type != expectedResponse || !(result->flags & Fence) ||
226 result->fenceId != header->fenceId) {
227 __atomic_store_n(&m_Failed,
true, __ATOMIC_RELEASE);
228 ERROR(
"virtio-gpu: control command returned error " <<
Hex << result->type);
232 MemoryCopy(response, responseBytes, responseLength);
237bool VirtioGpu::initialise() {
239 !m_Transport.initialise() || !m_Transport.negotiate(0) ||
240 !m_Transport.setupQueue(0, m_ControlQueue)) {
247 !m_Transport.ready() || !PciBus::instance().updateCommand(m_Pci, 0, 0x400U)) {
251 (void)m_Transport.readIsr();
253 uint32_t scanoutCount = 0;
254 if (!m_Transport.readDeviceConfig32(8, scanoutCount) || !scanoutCount || scanoutCount > 16) {
257 const Header query = {GetDisplayInfo, 0, 0, 0, 0, {0, 0, 0}};
259 if (!transact(&query,
sizeof(query), OkDisplayInfo, &info,
sizeof(info))) {
265 for (uint32_t i = 0; i < scanoutCount; ++i) {
266 const auto& candidate = info.scanouts[i];
267 if (candidate.enabled && candidate.rect.width && candidate.rect.height &&
268 candidate.rect.width <= MaxWidth && candidate.rect.height <= MaxHeight) {
270 width = candidate.rect.width;
271 height = candidate.rect.height;
276 const size_t bytes = width * height * 4;
277 const size_t pixelPages = pagesFor(bytes);
278 const size_t backingBytes =
sizeof(AttachResource) + pixelPages *
sizeof(BackingEntry);
279 if (!memory.allocateRegion(m_Pixels, pixelPages, 0, mapFlags) ||
282 ERROR(
"virtio-gpu: framebuffer allocation failed");
287 CreateResource create{};
288 create.header.type = Create2d;
289 create.resourceId = ResourceId;
290 create.format = BgrxFormat;
291 create.width = width;
292 create.height = height;
293 if (!transact(&create,
sizeof(create), OkNoData,
nullptr,
sizeof(Header))) {
297 auto* attach =
static_cast<AttachResource*
>(m_Backing.
virtualAddress());
298 ByteSet(attach, 0, backingBytes);
299 attach->header.type = AttachBacking;
300 attach->resourceId = ResourceId;
301 attach->entries = pixelPages;
302 auto* entries =
reinterpret_cast<BackingEntry*
>(
303 static_cast<uint8_t*
>(m_Backing.
virtualAddress()) +
sizeof(AttachResource));
304 for (
size_t i = 0; i < pixelPages; ++i) {
307 physical_uintptr_t physical = 0;
312 entries[i].address = physical;
318 if (!transact(attach, backingBytes, OkNoData,
nullptr,
sizeof(Header))) {
322 m_Mode.width = width;
323 m_Mode.height = height;
324 ResourceRectangle scanout{};
325 scanout.header.type = SetScanout;
326 scanout.rect.width = width;
327 scanout.rect.height = height;
328 scanout.resourceId = m_Scanout;
329 scanout.extra = ResourceId;
330 if (!transact(&scanout,
sizeof(scanout), OkNoData,
nullptr,
sizeof(Header)) ||
331 !update(0, 0, width, height)) {
335 m_Framebuffer =
new GpuFramebuffer(
this);
337 if (!m_Framebuffer || !m_Provider) {
338 ERROR(
"virtio-gpu: could not allocate graphics provider");
341 m_Framebuffer->setWidth(width);
342 m_Framebuffer->setHeight(height);
343 m_Framebuffer->setBytesPerPixel(4);
344 m_Framebuffer->setBytesPerLine(width * 4);
345 m_Framebuffer->setFormat(Graphics::Bits32_Rgb);
346 m_Framebuffer->setFramebuffer(
reinterpret_cast<uintptr_t
>(m_Pixels.
virtualAddress()));
349 m_Mode.framebuffer = 0;
359 m_Mode.pf.
nPitch = width * 4;
360 m_Mode.pf2 = Graphics::Bits32_Rgb;
361 m_Mode.bytesPerLine = width * 4;
362 m_Mode.bytesPerPixel = 4;
363 m_Mode.textMode =
false;
365 m_Provider->pDisplay =
this;
366 m_Provider->pFramebuffer = m_Framebuffer;
367 m_Provider->maxWidth = width;
368 m_Provider->maxHeight = height;
369 m_Provider->maxDepth = 32;
370 m_Provider->maxTextWidth = 0;
371 m_Provider->maxTextHeight = 0;
372 m_Provider->bHardwareAccel =
false;
375 if (!registerProvider()) {
380 nullptr,
false,
false,
true);
381 if (!m_RefreshThread) {
382 ERROR(
"virtio-gpu: could not allocate refresh worker");
385 m_RefreshThread->setName(
String(
"virtio-gpu refresh"));
386 if (!m_RefreshThread->
start()) {
387 ERROR(
"virtio-gpu: could not start refresh worker");
388 __atomic_store_n(&m_Stopping,
true, __ATOMIC_RELEASE);
390 panic(
"virtio-gpu: failed refresh worker could not be joined");
392 m_RefreshThread =
nullptr;
395 NOTICE(
"virtio-gpu: scanout " <<
Dec << m_Scanout <<
" at " << width <<
"x" << height <<
"x32"
400bool VirtioGpu::update(
size_t x,
size_t y,
size_t width,
size_t height) {
401 if (!width || !height) {
404 TransferResource transfer{};
405 transfer.header.type = Transfer2d;
406 transfer.rect = {
static_cast<uint32_t
>(x),
static_cast<uint32_t
>(y),
static_cast<uint32_t
>(width),
407 static_cast<uint32_t
>(height)};
408 transfer.offset = (y * m_Mode.width + x) * 4;
409 transfer.resourceId = ResourceId;
410 if (!transact(&transfer,
sizeof(transfer), OkNoData,
nullptr,
sizeof(Header))) {
413 ResourceRectangle flush{};
414 flush.header.type = Flush;
415 flush.rect = transfer.rect;
416 flush.resourceId = ResourceId;
417 return transact(&flush,
sizeof(flush), OkNoData,
nullptr,
sizeof(Header));
420void VirtioGpu::redraw(
size_t x,
size_t y,
size_t width,
size_t height) {
421 if (!m_Ready || __atomic_load_n(&m_Stopping, __ATOMIC_ACQUIRE) ||
422 __atomic_load_n(&m_Failed, __ATOMIC_ACQUIRE) || x >= m_Mode.width || y >= m_Mode.height) {
425 if (width > m_Mode.width - x) {
426 width = m_Mode.width - x;
428 if (height > m_Mode.height - y) {
429 height = m_Mode.height - y;
431 (void)update(x, y, width, height);
434int VirtioGpu::refreshThread(
void* context) {
435 auto* gpu =
static_cast<VirtioGpu*
>(context);
436 while (!__atomic_load_n(&gpu->m_Stopping, __ATOMIC_ACQUIRE) &&
437 !__atomic_load_n(&gpu->m_Failed, __ATOMIC_ACQUIRE)) {
438 Time::delay(RefreshMilliseconds * Time::Multiplier::Millisecond);
439 if (!__atomic_load_n(&gpu->m_Stopping, __ATOMIC_ACQUIRE) &&
440 !__atomic_load_n(&gpu->m_Failed, __ATOMIC_ACQUIRE)) {
441 gpu->redraw(0, 0, gpu->m_Mode.width, gpu->m_Mode.height);
447bool VirtioGpu::registerProvider() {
451 ERROR(
"virtio-gpu: graphics service unavailable");
455 ERROR(
"virtio-gpu: graphics service rejected provider");
462void VirtioGpu::unregisterProvider() {
468 panic(
"virtio-gpu: graphics provider could not be withdrawn");
470 m_Registered =
false;
473void VirtioGpu::shutdown() {
477 __atomic_store_n(&m_Stopping,
true, __ATOMIC_RELEASE);
479 m_Framebuffer->closeCallbacks();
481 unregisterProvider();
482 if (m_RefreshThread) {
484 panic(
"virtio-gpu: refresh worker could not be joined");
486 m_RefreshThread =
nullptr;
489 m_Framebuffer->detach();
491 if (!m_Transport.reset()) {
492 panic(
"virtio-gpu: device did not stop DMA");
494 m_ControlQueue.stop();
497 delete m_Framebuffer;
498 m_Provider =
nullptr;
499 m_Framebuffer =
nullptr;
504 name.assign(
"virtio-gpu", 10);
508 description.assign(
"virtio 2D graphics card", 23);
517 return m_Ready && !__atomic_load_n(&m_Failed, __ATOMIC_ACQUIRE);
522 return m_Ready && !__atomic_load_n(&m_Failed, __ATOMIC_ACQUIRE);
526 if (!m_Ready || __atomic_load_n(&m_Failed, __ATOMIC_ACQUIRE)) {
538 return m_Ready && !__atomic_load_n(&m_Failed, __ATOMIC_ACQUIRE) && mode.id == m_Mode.id &&
539 mode.width == m_Mode.width && mode.height == m_Mode.height && mode.pf.
nBpp == 32;
543 return m_Ready && !__atomic_load_n(&m_Failed, __ATOMIC_ACQUIRE) &&
544 (modeId == 0 || modeId == m_Mode.id);
548 return m_Ready && !__atomic_load_n(&m_Failed, __ATOMIC_ACQUIRE) && width == m_Mode.width &&
549 height == m_Mode.height && bpp == 32;
uint16_t getPciDeviceId()
uint16_t getPciVendorId()
virtual void setSpecificType(String str)
void * virtualAddress() const
physical_uintptr_t physicalAddress() const
static const size_t continuous
static PhysicalMemoryManager & instance()
static constexpr size_t getPageSize() PURE
static Scheduler & instance()
virtual bool provides(Type service)
Service * getService(const String &serviceName)
ServiceFeatures * enumerateOperations(const String &serviceName)
virtual bool serve(ServiceFeatures::Type type, void *pData, size_t dataLen)=0
physical_uintptr_t getPhysicalPage(size_t offset) const override
void hwRedraw(size_t x, size_t y, size_t width, size_t height) override
Inherited by drivers that provide a hardware redraw function.
void dump(String &description) override
void * getFramebuffer() override
void getName(String &name) override
bool setScreenMode(ScreenMode mode) override
bool getCurrentScreenMode(ScreenMode &mode) override
bool getScreenModes(List< ScreenMode * > &modes) override
bool getPixelFormat(PixelFormat &format) override
static const size_t KernelMode
virtual bool getMapping(void *virtualAddress, physical_uintptr_t &physicalAddress, size_t &flags)=0
static const size_t Write
static EXPORTED_PUBLIC VirtualAddressSpace & getKernelAddressSpace()
void EXPORTED_PUBLIC panic(const char *msg) NORETURN
void pushBack(const T &value)
bool bFirmwareFallback
Firmware scanout is a fallback once a native driver owns the display.