mirror of https://github.com/xqemu/xqemu.git
Merge remote branch 'mst/for_anthony' into staging
This commit is contained in:
commit
5dbbda3405
|
@ -0,0 +1,37 @@
|
||||||
|
QEMU<->ACPI BIOS PCI hotplug interface
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
QEMU supports PCI hotplug via ACPI, for PCI bus 0. This document
|
||||||
|
describes the interface between QEMU and the ACPI BIOS.
|
||||||
|
|
||||||
|
ACPI GPE block (IO ports 0xafe0-0xafe3, byte access):
|
||||||
|
-----------------------------------------
|
||||||
|
|
||||||
|
Generic ACPI GPE block. Bit 1 (GPE.1) used to notify PCI hotplug/eject
|
||||||
|
event to ACPI BIOS, via SCI interrupt.
|
||||||
|
|
||||||
|
PCI slot injection notification pending (IO port 0xae00-0xae03, 4-byte access):
|
||||||
|
---------------------------------------------------------------
|
||||||
|
Slot injection notification pending. One bit per slot.
|
||||||
|
|
||||||
|
Read by ACPI BIOS GPE.1 handler to notify OS of injection
|
||||||
|
events.
|
||||||
|
|
||||||
|
PCI slot removal notification (IO port 0xae04-0xae07, 4-byte access):
|
||||||
|
-----------------------------------------------------
|
||||||
|
Slot removal notification pending. One bit per slot.
|
||||||
|
|
||||||
|
Read by ACPI BIOS GPE.1 handler to notify OS of removal
|
||||||
|
events.
|
||||||
|
|
||||||
|
PCI device eject (IO port 0xae08-0xae0b, 4-byte access):
|
||||||
|
----------------------------------------
|
||||||
|
|
||||||
|
Used by ACPI BIOS _EJ0 method to request device removal. One bit per slot.
|
||||||
|
Reads return 0.
|
||||||
|
|
||||||
|
PCI removability status (IO port 0xae0c-0xae0f, 4-byte access):
|
||||||
|
-----------------------------------------------
|
||||||
|
|
||||||
|
Used by ACPI BIOS _RMV method to indicate removability status to OS. One
|
||||||
|
bit per slot.
|
|
@ -37,6 +37,7 @@
|
||||||
#define GPE_BASE 0xafe0
|
#define GPE_BASE 0xafe0
|
||||||
#define PCI_BASE 0xae00
|
#define PCI_BASE 0xae00
|
||||||
#define PCI_EJ_BASE 0xae08
|
#define PCI_EJ_BASE 0xae08
|
||||||
|
#define PCI_RMV_BASE 0xae0c
|
||||||
|
|
||||||
#define PIIX4_PCI_HOTPLUG_STATUS 2
|
#define PIIX4_PCI_HOTPLUG_STATUS 2
|
||||||
|
|
||||||
|
@ -73,6 +74,7 @@ typedef struct PIIX4PMState {
|
||||||
/* for pci hotplug */
|
/* for pci hotplug */
|
||||||
struct gpe_regs gpe;
|
struct gpe_regs gpe;
|
||||||
struct pci_status pci0_status;
|
struct pci_status pci0_status;
|
||||||
|
uint32_t pci0_hotplug_enable;
|
||||||
} PIIX4PMState;
|
} PIIX4PMState;
|
||||||
|
|
||||||
static void piix4_acpi_system_hot_add_init(PCIBus *bus, PIIX4PMState *s);
|
static void piix4_acpi_system_hot_add_init(PCIBus *bus, PIIX4PMState *s);
|
||||||
|
@ -322,6 +324,25 @@ static const VMStateDescription vmstate_acpi = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static void piix4_update_hotplug(PIIX4PMState *s)
|
||||||
|
{
|
||||||
|
PCIDevice *dev = &s->dev;
|
||||||
|
BusState *bus = qdev_get_parent_bus(&dev->qdev);
|
||||||
|
DeviceState *qdev, *next;
|
||||||
|
|
||||||
|
s->pci0_hotplug_enable = ~0;
|
||||||
|
|
||||||
|
QLIST_FOREACH_SAFE(qdev, &bus->children, sibling, next) {
|
||||||
|
PCIDeviceInfo *info = container_of(qdev->info, PCIDeviceInfo, qdev);
|
||||||
|
PCIDevice *pdev = DO_UPCAST(PCIDevice, qdev, qdev);
|
||||||
|
int slot = PCI_SLOT(pdev->devfn);
|
||||||
|
|
||||||
|
if (info->no_hotplug) {
|
||||||
|
s->pci0_hotplug_enable &= ~(1 << slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void piix4_reset(void *opaque)
|
static void piix4_reset(void *opaque)
|
||||||
{
|
{
|
||||||
PIIX4PMState *s = opaque;
|
PIIX4PMState *s = opaque;
|
||||||
|
@ -336,6 +357,7 @@ static void piix4_reset(void *opaque)
|
||||||
/* Mark SMM as already inited (until KVM supports SMM). */
|
/* Mark SMM as already inited (until KVM supports SMM). */
|
||||||
pci_conf[0x5B] = 0x02;
|
pci_conf[0x5B] = 0x02;
|
||||||
}
|
}
|
||||||
|
piix4_update_hotplug(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void piix4_powerdown(void *opaque, int irq, int power_failing)
|
static void piix4_powerdown(void *opaque, int irq, int power_failing)
|
||||||
|
@ -576,6 +598,18 @@ static void pciej_write(void *opaque, uint32_t addr, uint32_t val)
|
||||||
PIIX4_DPRINTF("pciej write %x <== %d\n", addr, val);
|
PIIX4_DPRINTF("pciej write %x <== %d\n", addr, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static uint32_t pcirmv_read(void *opaque, uint32_t addr)
|
||||||
|
{
|
||||||
|
PIIX4PMState *s = opaque;
|
||||||
|
|
||||||
|
return s->pci0_hotplug_enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pcirmv_write(void *opaque, uint32_t addr, uint32_t val)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
static int piix4_device_hotplug(DeviceState *qdev, PCIDevice *dev,
|
static int piix4_device_hotplug(DeviceState *qdev, PCIDevice *dev,
|
||||||
PCIHotplugState state);
|
PCIHotplugState state);
|
||||||
|
|
||||||
|
@ -592,6 +626,9 @@ static void piix4_acpi_system_hot_add_init(PCIBus *bus, PIIX4PMState *s)
|
||||||
register_ioport_write(PCI_EJ_BASE, 4, 4, pciej_write, bus);
|
register_ioport_write(PCI_EJ_BASE, 4, 4, pciej_write, bus);
|
||||||
register_ioport_read(PCI_EJ_BASE, 4, 4, pciej_read, bus);
|
register_ioport_read(PCI_EJ_BASE, 4, 4, pciej_read, bus);
|
||||||
|
|
||||||
|
register_ioport_write(PCI_RMV_BASE, 4, 4, pcirmv_write, s);
|
||||||
|
register_ioport_read(PCI_RMV_BASE, 4, 4, pcirmv_read, s);
|
||||||
|
|
||||||
pci_bus_hotplug(bus, piix4_device_hotplug, &s->dev.qdev);
|
pci_bus_hotplug(bus, piix4_device_hotplug, &s->dev.qdev);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
5
hw/msi.c
5
hw/msi.c
|
@ -255,7 +255,6 @@ void msi_write_config(PCIDevice *dev, uint32_t addr, uint32_t val, int len)
|
||||||
uint8_t log_max_vecs;
|
uint8_t log_max_vecs;
|
||||||
unsigned int vector;
|
unsigned int vector;
|
||||||
uint32_t pending;
|
uint32_t pending;
|
||||||
int i;
|
|
||||||
|
|
||||||
if (!ranges_overlap(addr, len, dev->msi_cap, msi_cap_sizeof(flags))) {
|
if (!ranges_overlap(addr, len, dev->msi_cap, msi_cap_sizeof(flags))) {
|
||||||
return;
|
return;
|
||||||
|
@ -296,9 +295,7 @@ void msi_write_config(PCIDevice *dev, uint32_t addr, uint32_t val, int len)
|
||||||
* from using its INTx# pin (if implemented) to request
|
* from using its INTx# pin (if implemented) to request
|
||||||
* service (MSI, MSI-X, and INTx# are mutually exclusive).
|
* service (MSI, MSI-X, and INTx# are mutually exclusive).
|
||||||
*/
|
*/
|
||||||
for (i = 0; i < PCI_NUM_PINS; ++i) {
|
pci_device_deassert_intx(dev);
|
||||||
qemu_set_irq(dev->irq[i], 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* nr_vectors might be set bigger than capable. So clamp it.
|
* nr_vectors might be set bigger than capable. So clamp it.
|
||||||
|
|
|
@ -159,7 +159,6 @@ void msix_write_config(PCIDevice *dev, uint32_t addr,
|
||||||
{
|
{
|
||||||
unsigned enable_pos = dev->msix_cap + MSIX_CONTROL_OFFSET;
|
unsigned enable_pos = dev->msix_cap + MSIX_CONTROL_OFFSET;
|
||||||
int vector;
|
int vector;
|
||||||
int i;
|
|
||||||
|
|
||||||
if (!range_covers_byte(addr, len, enable_pos)) {
|
if (!range_covers_byte(addr, len, enable_pos)) {
|
||||||
return;
|
return;
|
||||||
|
@ -169,9 +168,7 @@ void msix_write_config(PCIDevice *dev, uint32_t addr,
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (i = 0; i < PCI_NUM_PINS; ++i) {
|
pci_device_deassert_intx(dev);
|
||||||
qemu_set_irq(dev->irq[i], 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (msix_function_masked(dev)) {
|
if (msix_function_masked(dev)) {
|
||||||
return;
|
return;
|
||||||
|
|
27
hw/pci.c
27
hw/pci.c
|
@ -137,6 +137,14 @@ static void pci_update_irq_status(PCIDevice *dev)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void pci_device_deassert_intx(PCIDevice *dev)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
for (i = 0; i < PCI_NUM_PINS; ++i) {
|
||||||
|
qemu_set_irq(dev->irq[i], 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* This function is called on #RST and FLR.
|
* This function is called on #RST and FLR.
|
||||||
* FLR if PCI_EXP_DEVCTL_BCR_FLR is set
|
* FLR if PCI_EXP_DEVCTL_BCR_FLR is set
|
||||||
|
@ -152,6 +160,7 @@ void pci_device_reset(PCIDevice *dev)
|
||||||
|
|
||||||
dev->irq_state = 0;
|
dev->irq_state = 0;
|
||||||
pci_update_irq_status(dev);
|
pci_update_irq_status(dev);
|
||||||
|
pci_device_deassert_intx(dev);
|
||||||
/* Clear all writeable bits */
|
/* Clear all writeable bits */
|
||||||
pci_word_test_and_clear_mask(dev->config + PCI_COMMAND,
|
pci_word_test_and_clear_mask(dev->config + PCI_COMMAND,
|
||||||
pci_get_word(dev->wmask + PCI_COMMAND) |
|
pci_get_word(dev->wmask + PCI_COMMAND) |
|
||||||
|
@ -2032,10 +2041,13 @@ static char *pcibus_get_dev_path(DeviceState *dev)
|
||||||
* domain:Bus:Slot.Func for systems without nested PCI bridges.
|
* domain:Bus:Slot.Func for systems without nested PCI bridges.
|
||||||
* Slot.Function list specifies the slot and function numbers for all
|
* Slot.Function list specifies the slot and function numbers for all
|
||||||
* devices on the path from root to the specific device. */
|
* devices on the path from root to the specific device. */
|
||||||
int domain_len = strlen("DDDD:00");
|
char domain[] = "DDDD:00";
|
||||||
int slot_len = strlen(":SS.F");
|
char slot[] = ":SS.F";
|
||||||
|
int domain_len = sizeof domain - 1 /* For '\0' */;
|
||||||
|
int slot_len = sizeof slot - 1 /* For '\0' */;
|
||||||
int path_len;
|
int path_len;
|
||||||
char *path, *p;
|
char *path, *p;
|
||||||
|
int s;
|
||||||
|
|
||||||
/* Calculate # of slots on path between device and root. */;
|
/* Calculate # of slots on path between device and root. */;
|
||||||
slot_depth = 0;
|
slot_depth = 0;
|
||||||
|
@ -2046,18 +2058,23 @@ static char *pcibus_get_dev_path(DeviceState *dev)
|
||||||
path_len = domain_len + slot_len * slot_depth;
|
path_len = domain_len + slot_len * slot_depth;
|
||||||
|
|
||||||
/* Allocate memory, fill in the terminating null byte. */
|
/* Allocate memory, fill in the terminating null byte. */
|
||||||
path = malloc(path_len + 1 /* For '\0' */);
|
path = qemu_malloc(path_len + 1 /* For '\0' */);
|
||||||
path[path_len] = '\0';
|
path[path_len] = '\0';
|
||||||
|
|
||||||
/* First field is the domain. */
|
/* First field is the domain. */
|
||||||
snprintf(path, domain_len, "%04x:00", pci_find_domain(d->bus));
|
s = snprintf(domain, sizeof domain, "%04x:00", pci_find_domain(d->bus));
|
||||||
|
assert(s == domain_len);
|
||||||
|
memcpy(path, domain, domain_len);
|
||||||
|
|
||||||
/* Fill in slot numbers. We walk up from device to root, so need to print
|
/* Fill in slot numbers. We walk up from device to root, so need to print
|
||||||
* them in the reverse order, last to first. */
|
* them in the reverse order, last to first. */
|
||||||
p = path + path_len;
|
p = path + path_len;
|
||||||
for (t = d; t; t = t->bus->parent_dev) {
|
for (t = d; t; t = t->bus->parent_dev) {
|
||||||
p -= slot_len;
|
p -= slot_len;
|
||||||
snprintf(p, slot_len, ":%02x.%x", PCI_SLOT(t->devfn), PCI_FUNC(d->devfn));
|
s = snprintf(slot, sizeof slot, ":%02x.%x",
|
||||||
|
PCI_SLOT(t->devfn), PCI_FUNC(d->devfn));
|
||||||
|
assert(s == slot_len);
|
||||||
|
memcpy(p, slot, slot_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
return path;
|
return path;
|
||||||
|
|
2
hw/pci.h
2
hw/pci.h
|
@ -264,6 +264,8 @@ void do_pci_info_print(Monitor *mon, const QObject *data);
|
||||||
void do_pci_info(Monitor *mon, QObject **ret_data);
|
void do_pci_info(Monitor *mon, QObject **ret_data);
|
||||||
void pci_bridge_update_mappings(PCIBus *b);
|
void pci_bridge_update_mappings(PCIBus *b);
|
||||||
|
|
||||||
|
void pci_device_deassert_intx(PCIDevice *dev);
|
||||||
|
|
||||||
static inline void
|
static inline void
|
||||||
pci_set_byte(uint8_t *config, uint8_t val)
|
pci_set_byte(uint8_t *config, uint8_t val)
|
||||||
{
|
{
|
||||||
|
|
|
@ -769,10 +769,16 @@ VirtIODevice *virtio_serial_init(DeviceState *dev, uint32_t max_nr_ports)
|
||||||
/* Add a queue for guest to host transfers for port 0 (backward compat) */
|
/* Add a queue for guest to host transfers for port 0 (backward compat) */
|
||||||
vser->ovqs[0] = virtio_add_queue(vdev, 128, handle_output);
|
vser->ovqs[0] = virtio_add_queue(vdev, 128, handle_output);
|
||||||
|
|
||||||
|
/* TODO: host to guest notifications can get dropped
|
||||||
|
* if the queue fills up. Implement queueing in host,
|
||||||
|
* this might also make it possible to reduce the control
|
||||||
|
* queue size: as guest preposts buffers there,
|
||||||
|
* this will save 4Kbyte of guest memory per entry. */
|
||||||
|
|
||||||
/* control queue: host to guest */
|
/* control queue: host to guest */
|
||||||
vser->c_ivq = virtio_add_queue(vdev, 16, control_in);
|
vser->c_ivq = virtio_add_queue(vdev, 32, control_in);
|
||||||
/* control queue: guest to host */
|
/* control queue: guest to host */
|
||||||
vser->c_ovq = virtio_add_queue(vdev, 16, control_out);
|
vser->c_ovq = virtio_add_queue(vdev, 32, control_out);
|
||||||
|
|
||||||
for (i = 1; i < vser->bus->max_nr_ports; i++) {
|
for (i = 1; i < vser->bus->max_nr_ports; i++) {
|
||||||
/* Add a per-port queue for host to guest transfers */
|
/* Add a per-port queue for host to guest transfers */
|
||||||
|
|
|
@ -88,6 +88,10 @@ int do_migrate(Monitor *mon, const QDict *qdict, QObject **ret_data)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (qemu_savevm_state_blocked(mon)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
if (strstart(uri, "tcp:", &p)) {
|
if (strstart(uri, "tcp:", &p)) {
|
||||||
s = tcp_start_outgoing_migration(mon, p, max_throttle, detach,
|
s = tcp_start_outgoing_migration(mon, p, max_throttle, detach,
|
||||||
blk, inc);
|
blk, inc);
|
||||||
|
|
40
savevm.c
40
savevm.c
|
@ -1401,19 +1401,13 @@ static int vmstate_load(QEMUFile *f, SaveStateEntry *se, int version_id)
|
||||||
return vmstate_load_state(f, se->vmsd, se->opaque, version_id);
|
return vmstate_load_state(f, se->vmsd, se->opaque, version_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
static int vmstate_save(QEMUFile *f, SaveStateEntry *se)
|
static void vmstate_save(QEMUFile *f, SaveStateEntry *se)
|
||||||
{
|
{
|
||||||
if (se->no_migrate) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!se->vmsd) { /* Old style */
|
if (!se->vmsd) { /* Old style */
|
||||||
se->save_state(f, se->opaque);
|
se->save_state(f, se->opaque);
|
||||||
return 0;
|
return;
|
||||||
}
|
}
|
||||||
vmstate_save_state(f,se->vmsd, se->opaque);
|
vmstate_save_state(f,se->vmsd, se->opaque);
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#define QEMU_VM_FILE_MAGIC 0x5145564d
|
#define QEMU_VM_FILE_MAGIC 0x5145564d
|
||||||
|
@ -1427,6 +1421,20 @@ static int vmstate_save(QEMUFile *f, SaveStateEntry *se)
|
||||||
#define QEMU_VM_SECTION_FULL 0x04
|
#define QEMU_VM_SECTION_FULL 0x04
|
||||||
#define QEMU_VM_SUBSECTION 0x05
|
#define QEMU_VM_SUBSECTION 0x05
|
||||||
|
|
||||||
|
bool qemu_savevm_state_blocked(Monitor *mon)
|
||||||
|
{
|
||||||
|
SaveStateEntry *se;
|
||||||
|
|
||||||
|
QTAILQ_FOREACH(se, &savevm_handlers, entry) {
|
||||||
|
if (se->no_migrate) {
|
||||||
|
monitor_printf(mon, "state blocked by non-migratable device '%s'\n",
|
||||||
|
se->idstr);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
int qemu_savevm_state_begin(Monitor *mon, QEMUFile *f, int blk_enable,
|
int qemu_savevm_state_begin(Monitor *mon, QEMUFile *f, int blk_enable,
|
||||||
int shared)
|
int shared)
|
||||||
{
|
{
|
||||||
|
@ -1508,7 +1516,6 @@ int qemu_savevm_state_iterate(Monitor *mon, QEMUFile *f)
|
||||||
int qemu_savevm_state_complete(Monitor *mon, QEMUFile *f)
|
int qemu_savevm_state_complete(Monitor *mon, QEMUFile *f)
|
||||||
{
|
{
|
||||||
SaveStateEntry *se;
|
SaveStateEntry *se;
|
||||||
int r;
|
|
||||||
|
|
||||||
cpu_synchronize_all_states();
|
cpu_synchronize_all_states();
|
||||||
|
|
||||||
|
@ -1541,11 +1548,7 @@ int qemu_savevm_state_complete(Monitor *mon, QEMUFile *f)
|
||||||
qemu_put_be32(f, se->instance_id);
|
qemu_put_be32(f, se->instance_id);
|
||||||
qemu_put_be32(f, se->version_id);
|
qemu_put_be32(f, se->version_id);
|
||||||
|
|
||||||
r = vmstate_save(f, se);
|
vmstate_save(f, se);
|
||||||
if (r < 0) {
|
|
||||||
monitor_printf(mon, "cannot migrate with device '%s'\n", se->idstr);
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
qemu_put_byte(f, QEMU_VM_EOF);
|
qemu_put_byte(f, QEMU_VM_EOF);
|
||||||
|
@ -1575,6 +1578,11 @@ static int qemu_savevm_state(Monitor *mon, QEMUFile *f)
|
||||||
saved_vm_running = vm_running;
|
saved_vm_running = vm_running;
|
||||||
vm_stop(0);
|
vm_stop(0);
|
||||||
|
|
||||||
|
if (qemu_savevm_state_blocked(mon)) {
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
ret = qemu_savevm_state_begin(mon, f, 0, 0);
|
ret = qemu_savevm_state_begin(mon, f, 0, 0);
|
||||||
if (ret < 0)
|
if (ret < 0)
|
||||||
goto out;
|
goto out;
|
||||||
|
@ -1692,6 +1700,10 @@ int qemu_loadvm_state(QEMUFile *f)
|
||||||
unsigned int v;
|
unsigned int v;
|
||||||
int ret;
|
int ret;
|
||||||
|
|
||||||
|
if (qemu_savevm_state_blocked(default_mon)) {
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
v = qemu_get_be32(f);
|
v = qemu_get_be32(f);
|
||||||
if (v != QEMU_VM_FILE_MAGIC)
|
if (v != QEMU_VM_FILE_MAGIC)
|
||||||
return -EINVAL;
|
return -EINVAL;
|
||||||
|
|
1
sysemu.h
1
sysemu.h
|
@ -75,6 +75,7 @@ void qemu_announce_self(void);
|
||||||
|
|
||||||
void main_loop_wait(int nonblocking);
|
void main_loop_wait(int nonblocking);
|
||||||
|
|
||||||
|
bool qemu_savevm_state_blocked(Monitor *mon);
|
||||||
int qemu_savevm_state_begin(Monitor *mon, QEMUFile *f, int blk_enable,
|
int qemu_savevm_state_begin(Monitor *mon, QEMUFile *f, int blk_enable,
|
||||||
int shared);
|
int shared);
|
||||||
int qemu_savevm_state_iterate(Monitor *mon, QEMUFile *f);
|
int qemu_savevm_state_iterate(Monitor *mon, QEMUFile *f);
|
||||||
|
|
Loading…
Reference in New Issue