summaryrefslogtreecommitdiffstats
path: root/src/VBox/Additions/solaris/Virtio/VirtioPci-solaris.c
blob: 6c8f0ec3635249a4b5b1d6d6d319609d8f57f47c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
/* $Id: VirtioPci-solaris.c $ */
/** @file
 * VirtualBox Guest Additions - Virtio Driver for Solaris, PCI Hypervisor Interface.
 */

/*
 * Copyright (C) 2010-2020 Oracle Corporation
 *
 * This file is part of VirtualBox Open Source Edition (OSE), as
 * available from http://www.virtualbox.org. This file is free software;
 * you can redistribute it and/or modify it under the terms of the GNU
 * General Public License (GPL) as published by the Free Software
 * Foundation, in version 2 as it comes in the "COPYING" file of the
 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
 *
 * The contents of this file may alternatively be used under the terms
 * of the Common Development and Distribution License Version 1.0
 * (CDDL) only, as it comes in the "COPYING.CDDL" file of the
 * VirtualBox OSE distribution, in which case the provisions of the
 * CDDL are applicable instead of those of the GPL.
 *
 * You may elect to license modified versions of this file under the
 * terms and conditions of either the GPL or the CDDL or both.
 */


/*********************************************************************************************************************************
*   Header Files                                                                                                                 *
*********************************************************************************************************************************/
#include "VirtioPci-solaris.h"

#include <iprt/asm.h>
#include <iprt/assert.h>
#include <iprt/mem.h>
#include <iprt/param.h>
#include <VBox/log.h>

#include <sys/pci.h>
#include <sys/param.h>


/*********************************************************************************************************************************
*   Defined Constants And Macros                                                                                                 *
*********************************************************************************************************************************/
/*
 * Pci Register offsets.
 */
#define VIRTIO_PCI_HOST_FEATURES            0x00
#define VIRTIO_PCI_GUEST_FEATURES           0x04
#define VIRTIO_PCI_QUEUE_PFN                0x08
#define VIRTIO_PCI_QUEUE_NUM                0x0C
#define VIRTIO_PCI_QUEUE_SEL                0x0E
#define VIRTIO_PCI_QUEUE_NOTIFY             0x10
#define VIRTIO_PCI_STATUS                   0x12
#define VIRTIO_PCI_ISR                      0x13
#define VIRTIO_PCI_CONFIG                   0x14

#define VIRTIO_PCI_RING_ALIGN               PAGE_SIZE
#define VIRTIO_PCI_QUEUE_ADDR_SHIFT         PAGE_SHIFT

/**
 * virtio_pci_t: Private data per device instance.
 */
typedef struct virtio_pci_t
{
    ddi_acc_handle_t    hIO;            /* IO handle */
    caddr_t             addrIOBase;     /* IO base address */
} virtio_pci_t;

/**
 * virtio_pci_queue_t: Private data per queue instance.
 */
typedef struct virtio_pci_queue_t
{
    ddi_dma_handle_t    hDMA;           /* DMA handle. */
    ddi_acc_handle_t    hIO;            /* IO handle. */
    size_t              cbBuf;          /* Physical address of buffer. */
    paddr_t             physBuf;        /* Size of buffer. */
    pfn_t               pageBuf;        /* Page frame number of buffer. */
} virtio_pci_queue_t;

static ddi_device_acc_attr_t g_VirtioPciAccAttrRegs =
{
    DDI_DEVICE_ATTR_V0,                 /* Version */
    DDI_STRUCTURE_LE_ACC,               /* Structural data access in little endian. */
    DDI_STRICTORDER_ACC,                /* Strict ordering. */
    DDI_DEFAULT_ACC                     /* Default access, possible panic on errors*/
};

static ddi_device_acc_attr_t g_VirtioPciAccAttrRing =
{
    DDI_DEVICE_ATTR_V0,                 /* Version. */
    DDI_NEVERSWAP_ACC,                  /* Data access with no byte swapping*/
    DDI_STRICTORDER_ACC,                /* Strict ordering. */
    DDI_DEFAULT_ACC                     /* Default access, possible panic on errors*/
};

static ddi_dma_attr_t g_VirtioPciDmaAttrRing =
{
    DMA_ATTR_V0,                        /* Version. */
    0,                                  /* Lowest usable address. */
    0xffffffffffffffffULL,              /* Highest usable address. */
    0x7fffffff,                         /* Maximum DMAable byte count. */
    VIRTIO_PCI_RING_ALIGN,              /* Alignment in bytes. */
    0x7ff,                              /* Bitmap of burst sizes */
    1,                                  /* Minimum transfer. */
    0xffffffffU,                        /* Maximum transfer. */
    0xffffffffffffffffULL,              /* Maximum segment length. */
    1,                                  /* Maximum number of segments. */
    1,                                  /* Granularity. */
    0                                   /* Flags (reserved). */
};

/** Pointer to the interrupt handle vector */
static ddi_intr_handle_t   *g_pIntr;
/** Number of actually allocated interrupt handles */
static size_t               g_cIntrAllocated;
/** The IRQ Mutex */
static kmutex_t             g_IrqMtx;


/*********************************************************************************************************************************
*   Internal Functions                                                                                                           *
*********************************************************************************************************************************/
static void    *VirtioPciAlloc(PVIRTIODEVICE pDevice);
static void     VirtioPciFree(PVIRTIODEVICE pDevice);
static int      VirtioPciAttach(PVIRTIODEVICE pDevice);
static int      VirtioPciDetach(PVIRTIODEVICE pDevice);
static uint32_t VirtioPciGetFeatures(PVIRTIODEVICE pDevice);
static void     VirtioPciSetFeatures(PVIRTIODEVICE pDevice, uint32_t fFeatures);
static int      VirtioPciNotifyQueue(PVIRTIODEVICE pDevice, PVIRTIOQUEUE pQueue);
static void     VirtioPciGet(PVIRTIODEVICE pDevice, off_t off, void *pv, size_t cb);
static void     VirtioPciSet(PVIRTIODEVICE pDevice, off_t off, void *pv, size_t cb);
static void    *VirtioPciGetQueue(PVIRTIODEVICE pDevice, PVIRTIOQUEUE pQueue);
static void     VirtioPciPutQueue(PVIRTIODEVICE pDevice, PVIRTIOQUEUE pQueue);
static void     VirtioPciSetStatus(PVIRTIODEVICE pDevice, uint8_t Status);

static uint_t   VirtioPciISR(caddr_t Arg);
static int      VirtioPciSetupIRQ(dev_info_t *pDip);
static void     VirtioPciRemoveIRQ(dev_info_t *pDip);

/**
 * Hypervisor operations for Virtio Pci.
 */
VIRTIOHYPEROPS g_VirtioHyperOpsPci =
{
    VirtioPciAlloc,
    VirtioPciFree,
    VirtioPciAttach,
    VirtioPciDetach,
    VirtioPciGetFeatures,
    VirtioPciSetFeatures,
    VirtioPciNotifyQueue,
    VirtioPciGet,
    VirtioPciSet,
    VirtioPciGetQueue,
    VirtioPciPutQueue,
    VirtioPciSetStatus
};


/**
 * Virtio Pci private data allocation routine.
 *
 * @param pDevice           Pointer to the Virtio device instance.
 * @return Allocated private data structure which must only be freed by calling
 *         VirtioPciFree().
 */
static void *VirtioPciAlloc(PVIRTIODEVICE pDevice)
{
    LogFlowFunc((VIRTIOLOGNAME ":VirtioPciAlloc pDevice=%p\n", pDevice));
    virtio_pci_t *pPciData = RTMemAllocZ(sizeof(virtio_pci_t));
    return pPciData;
}


/**
 * Virtio Pci private data deallocation routine.
 *
 * @param pDevice           Pointer to the Virtio device instance.
 */
static void VirtioPciFree(PVIRTIODEVICE pDevice)
{
    LogFlowFunc((VIRTIOLOGNAME ":VirtioPciFree pDevice=%p\n", pDevice));
    virtio_pci_t *pPciData = pDevice->pvHyper;
    if (pPciData)
    {
        RTMemFree(pDevice->pvHyper);
        pDevice->pvHyper = NULL;
    }
}


/**
 * Virtio Pci attach routine, called from driver attach.
 *
 * @param pDevice           Pointer to the Virtio device instance.
 *
 * @return Solaris DDI error code. DDI_SUCCESS or DDI_FAILURE.
 */
static int VirtioPciAttach(PVIRTIODEVICE pDevice)
{
    LogFlowFunc((VIRTIOLOGNAME ":VirtioPciAttach pDevice=%p\n", pDevice));
    virtio_pci_t *pPciData = pDevice->pvHyper;
    AssertReturn(pPciData, DDI_FAILURE);

    int rc = ddi_regs_map_setup(pDevice->pDip,
                                1,                       /* reg. num */
                                &pPciData->addrIOBase,
                                0,                       /* offset */
                                0,                       /* length */
                                &g_VirtioPciAccAttrRegs,
                                &pPciData->hIO);
    if (rc == DDI_SUCCESS)
    {
        /*
         * Reset the device.
         */
        VirtioPciSetStatus(pDevice, 0);

        /*
         * Add interrupt handler.
         */
        VirtioPciSetupIRQ(pDevice->pDip);

        LogFlow((VIRTIOLOGNAME ":VirtioPciAttach: successfully mapped registers.\n"));
        return DDI_SUCCESS;
    }
    else
        LogRel((VIRTIOLOGNAME ":VirtioPciAttach: ddi_regs_map_setup failed. rc=%d\n", rc));
    return DDI_FAILURE;
}


/**
 * Virtio Pci detach routine, called from driver detach.
 *
 * @param pDevice           Pointer to the Virtio device instance.
 *
 * @return Solaris DDI error code. DDI_SUCCESS or DDI_FAILURE.
 */
static int VirtioPciDetach(PVIRTIODEVICE pDevice)
{
    LogFlowFunc((VIRTIOLOGNAME ":VirtioPciDetach pDevice=%p\n", pDevice));
    virtio_pci_t *pPciData = pDevice->pvHyper;
    AssertReturn(pPciData, DDI_FAILURE);

    VirtioPciRemoveIRQ(pDevice->pDip);
    ddi_regs_map_free(&pPciData->hIO);
    return DDI_SUCCESS;
}


/**
 * Get host supported features.
 *
 * @param pDevice           Pointer to the Virtio device instance.
 *
 * @return Mask of host features.
 */
static uint32_t VirtioPciGetFeatures(PVIRTIODEVICE pDevice)
{
    LogFlowFunc((VIRTIOLOGNAME ":VirtioPciGetFeatures pDevice=%p\n", pDevice));
    virtio_pci_t *pPciData = pDevice->pvHyper;
    AssertReturn(pPciData, 0);

    return ddi_get32(pPciData->hIO, (uint32_t *)(pPciData->addrIOBase + VIRTIO_PCI_HOST_FEATURES));
}


/**
 * Set guest supported features.
 *
 * @param pDevice           Pointer to the Virtio device instance.
 * @param u32Features       Mask of guest supported features.
 */
static void VirtioPciSetFeatures(PVIRTIODEVICE pDevice, uint32_t u32Features)
{
    LogFlowFunc((VIRTIOLOGNAME ":VirtioPciSetFeatures pDevice=%p\n", pDevice));
    virtio_pci_t *pPciData = pDevice->pvHyper;
    AssertReturnVoid(pPciData);

    ddi_put32(pPciData->hIO, (uint32_t *)(pPciData->addrIOBase + VIRTIO_PCI_GUEST_FEATURES), u32Features);
}


/**
 * Update the queue, notify the host.
 *
 * @param pDevice           Pointer to the Virtio device instance.
 * @param pQueue            Pointer to the Queue that is doing the notification.
 *
 * @return Solaris DDI error code. DDI_SUCCESS or DDI_FAILURE.
 */
static int VirtioPciNotifyQueue(PVIRTIODEVICE pDevice, PVIRTIOQUEUE pQueue)
{
    LogFlowFunc((VIRTIOLOGNAME ":VirtioPciNotifyQueue pDevice=%p pQueue=%p\n", pDevice, pQueue));
    virtio_pci_t *pPciData = pDevice->pvHyper;
    AssertReturn(pPciData, DDI_FAILURE);

    pQueue->Ring.pRingAvail->Index += pQueue->cBufs;
    pQueue->cBufs = 0;

    ASMCompilerBarrier();

    ddi_put16(pPciData->hIO, (uint16_t *)(pPciData->addrIOBase + VIRTIO_PCI_QUEUE_NOTIFY), pQueue->QueueIndex);
    cmn_err(CE_NOTE, "VirtioPciNotifyQueue\n");
}



/**
 * Virtio Pci set (write) routine.
 *
 * @param pDevice           Pointer to the Virtio device instance.
 * @param off               Offset into the PCI config space.
 * @param pv                Pointer to the buffer to write from.
 * @param cb                Size of the buffer in bytes.
 */
static void VirtioPciSet(PVIRTIODEVICE pDevice, off_t off, void *pv, size_t cb)
{
    LogFlowFunc((VIRTIOLOGNAME ":VirtioPciSet pDevice=%p\n", pDevice));
    virtio_pci_t *pPciData = pDevice->pvHyper;
    AssertReturnVoid(pPciData);

    uint8_t *pb = pv;
    for (size_t i = 0; i < cb; i++, pb++)
        ddi_put8(pPciData->hIO, (uint8_t *)(pPciData->addrIOBase + VIRTIO_PCI_CONFIG + off + i), *pb);
}


/**
 * Virtio Pci get (read) routine.
 *
 * @param pDevice           Pointer to the Virtio device instance.
 * @param off               Offset into the PCI config space.
 * @param pv                Where to store the read data.
 * @param cb                Size of the buffer in bytes.
 */
static void VirtioPciGet(PVIRTIODEVICE pDevice, off_t off, void *pv, size_t cb)
{
    LogFlowFunc((VIRTIOLOGNAME ":VirtioPciGet pDevice=%p off=%u pv=%p cb=%u\n", pDevice, off, pv, cb));
    virtio_pci_t *pPciData = pDevice->pvHyper;
    AssertReturnVoid(pPciData);

    uint8_t *pb = pv;
    for (size_t i = 0; i < cb; i++, pb++)
        *pb = ddi_get8(pPciData->hIO, (uint8_t *)(pPciData->addrIOBase + VIRTIO_PCI_CONFIG + off + i));
}


/**
 * Virtio Pci put queue routine. Places the queue and frees associated queue.
 *
 * @param pDevice           Pointer to the Virtio device instance.
 * @param pQueue            Pointer to the queue.
 */
static void VirtioPciPutQueue(PVIRTIODEVICE pDevice, PVIRTIOQUEUE pQueue)
{
    LogFlowFunc((VIRTIOLOGNAME ":VirtioPciPutQueue pDevice=%p pQueue=%p\n", pDevice, pQueue));
    AssertReturnVoid(pDevice);
    AssertReturnVoid(pQueue);

    virtio_pci_t *pPci = pDevice->pvHyper;
    AssertReturnVoid(pPci);
    virtio_pci_queue_t *pPciQueue = pQueue->pvData;
    if (RT_UNLIKELY(!pPciQueue))
    {
        LogRel((VIRTIOLOGNAME ":VirtioPciPutQueue missing Pci queue.\n"));
        return;
    }

    ddi_put16(pPci->hIO, (uint16_t *)(pPci->addrIOBase + VIRTIO_PCI_QUEUE_SEL), pQueue->QueueIndex);
    ddi_put32(pPci->hIO, (uint32_t *)(pPci->addrIOBase + VIRTIO_PCI_QUEUE_PFN), 0);

    ddi_dma_unbind_handle(pPciQueue->hDMA);
    ddi_dma_mem_free(&pPciQueue->hIO);
    ddi_dma_free_handle(&pPciQueue->hDMA);
    RTMemFree(pPciQueue);
}


/**
 * Virtio Pci get queue routine. Allocates a PCI queue and DMA resources.
 *
 * @param pDevice           Pointer to the Virtio device instance.
 * @param pQueue            Where to store the queue.
 *
 * @return An allocated Virtio Pci queue, or NULL in case of errors.
 */
static void *VirtioPciGetQueue(PVIRTIODEVICE pDevice, PVIRTIOQUEUE pQueue)
{
    LogFlowFunc((VIRTIOLOGNAME ":VirtioPciGetQueue pDevice=%p pQueue=%p\n", pDevice, pQueue));
    AssertReturn(pDevice, NULL);

    virtio_pci_t *pPci = pDevice->pvHyper;
    AssertReturn(pPci, NULL);

    /*
     * Select a Queue.
     */
    ddi_put16(pPci->hIO, (uint16_t *)(pPci->addrIOBase + VIRTIO_PCI_QUEUE_SEL), pQueue->QueueIndex);

    /*
     * Get the currently selected Queue's size.
     */
    pQueue->Ring.cDesc = ddi_get16(pPci->hIO, (uint16_t *)(pPci->addrIOBase + VIRTIO_PCI_QUEUE_NUM));
    if (RT_UNLIKELY(!pQueue->Ring.cDesc))
    {
        LogRel((VIRTIOLOGNAME ": VirtioPciGetQueue: Queue[%d] has no descriptors.\n", pQueue->QueueIndex));
        return NULL;
    }

    /*
     * Check if it's already active.
     */
    uint32_t QueuePFN = ddi_get32(pPci->hIO, (uint32_t *)(pPci->addrIOBase + VIRTIO_PCI_QUEUE_PFN));
    if (QueuePFN != 0)
    {
        LogRel((VIRTIOLOGNAME ":VirtioPciGetQueue: Queue[%d] is already used.\n", pQueue->QueueIndex));
        return NULL;
    }

    LogFlow(("Queue[%d] has %d slots.\n", pQueue->QueueIndex, pQueue->Ring.cDesc));

    /*
     * Allocate and initialize Pci queue data.
     */
    virtio_pci_queue_t *pPciQueue = RTMemAllocZ(sizeof(virtio_pci_queue_t));
    if (pPciQueue)
    {
        /*
         * Setup DMA.
         */
        size_t cbQueue = VirtioRingSize(pQueue->Ring.cDesc, VIRTIO_PCI_RING_ALIGN);
        int rc = ddi_dma_alloc_handle(pDevice->pDip, &g_VirtioPciDmaAttrRing, DDI_DMA_SLEEP, 0 /* addr */, &pPciQueue->hDMA);
        if (rc == DDI_SUCCESS)
        {
            rc = ddi_dma_mem_alloc(pPciQueue->hDMA, cbQueue, &g_VirtioPciAccAttrRing, DDI_DMA_CONSISTENT,
                                   DDI_DMA_SLEEP, 0 /* addr */, &pQueue->pQueue, &pPciQueue->cbBuf,
                                   &pPciQueue->hIO);
            if (rc == DDI_SUCCESS)
            {
                AssertRelease(pPciQueue->cbBuf >= cbQueue);
                ddi_dma_cookie_t DmaCookie;
                uint_t cCookies;
                rc = ddi_dma_addr_bind_handle(pPciQueue->hDMA, NULL /* addrspace */, pQueue->pQueue, pPciQueue->cbBuf,
                                              DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP,
                                              0 /* addr */, &DmaCookie, &cCookies);
                if (rc == DDI_SUCCESS)
                {
                    pPciQueue->physBuf = DmaCookie.dmac_laddress;
                    pPciQueue->pageBuf = pPciQueue->physBuf >> VIRTIO_PCI_QUEUE_ADDR_SHIFT;

                    LogFlow((VIRTIOLOGNAME ":VirtioPciGetQueue: Queue[%d]%p physBuf=%x pfn of Buf %#x\n", pQueue->QueueIndex,
                             pQueue->pQueue, pPciQueue->physBuf, pPciQueue->pageBuf));
                    cmn_err(CE_NOTE, ":VirtioPciGetQueue: Queue[%d]%p physBuf=%x pfn of Buf %x\n", pQueue->QueueIndex,
                             pQueue->pQueue, pPciQueue->physBuf, pPciQueue->pageBuf);

                    /*
                     * Activate the queue and initialize a ring for the queue.
                     */
                    memset(pQueue->pQueue, 0, pPciQueue->cbBuf);
                    ddi_put32(pPci->hIO, (uint32_t *)(pPci->addrIOBase + VIRTIO_PCI_QUEUE_PFN), pPciQueue->pageBuf);
                    VirtioRingInit(pQueue, pQueue->Ring.cDesc, pQueue->pQueue, VIRTIO_PCI_RING_ALIGN);
                    return pPciQueue;
                }
                else
                    LogRel((VIRTIOLOGNAME ":VirtioPciGetQueue: ddi_dma_addr_bind_handle failed. rc=%d\n", rc));

                ddi_dma_mem_free(&pPciQueue->hIO);
            }
            else
                LogRel((VIRTIOLOGNAME ":VirtioPciGetQueue: ddi_dma_mem_alloc failed for %u bytes rc=%d\n", cbQueue, rc));

            ddi_dma_free_handle(&pPciQueue->hDMA);
        }
        else
            LogRel((VIRTIOLOGNAME ":VirtioPciGetQueue: ddi_dma_alloc_handle failed. rc=%d\n", rc));

        RTMemFree(pPciQueue);
    }
    else
        LogRel((VIRTIOLOGNAME ":VirtioPciGetQueue: failed to alloc %u bytes for Pci Queue data.\n", sizeof(virtio_pci_queue_t)));

    return NULL;
}


/**
 * Set the Virtio PCI status bit.
 *
 * @param pDevice           Pointer to the Virtio device instance.
 * @param Status            The status to set.
 */
static void VirtioPciSetStatus(PVIRTIODEVICE pDevice, uint8_t Status)
{
    virtio_pci_t *pPciData = pDevice->pvHyper;
    ddi_put8(pPciData->hIO, (uint8_t *)(pPciData->addrIOBase + VIRTIO_PCI_STATUS), Status);
}


/**
 * Sets up IRQ for Virtio PCI.
 *
 * @param   pDip     Pointer to the device info structure.
 *
 * @return Solaris error code.
 */
static int VirtioPciSetupIRQ(dev_info_t *pDip)
{
    LogFlow((VIRTIOLOGNAME ":VirtioPciSetupIRQ: pDip=%p\n", pDip));
    cmn_err(CE_NOTE, "VirtioPciSetupIRQ\n");

    int IntrType = 0;
    int rc = ddi_intr_get_supported_types(pDip, &IntrType);
    if (rc == DDI_SUCCESS)
    {
        /* We won't need to bother about MSIs. */
        if (IntrType & DDI_INTR_TYPE_FIXED)
        {
            int IntrCount = 0;
            rc = ddi_intr_get_nintrs(pDip, IntrType, &IntrCount);
            if (   rc == DDI_SUCCESS
                && IntrCount > 0)
            {
                int IntrAvail = 0;
                rc = ddi_intr_get_navail(pDip, IntrType, &IntrAvail);
                if (   rc == DDI_SUCCESS
                    && IntrAvail > 0)
                {
                    /* Allocated kernel memory for the interrupt handles. The allocation size is stored internally. */
                    g_pIntr = RTMemAllocZ(IntrCount * sizeof(ddi_intr_handle_t));
                    if (g_pIntr)
                    {
                        int IntrAllocated;
                        rc = ddi_intr_alloc(pDip, g_pIntr, IntrType, 0, IntrCount, &IntrAllocated, DDI_INTR_ALLOC_NORMAL);
                        if (   rc == DDI_SUCCESS
                            && IntrAllocated > 0)
                        {
                            g_cIntrAllocated = IntrAllocated;
                            uint_t uIntrPriority;
                            rc = ddi_intr_get_pri(g_pIntr[0], &uIntrPriority);
                            if (rc == DDI_SUCCESS)
                            {
                                /* Initialize the mutex. */
                                mutex_init(&g_IrqMtx, NULL, MUTEX_DRIVER, DDI_INTR_PRI(uIntrPriority));

                                /* Assign interrupt handler functions and enable interrupts. */
                                for (int i = 0; i < IntrAllocated; i++)
                                {
                                    rc = ddi_intr_add_handler(g_pIntr[i], (ddi_intr_handler_t *)VirtioPciISR,
                                                            NULL /* No Private Data */, NULL);
                                    if (rc == DDI_SUCCESS)
                                        rc = ddi_intr_enable(g_pIntr[i]);
                                    if (rc != DDI_SUCCESS)
                                    {
                                        /* Changing local IntrAllocated to hold so-far allocated handles for freeing. */
                                        IntrAllocated = i;
                                        break;
                                    }
                                }
                                if (rc == DDI_SUCCESS)
                                {
                                    cmn_err(CE_NOTE, "VirtioPciSetupIRQ success\n");
                                    return rc;
                                }

                                /* Remove any assigned handlers */
                                LogRel((VIRTIOLOGNAME ":VirtioPciSetupIRQ failed to assign IRQs allocated=%d\n", IntrAllocated));
                                for (int x = 0; x < IntrAllocated; x++)
                                    ddi_intr_remove_handler(g_pIntr[x]);
                            }
                            else
                                LogRel((VIRTIOLOGNAME ":VirtioPciSetupIRQ failed to get priority of interrupt. rc=%d\n", rc));

                            /* Remove allocated IRQs, too bad we can free only one handle at a time. */
                            for (int k = 0; k < g_cIntrAllocated; k++)
                                ddi_intr_free(g_pIntr[k]);
                        }
                        else
                            LogRel((VIRTIOLOGNAME ":VirtioPciSetupIRQ: failed to allocated IRQs. count=%d\n", IntrCount));
                        RTMemFree(g_pIntr);
                    }
                    else
                        LogRel((VIRTIOLOGNAME ":VirtioPciSetupIRQ: failed to allocated IRQs. count=%d\n", IntrCount));
                }
                else
                {
                    LogRel((VIRTIOLOGNAME ":VirtioPciSetupIRQ: failed to get or insufficient available IRQs. rc=%d IntrAvail=%d\n",
                            rc, IntrAvail));
                }
            }
            else
            {
                LogRel((VIRTIOLOGNAME ":VirtioPciSetupIRQ: failed to get or insufficient number of IRQs. rc=%d IntrCount=%d\n", rc,
                        IntrCount));
            }
        }
        else
            LogRel((VIRTIOLOGNAME ":VirtioPciSetupIRQ: invalid irq type. IntrType=%#x\n", IntrType));
    }
    else
        LogRel((VIRTIOLOGNAME ":VirtioPciSetupIRQ: failed to get supported interrupt types\n"));
    return rc;
}


/**
 * Removes IRQ for Virtio PCI device.
 *
 * @param   pDip     Pointer to the device info structure.
 */
static void VirtioPciRemoveIRQ(dev_info_t *pDip)
{
    LogFlow((VIRTIOLOGNAME ":VirtioPciRemoveIRQ pDip=%p:\n", pDip));

    for (int i = 0; i < g_cIntrAllocated; i++)
    {
        int rc = ddi_intr_disable(g_pIntr[i]);
        if (rc == DDI_SUCCESS)
        {
            rc = ddi_intr_remove_handler(g_pIntr[i]);
            if (rc == DDI_SUCCESS)
                ddi_intr_free(g_pIntr[i]);
        }
    }
    RTMemFree(g_pIntr);
    mutex_destroy(&g_IrqMtx);
}


/**
 * Interrupt Service Routine for Virtio PCI device.
 *
 * @param   Arg     Private data (unused, will be NULL).
 * @returns DDI_INTR_CLAIMED if it's our interrupt, DDI_INTR_UNCLAIMED if it isn't.
 */
static uint_t VirtioPciISR(caddr_t Arg)
{
    LogFlow((VIRTIOLOGNAME ":VBoxGuestSolarisISR\n"));
    cmn_err(CE_NOTE, "VBoxGuestSolarisISRd Arg=%p\n", Arg);

    mutex_enter(&g_IrqMtx);
    bool fOurIRQ = false;
    /*
     * Call the DeviceOps ISR routine somehow which should notify all Virtio queues
     * on the interrupt.
     */
    mutex_exit(&g_IrqMtx);

    return fOurIRQ ? DDI_INTR_CLAIMED : DDI_INTR_UNCLAIMED;
}