Page MenuHomeFreeBSD

D26506.id77287.diff
No OneTemporary

D26506.id77287.diff

Index: lib/libvmmapi/vmmapi.h
===================================================================
--- lib/libvmmapi/vmmapi.h
+++ lib/libvmmapi/vmmapi.h
@@ -178,6 +178,8 @@
vm_paddr_t gpa, size_t len, vm_paddr_t hpa);
int vm_unmap_pptdev_mmio(struct vmctx *ctx, int bus, int slot, int func,
vm_paddr_t gpa, size_t len);
+int vm_get_vbios(struct vmctx *ctx, int bus, int slot, int func,
+ uint16_t vendor, uint16_t dev_id, uint64_t *bios, uint64_t *size);
int vm_setup_pptdev_msi(struct vmctx *ctx, int vcpu, int bus, int slot,
int func, uint64_t addr, uint64_t msg, int numvec);
int vm_setup_pptdev_msix(struct vmctx *ctx, int vcpu, int bus, int slot,
Index: lib/libvmmapi/vmmapi.c
===================================================================
--- lib/libvmmapi/vmmapi.c
+++ lib/libvmmapi/vmmapi.c
@@ -1000,6 +1000,30 @@
return (ioctl(ctx->fd, VM_UNMAP_PPTDEV_MMIO, &pptmmio));
}
+int
+vm_get_vbios(struct vmctx *ctx, int bus, int slot, int func,
+ uint16_t vendor, uint16_t dev_id, uint64_t *bios, uint64_t *size)
+{
+ int error;
+ struct vm_vbios vbios;
+
+ bzero(&vbios, sizeof(vbios));
+ vbios.bus = bus;
+ vbios.slot = slot;
+ vbios.func = func;
+ vbios.vendor = vendor;
+ vbios.dev_id = dev_id;
+
+ error = ioctl(ctx->fd, VM_GET_VBIOS, &vbios);
+
+ if (bios)
+ *bios = vbios.bios;
+ if (size)
+ *size = vbios.size;
+
+ return (error);
+}
+
int
vm_setup_pptdev_msi(struct vmctx *ctx, int vcpu, int bus, int slot, int func,
uint64_t addr, uint64_t msg, int numvec)
@@ -1660,7 +1684,7 @@
VM_IOAPIC_PULSE_IRQ, VM_IOAPIC_PINCOUNT, VM_ISA_ASSERT_IRQ,
VM_ISA_DEASSERT_IRQ, VM_ISA_PULSE_IRQ, VM_ISA_SET_IRQ_TRIGGER,
VM_SET_CAPABILITY, VM_GET_CAPABILITY, VM_BIND_PPTDEV,
- VM_UNBIND_PPTDEV, VM_MAP_PPTDEV_MMIO, VM_UNMAP_PPTDEV_MMIO, VM_PPTDEV_MSI,
+ VM_UNBIND_PPTDEV, VM_MAP_PPTDEV_MMIO, VM_UNMAP_PPTDEV_MMIO, VM_GET_VBIOS, VM_PPTDEV_MSI,
VM_PPTDEV_MSIX, VM_INJECT_NMI, VM_STATS, VM_STAT_DESC,
VM_SET_X2APIC_STATE, VM_GET_X2APIC_STATE,
VM_GET_HPET_CAPABILITIES, VM_GET_GPA_PMAP, VM_GLA2GPA,
Index: sys/amd64/include/vmm_dev.h
===================================================================
--- sys/amd64/include/vmm_dev.h
+++ sys/amd64/include/vmm_dev.h
@@ -141,6 +141,16 @@
size_t len;
};
+struct vm_vbios {
+ int bus;
+ int slot;
+ int func;
+ uint16_t vendor;
+ uint16_t dev_id;
+ uint64_t bios;
+ uint64_t size;
+};
+
struct vm_pptdev_msi {
int vcpu;
int bus;
@@ -302,6 +312,7 @@
IOCNUM_UNMAP_PPTDEV_MMIO = 45,
IOCNUM_PPTDEV_MSI = 43,
IOCNUM_PPTDEV_MSIX = 44,
+ IOCNUM_GET_VBIOS = 46,
/* statistics */
IOCNUM_VM_STATS = 50,
@@ -412,6 +423,8 @@
_IOW('v', IOCNUM_MAP_PPTDEV_MMIO, struct vm_pptdev_mmio)
#define VM_UNMAP_PPTDEV_MMIO \
_IOW('v', IOCNUM_UNMAP_PPTDEV_MMIO, struct vm_pptdev_mmio)
+#define VM_GET_VBIOS \
+ _IOWR('v', IOCNUM_GET_VBIOS, struct vm_vbios)
#define VM_PPTDEV_MSI \
_IOW('v', IOCNUM_PPTDEV_MSI, struct vm_pptdev_msi)
#define VM_PPTDEV_MSIX \
Index: sys/amd64/vmm/amd/amdgpu.h
===================================================================
--- /dev/null
+++ sys/amd64/vmm/amd/amdgpu.h
@@ -0,0 +1,10 @@
+
+#ifndef _AMD_AMDGPU_H_
+#define _AMD_AMDGPU_H_
+
+#include <machine/vmm_dev.h>
+
+int vm_amdgpu_get_vbios(struct vm *vm, int bus, int slot, int func,
+ uint16_t vendor, uint16_t dev_id, uint64_t *bios, uint64_t *size);
+
+#endif /* !_AMD_AMDGPU_H_ */
\ No newline at end of file
Index: sys/amd64/vmm/amd/amdgpu.c
===================================================================
--- /dev/null
+++ sys/amd64/vmm/amd/amdgpu.c
@@ -0,0 +1,257 @@
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/errno.h>
+#include <sys/malloc.h>
+
+
+#include <sys/param.h>
+#include <sys/bitstring.h>
+#include <sys/bus.h>
+#include <sys/systm.h>
+#include <sys/kernel.h>
+#include <sys/ktr.h>
+#include <sys/lock.h>
+#include <sys/malloc.h>
+#include <sys/mman.h>
+#include <sys/mutex.h>
+#include <sys/proc.h>
+#include <sys/rangeset.h>
+#include <sys/rwlock.h>
+#include <sys/sbuf.h>
+#include <sys/sx.h>
+#include <sys/turnstile.h>
+#include <sys/vmem.h>
+#include <sys/vmmeter.h>
+#include <sys/sched.h>
+#include <sys/sysctl.h>
+#include <sys/smp.h>
+
+#include <vm/vm.h>
+#include <vm/vm_param.h>
+#include <vm/vm_kern.h>
+#include <vm/vm_page.h>
+#include <vm/vm_map.h>
+#include <vm/vm_object.h>
+#include <vm/vm_extern.h>
+#include <vm/vm_pageout.h>
+#include <vm/vm_pager.h>
+#include <vm/vm_phys.h>
+#include <vm/vm_radix.h>
+#include <vm/vm_reserv.h>
+#include <vm/uma.h>
+
+#include <machine/pmap.h>
+#include <machine/vmm.h>
+#include <machine/vmparam.h>
+
+#include "amd/amdgpu.h"
+#include "contrib/dev/acpica/include/acpi.h"
+#include "contrib/dev/acpica/include/acpixf.h"
+
+#define GFP_NATIVE_MASK (M_NOWAIT | M_WAITOK | M_USE_RESERVE | M_ZERO)
+#define GFP_KERNEL M_WAITOK
+
+//MALLOC_DECLARE(M_KMALLOC);
+MALLOC_DECLARE(M_VMMDEV);
+
+typedef unsigned gfp_t;
+
+static inline gfp_t
+linux_check_m_flags(gfp_t flags)
+{
+ const gfp_t m = M_NOWAIT | M_WAITOK;
+
+ /* make sure either M_NOWAIT or M_WAITOK is set */
+ if ((flags & m) == 0)
+ flags |= M_NOWAIT;
+ else if ((flags & m) == m)
+ flags &= ~M_WAITOK;
+
+ /* mask away LinuxKPI specific flags */
+ return (flags & GFP_NATIVE_MASK);
+}
+
+static inline void *
+kmalloc(size_t size, gfp_t flags)
+{
+ return (malloc(size, M_VMMDEV, linux_check_m_flags(flags)));
+}
+
+static inline void
+kfree(const void *ptr)
+{
+ free(__DECONST(void *, ptr), M_VMMDEV);
+}
+
+static inline void *
+kmemdup(const void *src, size_t len, gfp_t gfp)
+{
+ void *dst;
+
+ dst = kmalloc(roundup2(len, PAGE_SIZE), gfp);
+ if (dst != NULL) {
+ memcpy(dst, src, len);
+ memset((char *)dst + len, 0, roundup2(len, PAGE_SIZE) - len);
+ }
+ return (dst);
+}
+
+#define AMD_VBIOS_SIGNATURE " 761295520"
+#define AMD_VBIOS_SIGNATURE_OFFSET 0x30
+#define AMD_VBIOS_SIGNATURE_SIZE sizeof(AMD_VBIOS_SIGNATURE)
+#define AMD_VBIOS_SIGNATURE_END (AMD_VBIOS_SIGNATURE_OFFSET + AMD_VBIOS_SIGNATURE_SIZE)
+#define AMD_IS_VALID_VBIOS(p) ((p)[0] == 0x55 && (p)[1] == 0xAA)
+#define AMD_VBIOS_LENGTH(p) ((p)[2] << 9)
+
+#define acpi_get_table AcpiGetTable
+
+typedef struct {
+ uint32_t Signature;
+ uint32_t TableLength; //Length
+ uint8_t Revision;
+ uint8_t Checksum;
+ uint8_t OemId[6];
+ uint8_t OemTableId[8]; //UINT64 OemTableId;
+ uint32_t OemRevision;
+ uint32_t CreatorId;
+ uint32_t CreatorRevision;
+} AMD_ACPI_DESCRIPTION_HEADER;
+
+typedef struct {
+ AMD_ACPI_DESCRIPTION_HEADER SHeader;
+ uint8_t TableUUID[16]; //0x24
+ uint32_t VBIOSImageOffset; //0x34. Offset to the first GOP_VBIOS_CONTENT block from the beginning of the stucture.
+ uint32_t Lib1ImageOffset; //0x38. Offset to the first GOP_LIB1_CONTENT block from the beginning of the stucture.
+ uint32_t Reserved[4]; //0x3C
+}UEFI_ACPI_VFCT;
+
+typedef struct {
+ uint32_t PCIBus; //0x4C
+ uint32_t PCIDevice; //0x50
+ uint32_t PCIFunction; //0x54
+ uint16_t VendorID; //0x58
+ uint16_t DeviceID; //0x5A
+ uint16_t SSVID; //0x5C
+ uint16_t SSID; //0x5E
+ uint32_t Revision; //0x60
+ uint32_t ImageLength; //0x64
+}VFCT_IMAGE_HEADER;
+
+
+typedef struct {
+ VFCT_IMAGE_HEADER VbiosHeader;
+ uint8_t VbiosContent[1];
+}GOP_VBIOS_CONTENT;
+
+/* Check if current bios is an ATOM BIOS.
+ * Return true if it is ATOM BIOS. Otherwise, return false.
+ */
+static bool check_atom_bios(uint8_t *bios, size_t size)
+{
+ uint16_t tmp, bios_header_start;
+
+ if (!bios || size < 0x49) {
+ // vbios mem is null or mem size is wrong
+ return false;
+ }
+
+ if (!AMD_IS_VALID_VBIOS(bios)) {
+ // BIOS signature incorrect
+ return false;
+ }
+
+ bios_header_start = bios[0x48] | (bios[0x49] << 8);
+ if (!bios_header_start) {
+ // Can't locate bios header
+ return false;
+ }
+
+ tmp = bios_header_start + 4;
+ if (size < tmp) {
+ // BIOS header is broken
+ return false;
+ }
+
+ if (!memcmp(bios + tmp, "ATOM", 4) ||
+ !memcmp(bios + tmp, "MOTA", 4)) {
+ // ATOMBIOS detected
+ return true;
+ }
+
+ return false;
+}
+
+static int
+amdgpu_get_vbios_vfct(struct vm *vm, int bus, int slot, int func,
+ uint16_t vendor, uint16_t dev_id, uint64_t *bios, uint64_t *size)
+{
+ if (vm == NULL || bios == NULL || size == NULL)
+ return EINVAL;
+
+ struct acpi_table_header *hdr;
+ uint32_t tbl_size;
+ UEFI_ACPI_VFCT *vfct;
+ unsigned offset;
+
+ if (!ACPI_SUCCESS(acpi_get_table("VFCT", 1, &hdr)))
+ return ENOENT;
+
+ tbl_size = hdr->Length;
+
+ if (tbl_size < sizeof(UEFI_ACPI_VFCT))
+ return ENODEV;
+
+ vfct = (UEFI_ACPI_VFCT *)hdr;
+ offset = vfct->VBIOSImageOffset;
+
+ while (offset < tbl_size) {
+ GOP_VBIOS_CONTENT *vbios = (GOP_VBIOS_CONTENT *)((char *)hdr + offset);
+ VFCT_IMAGE_HEADER *vhdr = &vbios->VbiosHeader;
+
+ offset += sizeof(VFCT_IMAGE_HEADER);
+ if (offset > tbl_size)
+ return ENODEV;
+
+ offset += vhdr->ImageLength;
+ if (offset > tbl_size)
+ return ENODEV;
+
+ if (vhdr->ImageLength &&
+ vhdr->PCIBus == bus &&
+ vhdr->PCIDevice == slot &&
+ vhdr->PCIFunction == func &&
+ vhdr->VendorID == vendor &&
+ vhdr->DeviceID == dev_id) {
+ *bios = (uint64_t)kmemdup(&vbios->VbiosContent,
+ vhdr->ImageLength,
+ GFP_KERNEL);
+
+ if (!check_atom_bios((uint8_t *)*bios, vhdr->ImageLength)) {
+ kfree((void *)*bios);
+ return ENODEV;
+ }
+ *size = vhdr->ImageLength;
+ return 0;
+ }
+ }
+
+ return ENOENT;
+}
+
+int
+vm_amdgpu_get_vbios(struct vm *vm, int bus, int slot, int func,
+ uint16_t vendor, uint16_t dev_id, uint64_t *bios, uint64_t *size)
+{
+ if (amdgpu_get_vbios_vfct(vm, bus, slot, func, vendor, dev_id, bios, size) == 0)
+ goto done;
+
+ return ENOENT;
+
+done:
+ *bios = vtophys(*bios);
+
+ return (0);
+}
Index: sys/amd64/vmm/vmm_dev.c
===================================================================
--- sys/amd64/vmm/vmm_dev.c
+++ sys/amd64/vmm/vmm_dev.c
@@ -60,6 +60,7 @@
#include <machine/vmm_snapshot.h>
#include <x86/apicreg.h>
+#include "amd/amdgpu.h"
#include "vmm_lapic.h"
#include "vmm_stat.h"
#include "vmm_mem.h"
@@ -366,6 +367,7 @@
struct vm_capability *vmcap;
struct vm_pptdev *pptdev;
struct vm_pptdev_mmio *pptmmio;
+ struct vm_vbios *vbios;
struct vm_pptdev_msi *pptmsi;
struct vm_pptdev_msix *pptmsix;
struct vm_nmi *vmnmi;
@@ -436,6 +438,7 @@
case VM_MAP_PPTDEV_MMIO:
case VM_UNMAP_PPTDEV_MMIO:
+ case VM_GET_VBIOS:
case VM_BIND_PPTDEV:
case VM_UNBIND_PPTDEV:
#ifdef COMPAT_FREEBSD12
@@ -526,6 +529,12 @@
error = ppt_unmap_mmio(sc->vm, pptmmio->bus, pptmmio->slot,
pptmmio->func, pptmmio->gpa, pptmmio->len);
break;
+ case VM_GET_VBIOS:
+ vbios = (struct vm_vbios *)data;
+ // currently only amd cpus are supported
+ error = vm_amdgpu_get_vbios(sc->vm, vbios->bus, vbios->slot, vbios->func,
+ vbios->vendor, vbios->dev_id, &vbios->bios, &vbios->size);
+ break;
case VM_BIND_PPTDEV:
pptdev = (struct vm_pptdev *)data;
error = vm_assign_pptdev(sc->vm, pptdev->bus, pptdev->slot,
Index: sys/dev/pci/vga_pci.c
===================================================================
--- sys/dev/pci/vga_pci.c
+++ sys/dev/pci/vga_pci.c
@@ -172,21 +172,21 @@
volatile unsigned char *bios;
int i, rid, found;
-#if defined(__amd64__) || defined(__i386__)
- if (vga_pci_is_boot_display(dev)) {
- /*
- * On x86, the System BIOS copy the default display
- * device's Video BIOS at a fixed location in system
- * memory (0xC0000, 128 kBytes long) at boot time.
- *
- * We use this copy for the default boot device, because
- * the original ROM may not be valid after boot.
- */
-
- *size = VGA_PCI_BIOS_SHADOW_SIZE;
- return (pmap_mapbios(VGA_PCI_BIOS_SHADOW_ADDR, *size));
- }
-#endif
+// #if defined(__amd64__) || defined(__i386__)
+// if (vga_pci_is_boot_display(dev)) {
+// /*
+// * On x86, the System BIOS copy the default display
+// * device's Video BIOS at a fixed location in system
+// * memory (0xC0000, 128 kBytes long) at boot time.
+// *
+// * We use this copy for the default boot device, because
+// * the original ROM may not be valid after boot.
+// */
+
+// *size = VGA_PCI_BIOS_SHADOW_SIZE;
+// return (pmap_mapbios(VGA_PCI_BIOS_SHADOW_ADDR, *size));
+// }
+// #endif
pcib = device_get_parent(device_get_parent(dev));
if (device_get_devclass(device_get_parent(pcib)) ==
Index: sys/modules/vmm/Makefile
===================================================================
--- sys/modules/vmm/Makefile
+++ sys/modules/vmm/Makefile
@@ -56,7 +56,8 @@
npt.c \
ivrs_drv.c \
amdvi_hw.c \
- svm_msr.c
+ svm_msr.c \
+ amdgpu.c
.if ${KERN_OPTS:MBHYVE_SNAPSHOT} != ""
SRCS+= vmm_snapshot.c
Index: usr.sbin/bhyve/pci_emul.h
===================================================================
--- usr.sbin/bhyve/pci_emul.h
+++ usr.sbin/bhyve/pci_emul.h
@@ -117,6 +117,12 @@
PENDING
};
+struct pci_vbiosemu {
+ uint64_t hpa;
+ uint64_t len;
+ uint64_t gpa;
+};
+
struct pci_devinst {
struct pci_devemu *pi_d;
struct vmctx *pi_vmctx;
@@ -159,6 +165,8 @@
u_char pi_cfgdata[PCI_REGMAX + 1];
struct pcibar pi_bar[PCI_BARMAX + 1];
+
+ struct pci_vbiosemu vbios;
};
struct msicap {
Index: usr.sbin/bhyve/pci_passthru.c
===================================================================
--- usr.sbin/bhyve/pci_passthru.c
+++ usr.sbin/bhyve/pci_passthru.c
@@ -854,6 +854,15 @@
passthru_cfgwrite_igd_gen5_75(struct vmctx *ctx, int vcpu, struct pci_devinst *pi,
int coff, int bytes, uint32_t val);
+static int
+passthru_init_apu(struct vmctx *ctx, struct passthru_softc *sc);
+static int
+passthru_cfgread_apu(struct vmctx *ctx, int vcpu, struct pci_devinst *pi,
+ int coff, int bytes, uint32_t *rv);
+static int
+passthru_cfgwrite_apu(struct vmctx *ctx, int vcpu, struct pci_devinst *pi,
+ int coff, int bytes, uint32_t val);
+
static int
passthru_init(struct vmctx *ctx, struct pci_devinst *pi, char *opts)
{
@@ -959,6 +968,14 @@
}
}
+ // init amd apu
+ if (opt != NULL && strcmp(opt, "apu") == 0) {
+ if ((error = passthru_init_apu(ctx, sc)) != 0) {
+ warnx("Failed to init apu");
+ goto done;
+ }
+ }
+
error = 0; /* success */
done:
if (error) {
@@ -1325,6 +1342,37 @@
return (error);
}
+static int
+passthru_init_apu(struct vmctx *ctx, struct passthru_softc *sc)
+{
+ int error;
+
+ uint16_t vendor = read_config(&sc->psc_sel, PCIR_VENDOR, 2);
+ uint16_t dev_id = read_config(&sc->psc_sel, PCIR_DEVICE, 2);
+
+ if ((error = vm_get_vbios(ctx, sc->psc_sel.pc_bus, sc->psc_sel.pc_dev, sc->psc_sel.pc_func, vendor, dev_id, &sc->psc_pi->vbios.hpa, &sc->psc_pi->vbios.len)) != 0) {
+ warnx("Failed to load vBIOS (%d)", error);
+ goto done;
+ }
+ if ((sc->psc_pi->vbios.gpa = pci_emul_alloc_mmio(PCIBAR_MEM32, roundup2(sc->psc_pi->vbios.len, PAGE_SIZE), 0x7FF)) == 0) {
+ warnx("Failed to alloc guest memory for vBIOS");
+ error = -ENOMEM;
+ goto done;
+ }
+ if ((error = vm_map_pptdev_mmio(ctx, sc->psc_sel.pc_bus, sc->psc_sel.pc_dev, sc->psc_sel.pc_func, sc->psc_pi->vbios.gpa, roundup2(sc->psc_pi->vbios.len, PAGE_SIZE), sc->psc_pi->vbios.hpa)) != 0) {
+ warnx("Failed to map vBIOS to guest");
+ goto done;
+ }
+
+ pci_set_cfgdata32(sc->psc_pi, PCIR_BIOS, sc->psc_pi->vbios.gpa | 0x01);
+
+ sc->psc_pi->pi_d->pe_cfgread = passthru_cfgread_apu;
+ sc->psc_pi->pi_d->pe_cfgwrite = passthru_cfgwrite_apu;
+
+done:
+ return (error);
+}
+
static int
bar_access(int coff)
{
@@ -1443,6 +1491,16 @@
return passthru_cfgread(ctx, vcpu, pi, coff, bytes, rv);
}
+static int
+passthru_cfgread_apu(struct vmctx *ctx, int vcpu, struct pci_devinst *pi,
+ int coff, int bytes, uint32_t *rv)
+{
+ if (coff >= PCIR_BIOS && coff < PCIR_BIOS + 4)
+ return (-1);
+ else
+ return passthru_cfgread(ctx, vcpu, pi, coff, bytes, rv);
+}
+
static int
passthru_cfgwrite(struct vmctx *ctx, int vcpu, struct pci_devinst *pi,
int coff, int bytes, uint32_t val)
@@ -1592,6 +1650,37 @@
return passthru_cfgwrite(ctx, vcpu, pi, coff, bytes, val);
}
+static int
+passthru_cfgwrite_apu(struct vmctx *ctx, int vcpu, struct pci_devinst *pi,
+ int coff, int bytes, uint32_t val)
+{
+ if (coff == PCIR_BIOS) {
+ struct passthru_softc *sc;
+
+ sc = pi->pi_arg;
+
+ if ((val & ~0x7FF) == 0xFFFFF800) {
+ uint32_t lobits = pci_get_cfgdata32(pi, PCIR_BIOS) & 0x7FF;
+ uint32_t hibits = ~(roundup2(pi->vbios.len, PAGE_SIZE) - 1);
+ pci_set_cfgdata32(pi, PCIR_BIOS, hibits | lobits);
+ vm_unmap_pptdev_mmio(pi->pi_vmctx, sc->psc_sel.pc_bus, sc->psc_sel.pc_dev, sc->psc_sel.pc_func, pi->vbios.gpa, roundup2(pi->vbios.len, PAGE_SIZE));
+ pi->vbios.gpa = 0;
+ }
+ else {
+ pci_set_cfgdata32(pi, PCIR_BIOS, val);
+ val &= ~0x7FF;
+ if (vm_map_pptdev_mmio(pi->pi_vmctx, sc->psc_sel.pc_bus, sc->psc_sel.pc_dev, sc->psc_sel.pc_func, val, roundup2(pi->vbios.len, PAGE_SIZE), pi->vbios.hpa))
+ goto done;
+ pi->vbios.gpa = val;
+ }
+ }
+ else
+ return passthru_cfgwrite(ctx, vcpu, pi, coff, bytes, val);
+
+done:
+ return (0);
+}
+
static void
passthru_write(struct vmctx *ctx, int vcpu, struct pci_devinst *pi, int baridx,
uint64_t offset, int size, uint64_t value)

File Metadata

Mime Type
text/plain
Expires
Tue, Apr 29, 12:28 PM (11 h, 37 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
17841120
Default Alt Text
D26506.id77287.diff (17 KB)

Event Timeline