1 Star 0 Fork 1

Freeze/kvmtool

forked from 会飞的小胖/kvmtool 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
pci.c 12.57 KB
一键复制 编辑 原始数据 按行查看 历史
#include "kvm/devices.h"
#include "kvm/pci.h"
#include "kvm/ioport.h"
#include "kvm/irq.h"
#include "kvm/util.h"
#include "kvm/kvm.h"
#include <linux/err.h>
#include <assert.h>
static u32 pci_config_address_bits;
/* This is within our PCI gap - in an unused area.
* Note this is a PCI *bus address*, is used to assign BARs etc.!
* (That's why it can still 32bit even with 64bit guests-- 64bit
* PCI isn't currently supported.)
*/
static u32 mmio_blocks = KVM_PCI_MMIO_AREA;
static u16 io_port_blocks = PCI_IOPORT_START;
u16 pci_get_io_port_block(u32 size)
{
u16 port = ALIGN(io_port_blocks, PCI_IO_SIZE);
io_port_blocks = port + size;
return port;
}
/*
* BARs must be naturally aligned, so enforce this in the allocator.
*/
u32 pci_get_mmio_block(u32 size)
{
u32 block = ALIGN(mmio_blocks, size);
mmio_blocks = block + size;
return block;
}
void *pci_find_cap(struct pci_device_header *hdr, u8 cap_type)
{
u8 pos;
struct pci_cap_hdr *cap;
pci_for_each_cap(pos, cap, hdr) {
if (cap->type == cap_type)
return cap;
}
return NULL;
}
int pci__assign_irq(struct pci_device_header *pci_hdr)
{
/*
* PCI supports only INTA#,B#,C#,D# per device.
*
* A#,B#,C#,D# are allowed for multifunctional devices so stick
* with A# for our single function devices.
*/
pci_hdr->irq_pin = 1;
pci_hdr->irq_line = irq__alloc_line();
if (!pci_hdr->irq_type)
pci_hdr->irq_type = IRQ_TYPE_EDGE_RISING;
return pci_hdr->irq_line;
}
static bool pci_bar_is_implemented(struct pci_device_header *pci_hdr, int bar_num)
{
return pci__bar_size(pci_hdr, bar_num);
}
static bool pci_bar_is_active(struct pci_device_header *pci_hdr, int bar_num)
{
return pci_hdr->bar_active[bar_num];
}
static void *pci_config_address_ptr(u16 port)
{
unsigned long offset;
void *base;
offset = port - PCI_CONFIG_ADDRESS;
base = &pci_config_address_bits;
return base + offset;
}
static void pci_config_address_mmio(struct kvm_cpu *vcpu, u64 addr, u8 *data,
u32 len, u8 is_write, void *ptr)
{
void *p = pci_config_address_ptr(addr);
if (is_write)
memcpy(p, data, len);
else
memcpy(data, p, len);
}
static bool pci_device_exists(u8 bus_number, u8 device_number, u8 function_number)
{
union pci_config_address pci_config_address;
pci_config_address.w = ioport__read32(&pci_config_address_bits);
if (pci_config_address.bus_number != bus_number)
return false;
if (pci_config_address.function_number != function_number)
return false;
return !IS_ERR_OR_NULL(device__find_dev(DEVICE_BUS_PCI, device_number));
}
static void pci_config_data_mmio(struct kvm_cpu *vcpu, u64 addr, u8 *data,
u32 len, u8 is_write, void *kvm)
{
union pci_config_address pci_config_address;
if (len > 4)
len = 4;
pci_config_address.w = ioport__read32(&pci_config_address_bits);
/*
* If someone accesses PCI configuration space offsets that are not
* aligned to 4 bytes, it uses ioports to signify that.
*/
pci_config_address.reg_offset = addr - PCI_CONFIG_DATA;
if (is_write)
pci__config_wr(vcpu->kvm, pci_config_address, data, len);
else
pci__config_rd(vcpu->kvm, pci_config_address, data, len);
}
static int pci_activate_bar(struct kvm *kvm, struct pci_device_header *pci_hdr,
int bar_num)
{
int r = 0;
if (pci_bar_is_active(pci_hdr, bar_num))
goto out;
r = pci_hdr->bar_activate_fn(kvm, pci_hdr, bar_num, pci_hdr->data);
if (r < 0) {
pci_dev_warn(pci_hdr, "Error activating emulation for BAR %d",
bar_num);
goto out;
}
pci_hdr->bar_active[bar_num] = true;
out:
return r;
}
static int pci_deactivate_bar(struct kvm *kvm, struct pci_device_header *pci_hdr,
int bar_num)
{
int r = 0;
if (!pci_bar_is_active(pci_hdr, bar_num))
goto out;
r = pci_hdr->bar_deactivate_fn(kvm, pci_hdr, bar_num, pci_hdr->data);
if (r < 0) {
pci_dev_warn(pci_hdr, "Error deactivating emulation for BAR %d",
bar_num);
goto out;
}
pci_hdr->bar_active[bar_num] = false;
out:
return r;
}
static void pci_config_command_wr(struct kvm *kvm,
struct pci_device_header *pci_hdr,
u16 new_command)
{
int i;
bool toggle_io, toggle_mem;
toggle_io = (pci_hdr->command ^ new_command) & PCI_COMMAND_IO;
toggle_mem = (pci_hdr->command ^ new_command) & PCI_COMMAND_MEMORY;
for (i = 0; i < 6; i++) {
if (!pci_bar_is_implemented(pci_hdr, i))
continue;
if (toggle_io && pci__bar_is_io(pci_hdr, i)) {
if (__pci__io_space_enabled(new_command))
pci_activate_bar(kvm, pci_hdr, i);
else
pci_deactivate_bar(kvm, pci_hdr, i);
}
if (toggle_mem && pci__bar_is_memory(pci_hdr, i)) {
if (__pci__memory_space_enabled(new_command))
pci_activate_bar(kvm, pci_hdr, i);
else
pci_deactivate_bar(kvm, pci_hdr, i);
}
}
pci_hdr->command = new_command;
}
static int pci_toggle_bar_regions(bool activate, struct kvm *kvm, u32 start, u32 size)
{
struct device_header *dev_hdr;
struct pci_device_header *tmp_hdr;
u32 tmp_start, tmp_size;
int i, r;
dev_hdr = device__first_dev(DEVICE_BUS_PCI);
while (dev_hdr) {
tmp_hdr = dev_hdr->data;
for (i = 0; i < 6; i++) {
if (!pci_bar_is_implemented(tmp_hdr, i))
continue;
tmp_start = pci__bar_address(tmp_hdr, i);
tmp_size = pci__bar_size(tmp_hdr, i);
if (tmp_start + tmp_size <= start ||
tmp_start >= start + size)
continue;
if (activate)
r = pci_activate_bar(kvm, tmp_hdr, i);
else
r = pci_deactivate_bar(kvm, tmp_hdr, i);
if (r < 0)
return r;
}
dev_hdr = device__next_dev(dev_hdr);
}
return 0;
}
static inline int pci_activate_bar_regions(struct kvm *kvm, u32 start, u32 size)
{
return pci_toggle_bar_regions(true, kvm, start, size);
}
static inline int pci_deactivate_bar_regions(struct kvm *kvm, u32 start, u32 size)
{
return pci_toggle_bar_regions(false, kvm, start, size);
}
static void pci_config_bar_wr(struct kvm *kvm,
struct pci_device_header *pci_hdr, int bar_num,
u32 value)
{
u32 old_addr, new_addr, bar_size;
u32 mask;
int r;
if (pci__bar_is_io(pci_hdr, bar_num))
mask = (u32)PCI_BASE_ADDRESS_IO_MASK;
else
mask = (u32)PCI_BASE_ADDRESS_MEM_MASK;
/*
* If the kernel masks the BAR, it will expect to find the size of the
* BAR there next time it reads from it. After the kernel reads the
* size, it will write the address back.
*
* According to the PCI local bus specification REV 3.0: The number of
* upper bits that a device actually implements depends on how much of
* the address space the device will respond to. A device that wants a 1
* MB memory address space (using a 32-bit base address register) would
* build the top 12 bits of the address register, hardwiring the other
* bits to 0.
*
* Furthermore, software can determine how much address space the device
* requires by writing a value of all 1's to the register and then
* reading the value back. The device will return 0's in all don't-care
* address bits, effectively specifying the address space required.
*
* Software computes the size of the address space with the formula
* S = ~B + 1, where S is the memory size and B is the value read from
* the BAR. This means that the BAR value that kvmtool should return is
* B = ~(S - 1).
*/
if (value == 0xffffffff) {
value = ~(pci__bar_size(pci_hdr, bar_num) - 1);
/* Preserve the special bits. */
value = (value & mask) | (pci_hdr->bar[bar_num] & ~mask);
pci_hdr->bar[bar_num] = value;
return;
}
value = (value & mask) | (pci_hdr->bar[bar_num] & ~mask);
/* Don't toggle emulation when region type access is disbled. */
if (pci__bar_is_io(pci_hdr, bar_num) &&
!pci__io_space_enabled(pci_hdr)) {
pci_hdr->bar[bar_num] = value;
return;
}
if (pci__bar_is_memory(pci_hdr, bar_num) &&
!pci__memory_space_enabled(pci_hdr)) {
pci_hdr->bar[bar_num] = value;
return;
}
/*
* BAR reassignment can be done while device access is enabled and
* memory regions for different devices can overlap as long as no access
* is made to the overlapping memory regions. To implement BAR
* reasignment, we deactivate emulation for the region described by the
* BAR value that the guest is changing, we disable emulation for the
* regions that overlap with the new one (by scanning through all PCI
* devices), we enable emulation for the new BAR value and finally we
* enable emulation for all device regions that were overlapping with
* the old value.
*/
old_addr = pci__bar_address(pci_hdr, bar_num);
new_addr = __pci__bar_address(value);
bar_size = pci__bar_size(pci_hdr, bar_num);
r = pci_deactivate_bar(kvm, pci_hdr, bar_num);
if (r < 0)
return;
r = pci_deactivate_bar_regions(kvm, new_addr, bar_size);
if (r < 0) {
/*
* We cannot update the BAR because of an overlapping region
* that failed to deactivate emulation, so keep the old BAR
* value and re-activate emulation for it.
*/
pci_activate_bar(kvm, pci_hdr, bar_num);
return;
}
pci_hdr->bar[bar_num] = value;
r = pci_activate_bar(kvm, pci_hdr, bar_num);
if (r < 0) {
/*
* New region cannot be emulated, re-enable the regions that
* were overlapping.
*/
pci_activate_bar_regions(kvm, new_addr, bar_size);
return;
}
pci_activate_bar_regions(kvm, old_addr, bar_size);
}
void pci__config_wr(struct kvm *kvm, union pci_config_address addr, void *data, int size)
{
void *base;
u8 bar, offset;
struct pci_device_header *pci_hdr;
u8 dev_num = addr.device_number;
u32 value = 0;
if (!pci_device_exists(addr.bus_number, dev_num, 0))
return;
offset = addr.w & PCI_DEV_CFG_MASK;
base = pci_hdr = device__find_dev(DEVICE_BUS_PCI, dev_num)->data;
if (pci_hdr->cfg_ops.write)
pci_hdr->cfg_ops.write(kvm, pci_hdr, offset, data, size);
/*
* legacy hack: ignore writes to uninitialized regions (e.g. ROM BAR).
* Not very nice but has been working so far.
*/
if (*(u32 *)(base + offset) == 0)
return;
if (offset == PCI_COMMAND) {
memcpy(&value, data, size);
pci_config_command_wr(kvm, pci_hdr, (u16)value);
return;
}
bar = (offset - PCI_BAR_OFFSET(0)) / sizeof(u32);
if (bar < 6) {
memcpy(&value, data, size);
pci_config_bar_wr(kvm, pci_hdr, bar, value);
return;
}
memcpy(base + offset, data, size);
}
void pci__config_rd(struct kvm *kvm, union pci_config_address addr, void *data, int size)
{
u8 offset;
struct pci_device_header *pci_hdr;
u8 dev_num = addr.device_number;
if (pci_device_exists(addr.bus_number, dev_num, 0)) {
pci_hdr = device__find_dev(DEVICE_BUS_PCI, dev_num)->data;
offset = addr.w & PCI_DEV_CFG_MASK;
if (pci_hdr->cfg_ops.read)
pci_hdr->cfg_ops.read(kvm, pci_hdr, offset, data, size);
memcpy(data, (void *)pci_hdr + offset, size);
} else {
memset(data, 0xff, size);
}
}
static void pci_config_mmio_access(struct kvm_cpu *vcpu, u64 addr, u8 *data,
u32 len, u8 is_write, void *kvm)
{
union pci_config_address cfg_addr;
addr -= KVM_PCI_CFG_AREA;
cfg_addr.w = (u32)addr;
cfg_addr.enable_bit = 1;
if (len > 4)
len = 4;
if (is_write)
pci__config_wr(kvm, cfg_addr, data, len);
else
pci__config_rd(kvm, cfg_addr, data, len);
}
struct pci_device_header *pci__find_dev(u8 dev_num)
{
struct device_header *hdr = device__find_dev(DEVICE_BUS_PCI, dev_num);
if (IS_ERR_OR_NULL(hdr))
return NULL;
return hdr->data;
}
int pci__register_bar_regions(struct kvm *kvm, struct pci_device_header *pci_hdr,
bar_activate_fn_t bar_activate_fn,
bar_deactivate_fn_t bar_deactivate_fn, void *data)
{
int i, r;
assert(bar_activate_fn && bar_deactivate_fn);
pci_hdr->bar_activate_fn = bar_activate_fn;
pci_hdr->bar_deactivate_fn = bar_deactivate_fn;
pci_hdr->data = data;
for (i = 0; i < 6; i++) {
if (!pci_bar_is_implemented(pci_hdr, i))
continue;
assert(!pci_bar_is_active(pci_hdr, i));
if (pci__bar_is_io(pci_hdr, i) &&
pci__io_space_enabled(pci_hdr)) {
r = pci_activate_bar(kvm, pci_hdr, i);
if (r < 0)
return r;
}
if (pci__bar_is_memory(pci_hdr, i) &&
pci__memory_space_enabled(pci_hdr)) {
r = pci_activate_bar(kvm, pci_hdr, i);
if (r < 0)
return r;
}
}
return 0;
}
int pci__init(struct kvm *kvm)
{
int r;
r = kvm__register_pio(kvm, PCI_CONFIG_DATA, 4,
pci_config_data_mmio, NULL);
if (r < 0)
return r;
r = kvm__register_pio(kvm, PCI_CONFIG_ADDRESS, 4,
pci_config_address_mmio, NULL);
if (r < 0)
goto err_unregister_data;
r = kvm__register_mmio(kvm, KVM_PCI_CFG_AREA, PCI_CFG_SIZE, false,
pci_config_mmio_access, kvm);
if (r < 0)
goto err_unregister_addr;
return 0;
err_unregister_addr:
kvm__deregister_pio(kvm, PCI_CONFIG_ADDRESS);
err_unregister_data:
kvm__deregister_pio(kvm, PCI_CONFIG_DATA);
return r;
}
dev_base_init(pci__init);
int pci__exit(struct kvm *kvm)
{
kvm__deregister_pio(kvm, PCI_CONFIG_DATA);
kvm__deregister_pio(kvm, PCI_CONFIG_ADDRESS);
return 0;
}
dev_base_exit(pci__exit);
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/FreezeFu/kvmtool.git
git@gitee.com:FreezeFu/kvmtool.git
FreezeFu
kvmtool
kvmtool
master

搜索帮助