接前一篇文章:
上一回講解了pci_edu_realize函數(shù)中的pci_register_bar函數(shù),本回開(kāi)始對(duì)于edu設(shè)備的MMIO讀寫函數(shù)進(jìn)行解析。
操作系統(tǒng)與PCI設(shè)備交互的主要方式是PIO和MMIO。MMIO雖然是一段內(nèi)存,但是其沒(méi)有EPT映射,在虛擬機(jī)訪問(wèn)設(shè)備的MMIO時(shí),會(huì)產(chǎn)生VM Exit;KVM識(shí)別此MMIO訪問(wèn)并且將該訪問(wèn)分派到應(yīng)用層QEMU中;QEMU根據(jù)內(nèi)存虛擬化的步驟進(jìn)行分派,找到設(shè)備注冊(cè)的MMIO讀寫回調(diào)函數(shù);設(shè)備的MMIO讀寫回調(diào)函數(shù)根據(jù)設(shè)備的功能進(jìn)行模擬,完成模擬之后可能會(huì)發(fā)送中斷到虛擬機(jī)中,從而完成一些MMIO訪問(wèn)。
前文書(QEMU源碼全解析 —— PCI設(shè)備模擬(5))已經(jīng)講過(guò),pci_edu_realize函數(shù)中調(diào)用memory_region_init_io函數(shù),指定其讀寫函數(shù)是edu_mmio_ops。
edu_mmio_ops在hw/misc/edu中初始化,代碼如下:
static const MemoryRegionOps edu_mmio_ops = {
.read = edu_mmio_read,
.write = edu_mmio_write,
.endianness = DEVICE_NATIVE_ENDIAN,
.valid = {
.min_access_size = 4,
.max_access_size = 8,
},
.impl = {
.min_access_size = 4,
.max_access_size = 8,
},
};
edu_mmio_ops的類型為MemoryRegionOps,此結(jié)構(gòu)在include/exec/memory.h中定義,代碼如下:
typedef struct MemoryRegionOps MemoryRegionOps;
而struct MemoryRegionOps的定義也在include/exec/memory.h中,如下:
/*
* Memory region callbacks
*/
struct MemoryRegionOps {
/* Read from the memory region. @addr is relative to @mr; @size is
* in bytes. */
uint64_t (*read)(void *opaque,
hwaddr addr,
unsigned size);
/* Write to the memory region. @addr is relative to @mr; @size is
* in bytes. */
void (*write)(void *opaque,
hwaddr addr,
uint64_t data,
unsigned size);
MemTxResult (*read_with_attrs)(void *opaque,
hwaddr addr,
uint64_t *data,
unsigned size,
MemTxAttrs attrs);
MemTxResult (*write_with_attrs)(void *opaque,
hwaddr addr,
uint64_t data,
unsigned size,
MemTxAttrs attrs);
enum device_endian endianness;
/* Guest-visible constraints: */
struct {
/* If nonzero, specify bounds on access sizes beyond which a machine
* check is thrown.
*/
unsigned min_access_size;
unsigned max_access_size;
/* If true, unaligned accesses are supported. Otherwise unaligned
* accesses throw machine checks.
*/
bool unaligned;
/*
* If present, and returns #false, the transaction is not accepted
* by the device (and results in machine dependent behaviour such
* as a machine check exception).
*/
bool (*accepts)(void *opaque, hwaddr addr,
unsigned size, bool is_write,
MemTxAttrs attrs);
} valid;
/* Internal implementation constraints: */
struct {
/* If nonzero, specifies the minimum size implemented. Smaller sizes
* will be rounded upwards and a partial result will be returned.
*/
unsigned min_access_size;
/* If nonzero, specifies the maximum size implemented. Larger sizes
* will be done as a series of accesses with smaller sizes.
*/
unsigned max_access_size;
/* If true, unaligned accesses are supported. Otherwise all accesses
* are converted to (possibly multiple) naturally aligned accesses.
*/
bool unaligned;
} impl;
};
其中的read和Write函數(shù)分別表示該MMIO的讀寫回調(diào);endianness表示字節(jié)的大小端模式。
以write回調(diào)函數(shù)為例,
/* Write to the memory region. @addr is relative to @mr; @size is
* in bytes. */
void (*write)(void *opaque,
hwaddr addr,
uint64_t data,
unsigned size);
static void edu_mmio_write(void *opaque, hwaddr addr, uint64_t val,
unsigned size)
其原型中的opaque表示的是設(shè)備的對(duì)象;addr表示虛擬機(jī)讀的地址在該MMIO中的偏移地址;data(val)表示要寫入的值;size表示寫入值的大小,通常由單字節(jié)、雙字節(jié)、四字節(jié)以及八字節(jié)。
edu_mmio_write函數(shù)同樣在hw/misc/edu.c中,代碼如下:
static void edu_mmio_write(void *opaque, hwaddr addr, uint64_t val,
unsigned size)
{
EduState *edu = opaque;
if (addr < 0x80 && size != 4) {
return;
}
if (addr >= 0x80 && size != 4 && size != 8) {
return;
}
switch (addr) {
case 0x04:
edu->addr4 = ~val;
break;
case 0x08:
if (qatomic_read(&edu->status) & EDU_STATUS_COMPUTING) {
break;
}
/* EDU_STATUS_COMPUTING cannot go 0->1 concurrently, because it is only
* set in this function and it is under the iothread mutex.
*/
qemu_mutex_lock(&edu->thr_mutex);
edu->fact = val;
qatomic_or(&edu->status, EDU_STATUS_COMPUTING);
qemu_cond_signal(&edu->thr_cond);
qemu_mutex_unlock(&edu->thr_mutex);
break;
case 0x20:
if (val & EDU_STATUS_IRQFACT) {
qatomic_or(&edu->status, EDU_STATUS_IRQFACT);
/* Order check of the COMPUTING flag after setting IRQFACT. */
smp_mb__after_rmw();
} else {
qatomic_and(&edu->status, ~EDU_STATUS_IRQFACT);
}
break;
case 0x60:
edu_raise_irq(edu, val);
break;
case 0x64:
edu_lower_irq(edu, val);
break;
case 0x80:
dma_rw(edu, true, &val, &edu->dma.src, false);
break;
case 0x88:
dma_rw(edu, true, &val, &edu->dma.dst, false);
break;
case 0x90:
dma_rw(edu, true, &val, &edu->dma.cnt, false);
break;
case 0x98:
if (!(val & EDU_DMA_RUN)) {
break;
}
dma_rw(edu, true, &val, &edu->dma.cmd, true);
break;
}
}
edu_mmio_write函數(shù)展示了一個(gè)虛擬機(jī)在寫設(shè)備MMIO地址時(shí)QEMU中設(shè)備模擬的典型行為。
(1)首先,需要檢查讀寫地址以及大小是否在范圍之內(nèi)。代碼片段如下:
if (addr < 0x80 && size != 4) {
return;
}
if (addr >= 0x80 && size != 4 && size != 8) {
return;
}
(2)然后,根據(jù)具體的地址來(lái)進(jìn)行適當(dāng)?shù)男袨椤?/p>
這些行為可以是簡(jiǎn)單地設(shè)置一個(gè)值,如這里的寫0x04地址,代碼片段如下:
case 0x04:
edu->addr4 = ~val;
break;
也可以是將中斷設(shè)置為高電平(寫0x60地址)或者設(shè)置為低電平(寫0x64地址),代碼片段如下:
case 0x60:
edu_raise_irq(edu, val);
break;
case 0x64:
edu_lower_irq(edu, val);
break;
還可以是通過(guò)dma讀寫設(shè)備虛擬機(jī)的物理地址(寫0x80地址),代碼片段如下:
case 0x80:
dma_rw(edu, true, &val, &edu->dma.src, false);
break;
對(duì)于read回調(diào)函數(shù),也是類似的機(jī)制。這里僅給出edu_mmio_read函數(shù)源碼,在hw/misc/edu.c中,代碼如下:文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-813522.html
static uint64_t edu_mmio_read(void *opaque, hwaddr addr, unsigned size)
{
EduState *edu = opaque;
uint64_t val = ~0ULL;
if (addr < 0x80 && size != 4) {
return val;
}
if (addr >= 0x80 && size != 4 && size != 8) {
return val;
}
switch (addr) {
case 0x00:
val = 0x010000edu;
break;
case 0x04:
val = edu->addr4;
break;
case 0x08:
qemu_mutex_lock(&edu->thr_mutex);
val = edu->fact;
qemu_mutex_unlock(&edu->thr_mutex);
break;
case 0x20:
val = qatomic_read(&edu->status);
break;
case 0x24:
val = edu->irq_status;
break;
case 0x80:
dma_rw(edu, false, &val, &edu->dma.src, false);
break;
case 0x88:
dma_rw(edu, false, &val, &edu->dma.dst, false);
break;
case 0x90:
dma_rw(edu, false, &val, &edu->dma.cnt, false);
break;
case 0x98:
dma_rw(edu, false, &val, &edu->dma.cmd, false);
break;
}
return val;
}
欲知后事如何,且看下回分解。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-813522.html
到了這里,關(guān)于QEMU源碼全解析 —— PCI設(shè)備模擬(7)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!