From: Boqun Feng <boqun.feng@gmail.com> To: Bjorn Helgaas <bhelgaas@google.com>, Arnd Bergmann <arnd@arndb.de>, Marc Zyngier <maz@kernel.org> Cc: "Catalin Marinas" <catalin.marinas@arm.com>, "Will Deacon" <will@kernel.org>, "K. Y. Srinivasan" <kys@microsoft.com>, "Haiyang Zhang" <haiyangz@microsoft.com>, "Stephen Hemminger" <sthemmin@microsoft.com>, "Wei Liu" <wei.liu@kernel.org>, "Dexuan Cui" <decui@microsoft.com>, "Lorenzo Pieralisi" <lorenzo.pieralisi@arm.com>, "Rob Herring" <robh@kernel.org>, "Krzysztof Wilczyński" <kw@linux.com>, "Boqun Feng" <boqun.feng@gmail.com>, linux-arm-kernel@lists.infradead.org, linux-kernel@vger.kernel.org, linux-hyperv@vger.kernel.org, linux-pci@vger.kernel.org, "Sunil Muthuswamy" <sunilmut@microsoft.com>, "Mike Rapoport" <rppt@kernel.org> Subject: [RFC v5 5/8] PCI: hv: Generify PCI probing Date: Tue, 20 Jul 2021 21:44:26 +0800 [thread overview] Message-ID: <20210720134429.511541-6-boqun.feng@gmail.com> (raw) In-Reply-To: <20210720134429.511541-1-boqun.feng@gmail.com> From: Arnd Bergmann <arnd@arndb.de> In order to support ARM64 Hyper-V PCI, we need to set up the bridge at probing time because ARM64 is a PCI_DOMAIN_GENERIC=y arch and we don't have pci_config_window (ARM64 sysdata) for a PCI root bus on Hyper-V, so it's impossible to retrieve the information (e.g. PCI domains, MSI domains) from bus sysdata on ARM64 after creation. Originally in create_root_hv_pci_bus(), pci_create_root_bus() is used to create the root bus and the corresponding bridge based on x86 sysdata. Now we create a bridge first and then call pci_scan_root_bus_bridge(), which allows us to do the necessary set-ups for the bridge. Signed-off-by: Arnd Bergmann <arnd@arndb.de> Signed-off-by: Boqun Feng <boqun.feng@gmail.com> --- drivers/pci/controller/pci-hyperv.c | 57 +++++++++++++++-------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/drivers/pci/controller/pci-hyperv.c b/drivers/pci/controller/pci-hyperv.c index a53bd8728d0d..8d42da5dd1d4 100644 --- a/drivers/pci/controller/pci-hyperv.c +++ b/drivers/pci/controller/pci-hyperv.c @@ -449,6 +449,7 @@ enum hv_pcibus_state { struct hv_pcibus_device { struct pci_sysdata sysdata; + struct pci_host_bridge *bridge; /* Protocol version negotiated with the host */ enum pci_protocol_version_t protocol_version; enum hv_pcibus_state state; @@ -464,8 +465,6 @@ struct hv_pcibus_device { spinlock_t device_list_lock; /* Protect lists below */ void __iomem *cfg_addr; - struct list_head resources_for_children; - struct list_head children; struct list_head dr_list; @@ -1797,7 +1796,7 @@ static void hv_pci_assign_slots(struct hv_pcibus_device *hbus) slot_nr = PCI_SLOT(wslot_to_devfn(hpdev->desc.win_slot.slot)); snprintf(name, SLOT_NAME_SIZE, "%u", hpdev->desc.ser); - hpdev->pci_slot = pci_create_slot(hbus->pci_bus, slot_nr, + hpdev->pci_slot = pci_create_slot(hbus->bridge->bus, slot_nr, name, NULL); if (IS_ERR(hpdev->pci_slot)) { pr_warn("pci_create slot %s failed\n", name); @@ -1827,7 +1826,7 @@ static void hv_pci_remove_slots(struct hv_pcibus_device *hbus) static void hv_pci_assign_numa_node(struct hv_pcibus_device *hbus) { struct pci_dev *dev; - struct pci_bus *bus = hbus->pci_bus; + struct pci_bus *bus = hbus->bridge->bus; struct hv_pci_dev *hv_dev; list_for_each_entry(dev, &bus->devices, bus_list) { @@ -1850,21 +1849,22 @@ static void hv_pci_assign_numa_node(struct hv_pcibus_device *hbus) */ static int create_root_hv_pci_bus(struct hv_pcibus_device *hbus) { - /* Register the device */ - hbus->pci_bus = pci_create_root_bus(&hbus->hdev->device, - 0, /* bus number is always zero */ - &hv_pcifront_ops, - &hbus->sysdata, - &hbus->resources_for_children); - if (!hbus->pci_bus) - return -ENODEV; + int error; + struct pci_host_bridge *bridge = hbus->bridge; + + bridge->dev.parent = &hbus->hdev->device; + bridge->sysdata = &hbus->sysdata; + bridge->ops = &hv_pcifront_ops; + + error = pci_scan_root_bus_bridge(bridge); + if (error) + return error; pci_lock_rescan_remove(); - pci_scan_child_bus(hbus->pci_bus); hv_pci_assign_numa_node(hbus); - pci_bus_assign_resources(hbus->pci_bus); + pci_bus_assign_resources(bridge->bus); hv_pci_assign_slots(hbus); - pci_bus_add_devices(hbus->pci_bus); + pci_bus_add_devices(bridge->bus); pci_unlock_rescan_remove(); hbus->state = hv_pcibus_installed; return 0; @@ -2127,7 +2127,7 @@ static void pci_devices_present_work(struct work_struct *work) * because there may have been changes. */ pci_lock_rescan_remove(); - pci_scan_child_bus(hbus->pci_bus); + pci_scan_child_bus(hbus->bridge->bus); hv_pci_assign_numa_node(hbus); hv_pci_assign_slots(hbus); pci_unlock_rescan_remove(); @@ -2295,8 +2295,8 @@ static void hv_eject_device_work(struct work_struct *work) /* * Ejection can come before or after the PCI bus has been set up, so * attempt to find it and tear down the bus state, if it exists. This - * must be done without constructs like pci_domain_nr(hbus->pci_bus) - * because hbus->pci_bus may not exist yet. + * must be done without constructs like pci_domain_nr(hbus->bridge->bus) + * because hbus->bridge->bus may not exist yet. */ wslot = wslot_to_devfn(hpdev->desc.win_slot.slot); pdev = pci_get_domain_bus_and_slot(hbus->sysdata.domain, 0, wslot); @@ -2662,8 +2662,7 @@ static int hv_pci_allocate_bridge_windows(struct hv_pcibus_device *hbus) /* Modify this resource to become a bridge window. */ hbus->low_mmio_res->flags |= IORESOURCE_WINDOW; hbus->low_mmio_res->flags &= ~IORESOURCE_BUSY; - pci_add_resource(&hbus->resources_for_children, - hbus->low_mmio_res); + pci_add_resource(&hbus->bridge->windows, hbus->low_mmio_res); } if (hbus->high_mmio_space) { @@ -2682,8 +2681,7 @@ static int hv_pci_allocate_bridge_windows(struct hv_pcibus_device *hbus) /* Modify this resource to become a bridge window. */ hbus->high_mmio_res->flags |= IORESOURCE_WINDOW; hbus->high_mmio_res->flags &= ~IORESOURCE_BUSY; - pci_add_resource(&hbus->resources_for_children, - hbus->high_mmio_res); + pci_add_resource(&hbus->bridge->windows, hbus->high_mmio_res); } return 0; @@ -3002,6 +3000,7 @@ static void hv_put_dom_num(u16 dom) static int hv_pci_probe(struct hv_device *hdev, const struct hv_vmbus_device_id *dev_id) { + struct pci_host_bridge *bridge; struct hv_pcibus_device *hbus; u16 dom_req, dom; char *name; @@ -3014,6 +3013,10 @@ static int hv_pci_probe(struct hv_device *hdev, */ BUILD_BUG_ON(sizeof(*hbus) > HV_HYP_PAGE_SIZE); + bridge = devm_pci_alloc_host_bridge(&hdev->device, 0); + if (!bridge) + return -ENOMEM; + /* * With the recent 59bb47985c1d ("mm, sl[aou]b: guarantee natural * alignment for kmalloc(power-of-two)"), kzalloc() is able to allocate @@ -3035,6 +3038,8 @@ static int hv_pci_probe(struct hv_device *hdev, hbus = kzalloc(HV_HYP_PAGE_SIZE, GFP_KERNEL); if (!hbus) return -ENOMEM; + + hbus->bridge = bridge; hbus->state = hv_pcibus_init; hbus->wslot_res_allocated = -1; @@ -3071,7 +3076,6 @@ static int hv_pci_probe(struct hv_device *hdev, hbus->hdev = hdev; INIT_LIST_HEAD(&hbus->children); INIT_LIST_HEAD(&hbus->dr_list); - INIT_LIST_HEAD(&hbus->resources_for_children); spin_lock_init(&hbus->config_lock); spin_lock_init(&hbus->device_list_lock); spin_lock_init(&hbus->retarget_msi_interrupt_lock); @@ -3295,9 +3299,9 @@ static int hv_pci_remove(struct hv_device *hdev) /* Remove the bus from PCI's point of view. */ pci_lock_rescan_remove(); - pci_stop_root_bus(hbus->pci_bus); + pci_stop_root_bus(hbus->bridge->bus); hv_pci_remove_slots(hbus); - pci_remove_root_bus(hbus->pci_bus); + pci_remove_root_bus(hbus->bridge->bus); pci_unlock_rescan_remove(); } @@ -3307,7 +3311,6 @@ static int hv_pci_remove(struct hv_device *hdev) iounmap(hbus->cfg_addr); hv_free_config_window(hbus); - pci_free_resource_list(&hbus->resources_for_children); hv_pci_free_bridge_windows(hbus); irq_domain_remove(hbus->irq_domain); irq_domain_free_fwnode(hbus->sysdata.fwnode); @@ -3390,7 +3393,7 @@ static int hv_pci_restore_msi_msg(struct pci_dev *pdev, void *arg) */ static void hv_pci_restore_msi_state(struct hv_pcibus_device *hbus) { - pci_walk_bus(hbus->pci_bus, hv_pci_restore_msi_msg, NULL); + pci_walk_bus(hbus->bridge->bus, hv_pci_restore_msi_msg, NULL); } static int hv_pci_resume(struct hv_device *hdev) -- 2.30.2
WARNING: multiple messages have this Message-ID (diff)
From: Boqun Feng <boqun.feng@gmail.com> To: Bjorn Helgaas <bhelgaas@google.com>, Arnd Bergmann <arnd@arndb.de>, Marc Zyngier <maz@kernel.org> Cc: "Catalin Marinas" <catalin.marinas@arm.com>, "Will Deacon" <will@kernel.org>, "K. Y. Srinivasan" <kys@microsoft.com>, "Haiyang Zhang" <haiyangz@microsoft.com>, "Stephen Hemminger" <sthemmin@microsoft.com>, "Wei Liu" <wei.liu@kernel.org>, "Dexuan Cui" <decui@microsoft.com>, "Lorenzo Pieralisi" <lorenzo.pieralisi@arm.com>, "Rob Herring" <robh@kernel.org>, "Krzysztof Wilczyński" <kw@linux.com>, "Boqun Feng" <boqun.feng@gmail.com>, linux-arm-kernel@lists.infradead.org, linux-kernel@vger.kernel.org, linux-hyperv@vger.kernel.org, linux-pci@vger.kernel.org, "Sunil Muthuswamy" <sunilmut@microsoft.com>, "Mike Rapoport" <rppt@kernel.org> Subject: [RFC v5 5/8] PCI: hv: Generify PCI probing Date: Tue, 20 Jul 2021 21:44:26 +0800 [thread overview] Message-ID: <20210720134429.511541-6-boqun.feng@gmail.com> (raw) In-Reply-To: <20210720134429.511541-1-boqun.feng@gmail.com> From: Arnd Bergmann <arnd@arndb.de> In order to support ARM64 Hyper-V PCI, we need to set up the bridge at probing time because ARM64 is a PCI_DOMAIN_GENERIC=y arch and we don't have pci_config_window (ARM64 sysdata) for a PCI root bus on Hyper-V, so it's impossible to retrieve the information (e.g. PCI domains, MSI domains) from bus sysdata on ARM64 after creation. Originally in create_root_hv_pci_bus(), pci_create_root_bus() is used to create the root bus and the corresponding bridge based on x86 sysdata. Now we create a bridge first and then call pci_scan_root_bus_bridge(), which allows us to do the necessary set-ups for the bridge. Signed-off-by: Arnd Bergmann <arnd@arndb.de> Signed-off-by: Boqun Feng <boqun.feng@gmail.com> --- drivers/pci/controller/pci-hyperv.c | 57 +++++++++++++++-------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/drivers/pci/controller/pci-hyperv.c b/drivers/pci/controller/pci-hyperv.c index a53bd8728d0d..8d42da5dd1d4 100644 --- a/drivers/pci/controller/pci-hyperv.c +++ b/drivers/pci/controller/pci-hyperv.c @@ -449,6 +449,7 @@ enum hv_pcibus_state { struct hv_pcibus_device { struct pci_sysdata sysdata; + struct pci_host_bridge *bridge; /* Protocol version negotiated with the host */ enum pci_protocol_version_t protocol_version; enum hv_pcibus_state state; @@ -464,8 +465,6 @@ struct hv_pcibus_device { spinlock_t device_list_lock; /* Protect lists below */ void __iomem *cfg_addr; - struct list_head resources_for_children; - struct list_head children; struct list_head dr_list; @@ -1797,7 +1796,7 @@ static void hv_pci_assign_slots(struct hv_pcibus_device *hbus) slot_nr = PCI_SLOT(wslot_to_devfn(hpdev->desc.win_slot.slot)); snprintf(name, SLOT_NAME_SIZE, "%u", hpdev->desc.ser); - hpdev->pci_slot = pci_create_slot(hbus->pci_bus, slot_nr, + hpdev->pci_slot = pci_create_slot(hbus->bridge->bus, slot_nr, name, NULL); if (IS_ERR(hpdev->pci_slot)) { pr_warn("pci_create slot %s failed\n", name); @@ -1827,7 +1826,7 @@ static void hv_pci_remove_slots(struct hv_pcibus_device *hbus) static void hv_pci_assign_numa_node(struct hv_pcibus_device *hbus) { struct pci_dev *dev; - struct pci_bus *bus = hbus->pci_bus; + struct pci_bus *bus = hbus->bridge->bus; struct hv_pci_dev *hv_dev; list_for_each_entry(dev, &bus->devices, bus_list) { @@ -1850,21 +1849,22 @@ static void hv_pci_assign_numa_node(struct hv_pcibus_device *hbus) */ static int create_root_hv_pci_bus(struct hv_pcibus_device *hbus) { - /* Register the device */ - hbus->pci_bus = pci_create_root_bus(&hbus->hdev->device, - 0, /* bus number is always zero */ - &hv_pcifront_ops, - &hbus->sysdata, - &hbus->resources_for_children); - if (!hbus->pci_bus) - return -ENODEV; + int error; + struct pci_host_bridge *bridge = hbus->bridge; + + bridge->dev.parent = &hbus->hdev->device; + bridge->sysdata = &hbus->sysdata; + bridge->ops = &hv_pcifront_ops; + + error = pci_scan_root_bus_bridge(bridge); + if (error) + return error; pci_lock_rescan_remove(); - pci_scan_child_bus(hbus->pci_bus); hv_pci_assign_numa_node(hbus); - pci_bus_assign_resources(hbus->pci_bus); + pci_bus_assign_resources(bridge->bus); hv_pci_assign_slots(hbus); - pci_bus_add_devices(hbus->pci_bus); + pci_bus_add_devices(bridge->bus); pci_unlock_rescan_remove(); hbus->state = hv_pcibus_installed; return 0; @@ -2127,7 +2127,7 @@ static void pci_devices_present_work(struct work_struct *work) * because there may have been changes. */ pci_lock_rescan_remove(); - pci_scan_child_bus(hbus->pci_bus); + pci_scan_child_bus(hbus->bridge->bus); hv_pci_assign_numa_node(hbus); hv_pci_assign_slots(hbus); pci_unlock_rescan_remove(); @@ -2295,8 +2295,8 @@ static void hv_eject_device_work(struct work_struct *work) /* * Ejection can come before or after the PCI bus has been set up, so * attempt to find it and tear down the bus state, if it exists. This - * must be done without constructs like pci_domain_nr(hbus->pci_bus) - * because hbus->pci_bus may not exist yet. + * must be done without constructs like pci_domain_nr(hbus->bridge->bus) + * because hbus->bridge->bus may not exist yet. */ wslot = wslot_to_devfn(hpdev->desc.win_slot.slot); pdev = pci_get_domain_bus_and_slot(hbus->sysdata.domain, 0, wslot); @@ -2662,8 +2662,7 @@ static int hv_pci_allocate_bridge_windows(struct hv_pcibus_device *hbus) /* Modify this resource to become a bridge window. */ hbus->low_mmio_res->flags |= IORESOURCE_WINDOW; hbus->low_mmio_res->flags &= ~IORESOURCE_BUSY; - pci_add_resource(&hbus->resources_for_children, - hbus->low_mmio_res); + pci_add_resource(&hbus->bridge->windows, hbus->low_mmio_res); } if (hbus->high_mmio_space) { @@ -2682,8 +2681,7 @@ static int hv_pci_allocate_bridge_windows(struct hv_pcibus_device *hbus) /* Modify this resource to become a bridge window. */ hbus->high_mmio_res->flags |= IORESOURCE_WINDOW; hbus->high_mmio_res->flags &= ~IORESOURCE_BUSY; - pci_add_resource(&hbus->resources_for_children, - hbus->high_mmio_res); + pci_add_resource(&hbus->bridge->windows, hbus->high_mmio_res); } return 0; @@ -3002,6 +3000,7 @@ static void hv_put_dom_num(u16 dom) static int hv_pci_probe(struct hv_device *hdev, const struct hv_vmbus_device_id *dev_id) { + struct pci_host_bridge *bridge; struct hv_pcibus_device *hbus; u16 dom_req, dom; char *name; @@ -3014,6 +3013,10 @@ static int hv_pci_probe(struct hv_device *hdev, */ BUILD_BUG_ON(sizeof(*hbus) > HV_HYP_PAGE_SIZE); + bridge = devm_pci_alloc_host_bridge(&hdev->device, 0); + if (!bridge) + return -ENOMEM; + /* * With the recent 59bb47985c1d ("mm, sl[aou]b: guarantee natural * alignment for kmalloc(power-of-two)"), kzalloc() is able to allocate @@ -3035,6 +3038,8 @@ static int hv_pci_probe(struct hv_device *hdev, hbus = kzalloc(HV_HYP_PAGE_SIZE, GFP_KERNEL); if (!hbus) return -ENOMEM; + + hbus->bridge = bridge; hbus->state = hv_pcibus_init; hbus->wslot_res_allocated = -1; @@ -3071,7 +3076,6 @@ static int hv_pci_probe(struct hv_device *hdev, hbus->hdev = hdev; INIT_LIST_HEAD(&hbus->children); INIT_LIST_HEAD(&hbus->dr_list); - INIT_LIST_HEAD(&hbus->resources_for_children); spin_lock_init(&hbus->config_lock); spin_lock_init(&hbus->device_list_lock); spin_lock_init(&hbus->retarget_msi_interrupt_lock); @@ -3295,9 +3299,9 @@ static int hv_pci_remove(struct hv_device *hdev) /* Remove the bus from PCI's point of view. */ pci_lock_rescan_remove(); - pci_stop_root_bus(hbus->pci_bus); + pci_stop_root_bus(hbus->bridge->bus); hv_pci_remove_slots(hbus); - pci_remove_root_bus(hbus->pci_bus); + pci_remove_root_bus(hbus->bridge->bus); pci_unlock_rescan_remove(); } @@ -3307,7 +3311,6 @@ static int hv_pci_remove(struct hv_device *hdev) iounmap(hbus->cfg_addr); hv_free_config_window(hbus); - pci_free_resource_list(&hbus->resources_for_children); hv_pci_free_bridge_windows(hbus); irq_domain_remove(hbus->irq_domain); irq_domain_free_fwnode(hbus->sysdata.fwnode); @@ -3390,7 +3393,7 @@ static int hv_pci_restore_msi_msg(struct pci_dev *pdev, void *arg) */ static void hv_pci_restore_msi_state(struct hv_pcibus_device *hbus) { - pci_walk_bus(hbus->pci_bus, hv_pci_restore_msi_msg, NULL); + pci_walk_bus(hbus->bridge->bus, hv_pci_restore_msi_msg, NULL); } static int hv_pci_resume(struct hv_device *hdev) -- 2.30.2 _______________________________________________ linux-arm-kernel mailing list linux-arm-kernel@lists.infradead.org http://lists.infradead.org/mailman/listinfo/linux-arm-kernel
next prev parent reply other threads:[~2021-07-20 14:08 UTC|newest] Thread overview: 28+ messages / expand[flat|nested] mbox.gz Atom feed top 2021-07-20 13:44 [RFC v5 0/8] PCI: hv: Support host bridge probing on ARM64 Boqun Feng 2021-07-20 13:44 ` Boqun Feng 2021-07-20 13:44 ` [RFC v5 1/8] PCI: Introduce domain_nr in pci_host_bridge Boqun Feng 2021-07-20 13:44 ` Boqun Feng 2021-07-20 22:49 ` Bjorn Helgaas 2021-07-20 22:49 ` Bjorn Helgaas 2021-07-21 0:57 ` Boqun Feng 2021-07-21 0:57 ` Boqun Feng 2021-07-20 13:44 ` [RFC v5 2/8] PCI: Support populating MSI domains of root buses via bridges Boqun Feng 2021-07-20 13:44 ` Boqun Feng 2021-07-20 13:44 ` [RFC v5 3/8] arm64: PCI: Restructure pcibios_root_bridge_prepare() Boqun Feng 2021-07-20 13:44 ` Boqun Feng 2021-07-20 13:44 ` [RFC v5 4/8] arm64: PCI: Support root bridge preparation for Hyper-V Boqun Feng 2021-07-20 13:44 ` Boqun Feng 2021-07-20 13:44 ` Boqun Feng [this message] 2021-07-20 13:44 ` [RFC v5 5/8] PCI: hv: Generify PCI probing Boqun Feng 2021-07-20 13:44 ` [RFC v5 6/8] PCI: hv: Set ->domain_nr of pci_host_bridge at probing time Boqun Feng 2021-07-20 13:44 ` Boqun Feng 2021-07-20 13:44 ` [RFC v5 7/8] PCI: hv: Set up MSI domain at bridge " Boqun Feng 2021-07-20 13:44 ` Boqun Feng 2021-07-20 13:44 ` [RFC v5 8/8] PCI: hv: Turn on the host bridge probing on ARM64 Boqun Feng 2021-07-20 13:44 ` Boqun Feng 2021-07-20 14:38 ` Marc Zyngier 2021-07-20 14:38 ` Marc Zyngier 2021-07-20 14:59 ` Boqun Feng 2021-07-20 14:59 ` Boqun Feng 2021-07-20 15:53 ` Marc Zyngier 2021-07-20 15:53 ` Marc Zyngier
Reply instructions: You may reply publicly to this message via plain-text email using any one of the following methods: * Save the following mbox file, import it into your mail client, and reply-to-all from there: mbox Avoid top-posting and favor interleaved quoting: https://en.wikipedia.org/wiki/Posting_style#Interleaved_style * Reply using the --to, --cc, and --in-reply-to switches of git-send-email(1): git send-email \ --in-reply-to=20210720134429.511541-6-boqun.feng@gmail.com \ --to=boqun.feng@gmail.com \ --cc=arnd@arndb.de \ --cc=bhelgaas@google.com \ --cc=catalin.marinas@arm.com \ --cc=decui@microsoft.com \ --cc=haiyangz@microsoft.com \ --cc=kw@linux.com \ --cc=kys@microsoft.com \ --cc=linux-arm-kernel@lists.infradead.org \ --cc=linux-hyperv@vger.kernel.org \ --cc=linux-kernel@vger.kernel.org \ --cc=linux-pci@vger.kernel.org \ --cc=lorenzo.pieralisi@arm.com \ --cc=maz@kernel.org \ --cc=robh@kernel.org \ --cc=rppt@kernel.org \ --cc=sthemmin@microsoft.com \ --cc=sunilmut@microsoft.com \ --cc=wei.liu@kernel.org \ --cc=will@kernel.org \ /path/to/YOUR_REPLY https://kernel.org/pub/software/scm/git/docs/git-send-email.html * If your mail client supports setting the In-Reply-To header via mailto: links, try the mailto: linkBe sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.