RISC-V IMSIC driver improvements
Subsystems (8)
v2 cover RISC-V IMSIC driver improvements
This series is based on recent discussion on LKML:
https://lore.kernel.org/lkml/[email protected]/
It primarily focuses on moving to RISC-V IMSIC driver use common MSI lib and GENERIC_PENDING_IRQ.
PATCH1: Fix for handling non-atomic MSI updates
PATCH2 & PATCH3: Preparatory patches
PATCH4: Main patch which updates IMSIC driver to use MSI lib PATCH5 & PATCH6: Preparatory patches for moving to GENERIC_PENDING_IRQ PATCH7 to PATCH11: Patches use GENERIC_PENDING_IRQ in IMSIC driver
These patches can also be found in the riscv_imsic_imp_v2 branch at:
https://github.com/avpatel/linux.git
Changes since v1:
- Changed series subject
- Expand this series to use GENERIC_PENDING_IRQ in IMSIC driver
Andrew Jones (1):
irqchip/riscv-imsic: Set irq_set_affinity for IMSIC base
Anup Patel (7):
irqchip/riscv-imsic: Handle non-atomic MSI updates for device
genirq: Introduce common irq_force_complete_move() implementation
RISC-V: Enable GENERIC_PENDING_IRQ and GENERIC_PENDING_IRQ_CHIPFLAGS irqchip/riscv-imsic: Separate next and previous pointers in IMSIC
vector
irqchip/riscv-imsic: Implement irq_force_complete_move() for IMSIC irqchip/riscv-imsic: Replace hwirq with irq in the IMSIC vector irqchip/riscv-imsic: Use IRQCHIP_MOVE_DEFERRED flag for PCI devices
Thomas Gleixner (3):
irqchip/irq-msi-lib: Optionally set default irq_eoi/irq_ack irqchip/riscv-imsic: Move to common MSI lib
genirq: Introduce kconfig option GENERIC_PENDING_IRQ_CHIPFLAGS
arch/riscv/Kconfig | 2 + drivers/irqchip/Kconfig | 8 +- drivers/irqchip/irq-gic-v2m.c | 1 + drivers/irqchip/irq-imx-mu-msi.c | 1 + drivers/irqchip/irq-msi-lib.c | 11 +- drivers/irqchip/irq-mvebu-gicp.c | 1 + drivers/irqchip/irq-mvebu-odmi.c | 1 + drivers/irqchip/irq-mvebu-sei.c | 1 + drivers/irqchip/irq-riscv-imsic-early.c | 14 +- drivers/irqchip/irq-riscv-imsic-platform.c | 177 +++++++++------------ drivers/irqchip/irq-riscv-imsic-state.c | 110 +++++++++---- drivers/irqchip/irq-riscv-imsic-state.h | 12 +- include/linux/irq.h | 15 ++ include/linux/msi.h | 11 ++ kernel/irq/Kconfig | 4 + kernel/irq/chip.c | 39 ++++- kernel/irq/irqdomain.c | 1 + kernel/irq/migration.c | 9 ++ 18 files changed, 262 insertions(+), 156 deletions(-)
Signature · 1 line
2.43.0
PATCH v2 1/11 irqchip/riscv-imsic: Handle non-atomic MSI updates for device
Device having non-atomic MSI update might see an intermediate state when changing target IMSIC vector from one CPU to another.
To handle such intermediate device state, update MSI address and MSI data through separate MSI writes to the device.
--- drivers/irqchip/irq-riscv-imsic-early.c | 8 +++- drivers/irqchip/irq-riscv-imsic-platform.c | 27 ++++++++++++ drivers/irqchip/irq-riscv-imsic-state.c | 50 ++++++++++++++-------- drivers/irqchip/irq-riscv-imsic-state.h | 2 +- 4 files changed, 68 insertions(+), 19 deletions(-)
drivers/irqchip/irq-riscv-imsic-early.c +7 -1
@@ -77,6 +77,12 @@ static void imsic_handle_irq(struct irq_desc *desc)struct imsic_vector *vec;unsigned long local_id; +/* +* First process pending IMSIC vector enable, disable and movement +* on the current CPU. +*/ +imsic_local_sync_all (false); +chained_irq_enter (chip, desc);while ((local_id =csr_swap (CSR_TOPEI ,0 ))) {
@@ -120,7 +126,7 @@ static int imsic_starting_cpu(unsigned int cpu)* Interrupts identities might have been enabled/disabled while * this CPU was not running so sync-up local enable/disable state. */ -imsic_local_sync_all (); +imsic_local_sync_all (true);/* Enable local interrupt delivery */ imsic_local_delivery (true);
drivers/irqchip/irq-riscv-imsic-platform.c +27 -0
@@ -97,6 +97,7 @@ static int imsic_irq_set_affinity(struct irq_data *d, const struct cpumask *mask {struct imsic_vector *old_vec, *new_vec;struct irq_data *pd = d->parent_data; +struct imsic_vector tmp_vec; old_vec =irq_data_get_irq_chip_data (pd);if (WARN_ON (!old_vec))
@@ -115,6 +116,32 @@ static int imsic_irq_set_affinity(struct irq_data *d, const struct cpumask *maskif (!new_vec)return -ENOSPC ; +/* +* Device having non-atomic MSI update might see an intermediate +* state when changing target IMSIC vector from one CPU to another. +* +* To avoid losing interrupt to some intermediate state, do the +* following (just like x86 APIC): +* +* 1) First write a temporary IMSIC vector to the device which +* has MSI address same as the old IMSIC vector but MSI data +* matches the new IMSIC vector. +* +* 2) Next write the new IMSIC vector to the device. +* +* Based on the above, the __imsic_local_sync() must check both +* old MSI data and new MSI data on the old CPU for pending +*/ + +if (new_vec->local_id != old_vec->local_id) { +/* Setup temporary vector */ + tmp_vec.cpu = old_vec->cpu; + tmp_vec.local_id = new_vec->local_id; + +/* Point device to the temporary vector */ +imsic_msi_update_msg (d, &tmp_vec); + } +/* Point device to the new vector */ imsic_msi_update_msg (d, new_vec);
drivers/irqchip/irq-riscv-imsic-state.c +33 -17
92 lines not on this page · raw message
drivers/irqchip/irq-riscv-imsic-state.h +1 -1
@@ -74,7 +74,7 @@ static inline void __imsic_id_clear_enable(unsigned long id)__imsic_eix_update (id,1 , false, false); } -void imsic_local_sync_all (void ); +void imsic_local_sync_all (bool force_all);void imsic_local_delivery (bool enable);void imsic_vector_mask (struct imsic_vector *vec);
Signature · 1 line
2.43.0
PATCH v2 2/11 irqchip/irq-msi-lib: Optionally set default irq_eoi/irq_ack
In reply to Anup PatelFrom: Thomas Gleixner <[email protected]>
Introduce chip_flags in struct msi_parent_ops. This allows msi_lib_init_dev_msi_info() set default irq_eoi/irq_ack callbacks only when the corresponding flags are set in the chip_flags.
--- drivers/irqchip/irq-gic-v2m.c | 1 + drivers/irqchip/irq-imx-mu-msi.c | 1 + drivers/irqchip/irq-msi-lib.c | 11 ++++++----- drivers/irqchip/irq-mvebu-gicp.c | 1 + drivers/irqchip/irq-mvebu-odmi.c | 1 + drivers/irqchip/irq-mvebu-sei.c | 1 + include/linux/msi.h | 11 +++++++++++ 7 files changed, 22 insertions(+), 5 deletions(-)
drivers/irqchip/irq-gic-v2m.c +1 -0
@@ -255,6 +255,7 @@ static void __init gicv2m_teardown(void)static struct msi_parent_ops gicv2m_msi_parent_ops = { .supported_flags =GICV2M_MSI_FLAGS_SUPPORTED , .required_flags =GICV2M_MSI_FLAGS_REQUIRED , + .chip_flags =MSI_CHIP_FLAG_SET_EOI |MSI_CHIP_FLAG_SET_ACK , .bus_select_token =DOMAIN_BUS_NEXUS , .bus_select_mask =MATCH_PCI_MSI |MATCH_PLATFORM_MSI , .prefix ="GICv2m-" ,
drivers/irqchip/irq-imx-mu-msi.c +1 -0
@@ -214,6 +214,7 @@ static void imx_mu_msi_irq_handler(struct irq_desc *desc)static const struct msi_parent_ops imx_mu_msi_parent_ops = { .supported_flags =IMX_MU_MSI_FLAGS_SUPPORTED , .required_flags =IMX_MU_MSI_FLAGS_REQUIRED , + .chip_flags =MSI_CHIP_FLAG_SET_EOI |MSI_CHIP_FLAG_SET_ACK , .bus_select_token =DOMAIN_BUS_NEXUS , .bus_select_mask =MATCH_PLATFORM_MSI , .prefix ="MU-MSI-" ,
drivers/irqchip/irq-msi-lib.c +6 -5
@@ -28,6 +28,7 @@ bool msi_lib_init_dev_msi_info(struct device *dev, struct irq_domain *domain, struct msi_domain_info *info) {const struct msi_parent_ops *pops = real_parent->msi_parent_ops; +struct irq_chip *chip = info->chip;u32 required_flags;/* Parent ops available? */
@@ -92,10 +93,10 @@ bool msi_lib_init_dev_msi_info(struct device *dev, struct irq_domain *domain, info->flags |= required_flags;/* Chip updates for all child bus types */ -if (!info->chip->irq_eoi) - info->chip->irq_eoi = irq_chip_eoi_parent; -if (!info->chip->irq_ack) - info->chip->irq_ack = irq_chip_ack_parent; +if (!chip->irq_eoi && (pops->chip_flags &MSI_CHIP_FLAG_SET_EOI )) + chip->irq_eoi = irq_chip_eoi_parent; +if (!chip->irq_ack && (pops->chip_flags &MSI_CHIP_FLAG_SET_ACK )) + chip->irq_ack = irq_chip_ack_parent;/* * The device MSI domain can never have a set affinity callback. It
@@ -105,7 +106,7 @@ bool msi_lib_init_dev_msi_info(struct device *dev, struct irq_domain *domain,* device MSI domain aside of mask/unmask which is provided e.g. by * PCI/MSI device domains. */ - info->chip->irq_set_affinity = msi_domain_set_affinity; + chip->irq_set_affinity = msi_domain_set_affinity;return true; }EXPORT_SYMBOL_GPL (msi_lib_init_dev_msi_info);
drivers/irqchip/irq-mvebu-gicp.c +1 -0
@@ -161,6 +161,7 @@ static const struct irq_domain_ops gicp_domain_ops = {static const struct msi_parent_ops gicp_msi_parent_ops = { .supported_flags =GICP_MSI_FLAGS_SUPPORTED , .required_flags =GICP_MSI_FLAGS_REQUIRED , + .chip_flags =MSI_CHIP_FLAG_SET_EOI |MSI_CHIP_FLAG_SET_ACK , .bus_select_token =DOMAIN_BUS_GENERIC_MSI , .bus_select_mask =MATCH_PLATFORM_MSI , .prefix ="GICP-" ,
drivers/irqchip/irq-mvebu-odmi.c +1 -0
@@ -157,6 +157,7 @@ static const struct irq_domain_ops odmi_domain_ops = {static const struct msi_parent_ops odmi_msi_parent_ops = { .supported_flags =ODMI_MSI_FLAGS_SUPPORTED , .required_flags =ODMI_MSI_FLAGS_REQUIRED , + .chip_flags =MSI_CHIP_FLAG_SET_EOI |MSI_CHIP_FLAG_SET_ACK , .bus_select_token =DOMAIN_BUS_GENERIC_MSI , .bus_select_mask =MATCH_PLATFORM_MSI , .prefix ="ODMI-" ,
drivers/irqchip/irq-mvebu-sei.c +1 -0
@@ -356,6 +356,7 @@ static void mvebu_sei_reset(struct mvebu_sei *sei)static const struct msi_parent_ops sei_msi_parent_ops = { .supported_flags =SEI_MSI_FLAGS_SUPPORTED , .required_flags =SEI_MSI_FLAGS_REQUIRED , + .chip_flags =MSI_CHIP_FLAG_SET_EOI |MSI_CHIP_FLAG_SET_ACK , .bus_select_mask =MATCH_PLATFORM_MSI , .bus_select_token =DOMAIN_BUS_GENERIC_MSI , .prefix ="SEI-" ,
include/linux/msi.h +11 -0
@@ -558,11 +558,21 @@ enum { MSI_FLAG_NO_AFFINITY = (1 << 21), }; +/* +* Flags for msi_parent_ops::chip_flags +*/ +enum { +MSI_CHIP_FLAG_SET_EOI = (1 <<0 ), +MSI_CHIP_FLAG_SET_ACK = (1 <<1 ), +}; +/** * struct msi_parent_ops - MSI parent domain callbacks and configuration info * * @supported_flags: Required: The supported MSI flags of the parent domain * @required_flags: Optional: The required MSI flags of the parent MSI domain +* @chip_flags: Optional: Select MSI chip callbacks to update with defaults +* in msi_lib_init_dev_msi_info(). * @bus_select_token: Optional: The bus token of the real parent domain for * irq_domain::select() * @bus_select_mask: Optional: A mask of supported BUS_DOMAINs for
@@ -575,6 +585,7 @@ enum {struct msi_parent_ops {u32 supported_flags;u32 required_flags; +u32 chip_flags;u32 bus_select_token;u32 bus_select_mask;const char *prefix;
Signature · 1 line
2.43.0
PATCH v2 3/11 irqchip/riscv-imsic: Set irq_set_affinity for IMSIC base
In reply to Anup PatelFrom: Andrew Jones <[email protected]>
Instead of using imsic_irq_set_affinity() for leaf MSI domains, use imsic_irq_set_affinity() for the non-leaf IMSIC base domain and use irq_chip_set_affinity_parent() for leaf MSI domains.
--- drivers/irqchip/irq-riscv-imsic-platform.c | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-)
drivers/irqchip/irq-riscv-imsic-platform.c +10 -8
@@ -96,10 +96,9 @@ static int imsic_irq_set_affinity(struct irq_data *d, const struct cpumask *mask bool force) {struct imsic_vector *old_vec, *new_vec; -struct irq_data *pd = d->parent_data;struct imsic_vector tmp_vec; - old_vec =irq_data_get_irq_chip_data (pd); + old_vec =irq_data_get_irq_chip_data (d);if (WARN_ON (!old_vec))return -ENOENT ;
@@ -139,17 +138,17 @@ static int imsic_irq_set_affinity(struct irq_data *d, const struct cpumask *mask tmp_vec.local_id = new_vec->local_id;/* Point device to the temporary vector */ -imsic_msi_update_msg (d, &tmp_vec); +imsic_msi_update_msg (irq_get_irq_data (d->irq), &tmp_vec); }/* Point device to the new vector */ -imsic_msi_update_msg (d, new_vec); +imsic_msi_update_msg (irq_get_irq_data (d->irq), new_vec);/* Update irq descriptors with the new vector */ - pd->chip_data = new_vec; + d->chip_data = new_vec; -/* Update effective affinity of parent irq data */ -irq_data_update_effective_affinity (pd,cpumask_of (new_vec->cpu)); +/* Update effective affinity */ +irq_data_update_effective_affinity (d,cpumask_of (new_vec->cpu));/* Move state of the old vector to the new vector */ imsic_vector_move (old_vec, new_vec);
@@ -162,6 +161,9 @@ static struct irq_chip imsic_irq_base_chip = { .name = "IMSIC", .irq_mask = imsic_irq_mask, .irq_unmask = imsic_irq_unmask, +#ifdef CONFIG_SMP + .irq_set_affinity = imsic_irq_set_affinity, +#endif .irq_retrigger = imsic_irq_retrigger, .irq_compose_msi_msg = imsic_irq_compose_msg, .flags = IRQCHIP_SKIP_SET_WAKE |
@@ -272,7 +274,7 @@ static bool imsic_init_dev_msi_info(struct device *dev,if (WARN_ON_ONCE (domain != real_parent))return false;#ifdef CONFIG_SMP - info->chip->irq_set_affinity = imsic_irq_set_affinity; + info->chip->irq_set_affinity = irq_chip_set_affinity_parent;#endif break ;default :
Signature · 1 line
2.43.0
PATCH v2 4/11 irqchip/riscv-imsic: Move to common MSI lib
In reply to Anup PatelFrom: Thomas Gleixner <[email protected]>
Simplify the leaf MSI domain handling in the RISC-V IMSIC driver by using msi_lib_init_dev_msi_info() and msi_lib_irq_domain_select() provided by common MSI lib.
--- drivers/irqchip/Kconfig | 8 +- drivers/irqchip/irq-riscv-imsic-platform.c | 114 +-------------------- 2 files changed, 6 insertions(+), 116 deletions(-)
drivers/irqchip/Kconfig +1 -7
@@ -587,13 +587,7 @@ config RISCV_IMSIC select IRQ_DOMAIN_HIERARCHY select GENERIC_IRQ_MATRIX_ALLOCATOR select GENERIC_MSI_IRQ - -config RISCV_IMSIC_PCI - bool - depends on RISCV_IMSIC - depends on PCI - depends on PCI_MSI - default RISCV_IMSIC + select IRQ_MSI_LIB config SIFIVE_PLIC bool
drivers/irqchip/irq-riscv-imsic-platform.c +5 -109
143 lines not on this page · raw message
Signature · 1 line
2.43.0
PATCH v2 5/11 genirq: Introduce kconfig option GENERIC_PENDING_IRQ_CHIPFLAGS
In reply to Anup PatelFrom: Thomas Gleixner <[email protected]>
Introduce kconfig option GENERIC_PENDING_IRQ_CHIPFLAGS which allows irq core to set IRQD_MOVE_PCNTXT flag based on IRQCHIP_MOVE_DEFERRED flag.
This kconfig option will help architectures such as x86 and RISC-V to mark top-level irqchip instances where irqs can't be moved in the process context.
--- include/linux/irq.h | 10 ++++++++++ kernel/irq/Kconfig | 4 ++++ kernel/irq/chip.c | 39 ++++++++++++++++++++++++++++++++++++--- kernel/irq/irqdomain.c | 1 + 4 files changed, 51 insertions(+), 3 deletions(-)
include/linux/irq.h +10 -0
@@ -567,6 +567,7 @@ struct irq_chip {* in the suspend path if they are in disabled state * IRQCHIP_AFFINITY_PRE_STARTUP: Default affinity update before startup * IRQCHIP_IMMUTABLE: Don't ever change anything in this chip +* IRQCHIP_MOVE_DEFERRED: Move the interrupt in actual interrupt context */ enum {IRQCHIP_SET_TYPE_MASKED = (1 <<0 ),
@@ -581,6 +582,7 @@ enum {IRQCHIP_ENABLE_WAKEUP_ON_SUSPEND = (1 <<9 ),IRQCHIP_AFFINITY_PRE_STARTUP = (1 <<10 ),IRQCHIP_IMMUTABLE = (1 <<11 ), + IRQCHIP_MOVE_DEFERRED = (1 << 12), };#include <linux/irqdesc.h>
@@ -634,6 +636,14 @@ static inline void irq_move_masked_irq(struct irq_data *data) { }static inline void irq_force_complete_move (struct irq_desc *desc) { }#endif +#if defined(CONFIG_GENERIC_PENDING_IRQ_CHIPFLAGS ) +void irq_update_pcntxt_flag (unsigned int irq,const struct irq_chip *chip); +#else +static inline void irq_update_pcntxt_flag (unsigned int irq,const struct irq_chip *chip) +{ +} +#endif +extern int no_irq_affinity;#ifdef CONFIG_HARDIRQS_SW_RESEND
kernel/irq/Kconfig +4 -0
@@ -31,6 +31,10 @@ config GENERIC_IRQ_EFFECTIVE_AFF_MASK config GENERIC_PENDING_IRQ bool +# Deduce delayed migration from top-level interrupt chip flags +config GENERIC_PENDING_IRQ_CHIPFLAGS + bool + # Support for generic irq migrating off cpu before the cpu is offline. config GENERIC_IRQ_MIGRATION bool
kernel/irq/chip.c +36 -3
@@ -33,6 +33,33 @@ struct irqaction chained_action = { .handler = bad_chained_irq, }; +#ifdef CONFIG_GENERIC_PENDING_IRQ_CHIPFLAGS +static void __irq_update_pcntxt_flag (struct irq_desc *desc, +const struct irq_chip *chip) +{ +if (chip) { +if (chip->flags &IRQCHIP_MOVE_DEFERRED ) +irqd_clear (&desc->irq_data,IRQD_MOVE_PCNTXT ); +else +irqd_set (&desc->irq_data,IRQD_MOVE_PCNTXT ); + } +} + +void irq_update_pcntxt_flag (unsigned int irq,const struct irq_chip *chip) +{ +unsigned long flags; +struct irq_desc *desc =irq_get_desc_lock (irq, &flags,0 ); + +__irq_update_pcntxt_flag (desc, chip); +irq_put_desc_unlock (desc, flags); +} +#else +static inline void __irq_update_pcntxt_flag (struct irq_desc *desc, +const struct irq_chip *chip) +{ +} +#endif +/** * irq_set_chip - set the irq chip for an irq * @irq: irq number
@@ -47,6 +74,7 @@ int irq_set_chip(unsigned int irq, const struct irq_chip *chip)return -EINVAL ; desc->irq_data.chip = (struct irq_chip *)(chip ?: &no_irq_chip); +__irq_update_pcntxt_flag (desc, chip);irq_put_desc_unlock (desc, flags);/* * For !CONFIG_SPARSE_IRQ make the irq show up in
@@ -1114,16 +1142,21 @@ void irq_modify_status(unsigned int irq, unsigned long clr, unsigned long set) trigger =irqd_get_trigger_type (&desc->irq_data);irqd_clear (&desc->irq_data,IRQD_NO_BALANCING |IRQD_PER_CPU | -IRQD_TRIGGER_MASK |IRQD_LEVEL |IRQD_MOVE_PCNTXT ); +IRQD_TRIGGER_MASK |IRQD_LEVEL );if (irq_settings_has_no_balance_set (desc))irqd_set (&desc->irq_data,IRQD_NO_BALANCING );if (irq_settings_is_per_cpu (desc))irqd_set (&desc->irq_data,IRQD_PER_CPU ); -if (irq_settings_can_move_pcntxt (desc)) -irqd_set (&desc->irq_data,IRQD_MOVE_PCNTXT );if (irq_settings_is_level (desc))irqd_set (&desc->irq_data,IRQD_LEVEL ); +/* Keep this around until x86 is converted over */ +if (!IS_ENABLED (CONFIG_GENERIC_PENDING_IRQ_CHIPFLAGS )) { +irqd_clear (&desc->irq_data,IRQD_MOVE_PCNTXT ); +if (irq_settings_can_move_pcntxt (desc)) +irqd_set (&desc->irq_data,IRQD_MOVE_PCNTXT ); + } + tmp =irq_settings_get_trigger_mask (desc);if (tmp !=IRQ_TYPE_NONE ) trigger = tmp;
kernel/irq/irqdomain.c +1 -0
@@ -1508,6 +1508,7 @@ int irq_domain_set_hwirq_and_chip(struct irq_domain *domain, unsigned int virq, irq_data->hwirq = hwirq; irq_data->chip = (struct irq_chip *)(chip ? chip : &no_irq_chip); irq_data->chip_data = chip_data; +irq_update_pcntxt_flag (virq, chip);return 0 ; }
Signature · 1 line
2.43.0
PATCH v2 6/11 genirq: Introduce common irq_force_complete_move() implementation
In reply to Anup PatelThe GENERIC_PENDING_IRQ requires an arch specific implementation of irq_force_complete_move(). At the moment, only x86 implements this but for RISC-V the irq_force_complete_move() is only needed when RISC-V IMSIC driver is in use and not needed otherwise.
To address the above, introduce common weak implementation of the irq_force_complete_move() which lets irqchip do the actual irq_force_complete_move().
--- include/linux/irq.h | 5 +++++ kernel/irq/migration.c | 9 +++++++++ 2 files changed, 14 insertions(+)
include/linux/irq.h +5 -0
@@ -496,6 +496,7 @@ static inline irq_hw_number_t irqd_to_hwirq(struct irq_data *d)* @ipi_send_mask: send an IPI to destination cpus in cpumask * @irq_nmi_setup: function called from core code before enabling an NMI * @irq_nmi_teardown: function called from core code after disabling an NMI +* @irq_force_complete_move: optional function to force complete pending irq move * @flags: chip specific flags */ struct irq_chip {
@@ -547,6 +548,10 @@ struct irq_chip {int (*irq_nmi_setup)(struct irq_data *data);void (*irq_nmi_teardown)(struct irq_data *data); +#if defined(CONFIG_SMP ) && defined(CONFIG_GENERIC_PENDING_IRQ ) +void (*irq_force_complete_move)(struct irq_data *data); +#endif +unsigned long flags; };
kernel/irq/migration.c +9 -0
@@ -5,6 +5,15 @@#include "internals.h" +void __weak irq_force_complete_move (struct irq_desc *desc) +{ +struct irq_data *d =irq_desc_get_irq_data (desc); +struct irq_chip *chip =irq_data_get_irq_chip (d); + +if (chip && chip->irq_force_complete_move) + chip->irq_force_complete_move (d); +} +/** * irq_fixup_move_pending - Cleanup irq move pending from a dying CPU * @desc: Interrupt descriptor to clean up
Signature · 1 line
2.43.0
PATCH v2 7/11 RISC-V: Enable GENERIC_PENDING_IRQ and GENERIC_PENDING_IRQ_CHIPFLAGS
In reply to Anup PatelEnable GENERIC_PENDING_IRQ and GENERIC_PENDING_IRQ_CHIPFLAGS for RISC-V so that RISC-V irqchips can support delayed irq mirgration in the interrupt context.
--- arch/riscv/Kconfig | 2 ++ 1 file changed, 2 insertions(+)
arch/riscv/Kconfig +2 -0
@@ -110,6 +110,8 @@ config RISCV select GENERIC_IRQ_SHOW select GENERIC_IRQ_SHOW_LEVEL select GENERIC_LIB_DEVMEM_IS_ALLOWED + select GENERIC_PENDING_IRQ if SMP + select GENERIC_PENDING_IRQ_CHIPFLAGS if SMP select GENERIC_PCI_IOMAP select GENERIC_PTDUMP if MMU select GENERIC_SCHED_CLOCK
Signature · 1 line
2.43.0
PATCH v2 8/11 irqchip/riscv-imsic: Separate next and previous pointers in IMSIC vector
In reply to Anup PatelCurrently, there is only one "move" pointer in the struct imsic_vector so during vector movement the old vector points to the new vector and new vector points to itself.
To support force cleanup of old vector, add separate "move_next" and "move_prev" pointers in the struct imsic_vector where during vector movement the "move_next" pointer of the old vector points to the new vector and the "move_prev" pointer of the new vector points to the old vector. Both "move_next" pointers are cleared separately by __imsic_local_sync() on the old and new CPUs respectively.
--- drivers/irqchip/irq-riscv-imsic-state.c | 60 +++++++++++++++++++------ drivers/irqchip/irq-riscv-imsic-state.h | 5 ++- 2 files changed, 50 insertions(+), 15 deletions(-)
drivers/irqchip/irq-riscv-imsic-state.c +47 -13
@@ -124,10 +124,11 @@ void __imsic_eix_update(unsigned long base_id, unsigned long num_id, bool pend, } } -static void __imsic_local_sync (struct imsic_local_priv *lpriv) +static bool __imsic_local_sync (struct imsic_local_priv *lpriv) {struct imsic_local_config *tlocal, *mlocal;struct imsic_vector *vec, *tvec, *mvec; +bool ret = true;int i;lockdep_assert_held (&lpriv->lock);
@@ -142,15 +143,33 @@ static void __imsic_local_sync(struct imsic_local_priv *lpriv)else __imsic_id_clear_enable (vec->local_id); +/* +* If the ID was being moved from an existing ID on some +* other CPU then we clear the pervious vector pointer +* only after the movement is complete. +*/ + mvec =READ_ONCE (vec->move_prev); +if (mvec) { +/* +* If the old IMSIC vector has not been updated then +* try again in the next sync-up call. +*/ +if (READ_ONCE (mvec->move_next)) { + ret = false; +continue ; + } + +WRITE_ONCE (vec->move_prev,NULL ); + } +/* * If the ID was being moved to a new ID on some other CPU * then we can get a MSI during the movement so check the * ID pending bit and re-trigger the new ID on other CPU * using MMIO write. */ - mvec =READ_ONCE (vec->move); -WRITE_ONCE (vec->move,NULL ); -if (mvec && mvec != vec) { + mvec =READ_ONCE (vec->move_next); +if (mvec) {/* * Device having non-atomic MSI update might see an * intermediate state so check both old ID and new ID
@@ -177,11 +196,14 @@ static void __imsic_local_sync(struct imsic_local_priv *lpriv)writel_relaxed (mvec->local_id, mlocal->msi_va); } +WRITE_ONCE (vec->move_next,NULL );imsic_vector_free (vec); }bitmap_clear (lpriv->dirty_bitmap, vec->local_id,1 ); } + +return ret; }void imsic_local_sync_all (bool force_all)
@@ -190,9 +212,16 @@ void imsic_local_sync_all(bool force_all)unsigned long flags;raw_spin_lock_irqsave (&lpriv->lock, flags); +if (force_all)bitmap_fill (lpriv->dirty_bitmap, imsic->global.nr_ids +1 ); -__imsic_local_sync (lpriv); +if (!__imsic_local_sync (lpriv)) { +if (!timer_pending (&lpriv->timer)) { + lpriv->timer.expires = jiffies +1 ; +add_timer_on (&lpriv->timer,smp_processor_id ()); + } + } +raw_spin_unlock_irqrestore (&lpriv->lock, flags); }
@@ -232,8 +261,8 @@ static void __imsic_remote_sync(struct imsic_local_priv *lpriv, unsigned int cpu*/ if (cpu_online (cpu)) {if (cpu ==smp_processor_id ()) { -__imsic_local_sync (lpriv); -return ; +if (__imsic_local_sync (lpriv)) +return ; }if (!timer_pending (&lpriv->timer)) {
@@ -294,8 +323,9 @@ void imsic_vector_unmask(struct imsic_vector *vec)raw_spin_unlock (&lpriv->lock); } -static bool imsic_vector_move_update (struct imsic_local_priv *lpriv,struct imsic_vector *vec, -bool new_enable,struct imsic_vector *new_move) +static bool imsic_vector_move_update (struct imsic_local_priv *lpriv, +struct imsic_vector *vec,bool is_old_vec, +bool new_enable,struct imsic_vector *move_vec) {unsigned long flags;bool enabled;
@@ -305,7 +335,10 @@ static bool imsic_vector_move_update(struct imsic_local_priv *lpriv, struct imsi/* Update enable and move details */ enabled =READ_ONCE (vec->enable);WRITE_ONCE (vec->enable, new_enable); -WRITE_ONCE (vec->move, new_move); +if (is_old_vec) +WRITE_ONCE (vec->move_next, move_vec); +else +WRITE_ONCE (vec->move_prev, move_vec);/* Mark the vector as dirty and synchronize */ bitmap_set (lpriv->dirty_bitmap, vec->local_id,1 );
@@ -338,8 +371,8 @@ void imsic_vector_move(struct imsic_vector *old_vec, struct imsic_vector *new_ve* interrupt on the old vector while device was being moved * to the new vector. */ - enabled =imsic_vector_move_update (old_lpriv, old_vec, false, new_vec); -imsic_vector_move_update (new_lpriv, new_vec, enabled, new_vec); + enabled =imsic_vector_move_update (old_lpriv, old_vec, true, false, new_vec); +imsic_vector_move_update (new_lpriv, new_vec, false, enabled, old_vec); }#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
@@ -402,7 +435,8 @@ struct imsic_vector *imsic_vector_alloc(unsigned int hwirq, const struct cpumask vec = &lpriv->vectors[local_id]; vec->hwirq = hwirq; vec->enable = false; - vec->move =NULL ; + vec->move_next =NULL ; + vec->move_prev =NULL ;return vec; }
drivers/irqchip/irq-riscv-imsic-state.h +3 -2
@@ -23,7 +23,8 @@ struct imsic_vector {unsigned int hwirq;/* Details accessed using local lock held */ bool enable; -struct imsic_vector *move; +struct imsic_vector *move_next; +struct imsic_vector *move_prev; };struct imsic_local_priv {
@@ -87,7 +88,7 @@ static inline bool imsic_vector_isenabled(struct imsic_vector *vec)static inline struct imsic_vector *imsic_vector_get_move (struct imsic_vector *vec) { -return READ_ONCE (vec->move); +return READ_ONCE (vec->move_prev); }void imsic_vector_move (struct imsic_vector *old_vec,struct imsic_vector *new_vec);
Signature · 1 line
2.43.0
PATCH v2 9/11 irqchip/riscv-imsic: Implement irq_force_complete_move() for IMSIC
In reply to Anup PatelImplement irq_force_complete_move() for IMSIC driver so that in-flight vector movements on a CPU can be cleaned-up when the CPU goes down.
--- drivers/irqchip/irq-riscv-imsic-platform.c | 32 ++++++++++++++++++++++ drivers/irqchip/irq-riscv-imsic-state.c | 17 ++++++++++++ drivers/irqchip/irq-riscv-imsic-state.h | 1 + 3 files changed, 50 insertions(+)
drivers/irqchip/irq-riscv-imsic-platform.c +32 -0
@@ -156,6 +156,37 @@ static int imsic_irq_set_affinity(struct irq_data *d, const struct cpumask *maskreturn IRQ_SET_MASK_OK_DONE ; } + +static void imsic_irq_force_complete_move (struct irq_data *d) +{ +struct imsic_vector *mvec, *vec =irq_data_get_irq_chip_data (d); +unsigned int cpu =smp_processor_id (); + +if (WARN_ON (!vec)) +return ; + +/* Do nothing if there is no in-flight move */ + mvec =imsic_vector_get_move (vec); +if (!mvec) +return ; + +/* Do nothing if the old IMSIC vector does not belong to current CPU */ +if (mvec->cpu != cpu) +return ; + +/* +* The best we can do is force cleanup the old IMSIC vector. +* +* The challenges over here are same as x86 vector domain so +* refer to the comments in irq_force_complete_move() function +* implemented at arch/x86/kernel/apic/vector.c. +*/ + +/* Force cleanup in-flight move */ +pr_info ("IRQ fixup: irq %d move in progress, old vector cpu %d local_id %d\n" , + d->irq, mvec->cpu, mvec->local_id); +imsic_vector_force_move_cleanup (vec); +}#endif static struct irq_chip imsic_irq_base_chip = {
@@ -164,6 +195,7 @@ static struct irq_chip imsic_irq_base_chip = { .irq_unmask = imsic_irq_unmask, #ifdef CONFIG_SMP .irq_set_affinity = imsic_irq_set_affinity, + .irq_force_complete_move = imsic_irq_force_complete_move, #endif .irq_retrigger = imsic_irq_retrigger, .irq_compose_msi_msg = imsic_irq_compose_msg,
drivers/irqchip/irq-riscv-imsic-state.c +17 -0
@@ -323,6 +323,23 @@ void imsic_vector_unmask(struct imsic_vector *vec)raw_spin_unlock (&lpriv->lock); } +void imsic_vector_force_move_cleanup (struct imsic_vector *vec) +{ +struct imsic_local_priv *lpriv; +struct imsic_vector *mvec; +unsigned long flags; + + lpriv =per_cpu_ptr (imsic->lpriv, vec->cpu); +raw_spin_lock_irqsave (&lpriv->lock, flags); + + mvec =READ_ONCE (vec->move_prev); +WRITE_ONCE (vec->move_prev,NULL ); +if (mvec) +imsic_vector_free (mvec); + +raw_spin_unlock_irqrestore (&lpriv->lock, flags); +} +static bool imsic_vector_move_update (struct imsic_local_priv *lpriv,struct imsic_vector *vec,bool is_old_vec,bool new_enable,struct imsic_vector *move_vec)
drivers/irqchip/irq-riscv-imsic-state.h +1 -0
@@ -91,6 +91,7 @@ static inline struct imsic_vector *imsic_vector_get_move(struct imsic_vector *vereturn READ_ONCE (vec->move_prev); } +void imsic_vector_force_move_cleanup (struct imsic_vector *vec);void imsic_vector_move (struct imsic_vector *old_vec,struct imsic_vector *new_vec);struct imsic_vector *imsic_vector_from_local_id (unsigned int cpu,unsigned int local_id);
Signature · 1 line
2.43.0
PATCH v2 10/11 irqchip/riscv-imsic: Replace hwirq with irq in the IMSIC vector
In reply to Anup PatelCurrently, the imsic_handle_irq() uses generic_handle_domain_irq() to handle the irq which internally has an extra step of resolving hwirq using domain. This extra step can be avoided by replacing hwirq with irq in the IMSIC vector and directly calling generic_handle_irq().
--- drivers/irqchip/irq-riscv-imsic-early.c | 6 ++---- drivers/irqchip/irq-riscv-imsic-platform.c | 2 +- drivers/irqchip/irq-riscv-imsic-state.c | 8 ++++---- drivers/irqchip/irq-riscv-imsic-state.h | 4 ++-- 4 files changed, 9 insertions(+), 11 deletions(-)
drivers/irqchip/irq-riscv-imsic-early.c +2 -4
@@ -73,7 +73,7 @@ static int __init imsic_ipi_domain_init(void) { return 0; }static void imsic_handle_irq (struct irq_desc *desc) {struct irq_chip *chip =irq_desc_get_chip (desc); -int err, cpu =smp_processor_id (); +int cpu =smp_processor_id ();struct imsic_vector *vec;unsigned long local_id;
@@ -103,9 +103,7 @@ static void imsic_handle_irq(struct irq_desc *desc)continue ; } - err =generic_handle_domain_irq (imsic->base_domain, vec->hwirq); -if (unlikely (err)) -pr_warn_ratelimited ("hwirq 0x%x mapping not found\n" , vec->hwirq); +generic_handle_irq (vec->irq); }chained_irq_exit (chip, desc);
drivers/irqchip/irq-riscv-imsic-platform.c +1 -1
@@ -112,7 +112,7 @@ static int imsic_irq_set_affinity(struct irq_data *d, const struct cpumask *maskreturn -EBUSY ;/* Get a new vector on the desired set of CPUs */ - new_vec =imsic_vector_alloc (old_vec->hwirq, mask_val); + new_vec =imsic_vector_alloc (old_vec->irq, mask_val);if (!new_vec)return -ENOSPC ;
drivers/irqchip/irq-riscv-imsic-state.c +4 -4
@@ -434,7 +434,7 @@ struct imsic_vector *imsic_vector_from_local_id(unsigned int cpu, unsigned int lreturn &lpriv->vectors[local_id]; } -struct imsic_vector *imsic_vector_alloc (unsigned int hwirq,const struct cpumask *mask) +struct imsic_vector *imsic_vector_alloc (unsigned int irq,const struct cpumask *mask) {struct imsic_vector *vec =NULL ;struct imsic_local_priv *lpriv;
@@ -450,7 +450,7 @@ struct imsic_vector *imsic_vector_alloc(unsigned int hwirq, const struct cpumask lpriv =per_cpu_ptr (imsic->lpriv, cpu); vec = &lpriv->vectors[local_id]; - vec->hwirq = hwirq; + vec->irq = irq; vec->enable = false; vec->move_next =NULL ; vec->move_prev =NULL ;
@@ -463,7 +463,7 @@ void imsic_vector_free(struct imsic_vector *vec)unsigned long flags;raw_spin_lock_irqsave (&imsic->matrix_lock, flags); - vec->hwirq =UINT_MAX ; + vec->irq =0 ;irq_matrix_free (imsic->matrix, vec->cpu, vec->local_id, false);raw_spin_unlock_irqrestore (&imsic->matrix_lock, flags); }
@@ -522,7 +522,7 @@ static int __init imsic_local_init(void) vec = &lpriv->vectors[i]; vec->cpu = cpu; vec->local_id = i; - vec->hwirq =UINT_MAX ; + vec->irq =0 ; } }
drivers/irqchip/irq-riscv-imsic-state.h +2 -2
@@ -20,7 +20,7 @@ struct imsic_vector {unsigned int cpu;unsigned int local_id;/* Details saved by driver in the vector */ -unsigned int hwirq; +unsigned int irq;/* Details accessed using local lock held */ bool enable;struct imsic_vector *move_next;
@@ -96,7 +96,7 @@ void imsic_vector_move(struct imsic_vector *old_vec, struct imsic_vector *new_vestruct imsic_vector *imsic_vector_from_local_id (unsigned int cpu,unsigned int local_id); -struct imsic_vector *imsic_vector_alloc (unsigned int hwirq,const struct cpumask *mask); +struct imsic_vector *imsic_vector_alloc (unsigned int irq,const struct cpumask *mask);void imsic_vector_free (struct imsic_vector *vector);void imsic_vector_debug_show (struct seq_file *m,struct imsic_vector *vec,int ind);
Signature · 1 line
2.43.0
PATCH v2 11/11 irqchip/riscv-imsic: Use IRQCHIP_MOVE_DEFERRED flag for PCI devices
In reply to Anup PatelDevices (such as PCI) which have non-atomic MSI update should migrate irq in the interrupt-context so use IRQCHIP_MOVE_DEFERRED flag for corresponding irqchips.
The use of IRQCHIP_MOVE_DEFERRED further simplifies IMSIC vector movement as follows:
1) No need to handle the intermediate state seen by devices with non-atomic MSI update because imsic_irq_set_affinity() is called in the interrupt-context with interrupt masked.
2) No need to check temporary vector when completing vector movement on the old CPU in __imsic_local_sync().
3) No need to call imsic_local_sync_all() from imsic_handle_irq()
--- drivers/irqchip/irq-riscv-imsic-platform.c | 74 ++++++++++++++-------- drivers/irqchip/irq-riscv-imsic-state.c | 25 +------- 2 files changed, 50 insertions(+), 49 deletions(-)
drivers/irqchip/irq-riscv-imsic-platform.c +48 -26
@@ -64,6 +64,11 @@ static int imsic_irq_retrigger(struct irq_data *d)return 0 ; } +static void imsic_irq_ack (struct irq_data *d) +{ +irq_move_irq (d); +} +static void imsic_irq_compose_vector_msg (struct imsic_vector *vec,struct msi_msg *msg) {phys_addr_t msi_addr;
@@ -97,7 +102,20 @@ static int imsic_irq_set_affinity(struct irq_data *d, const struct cpumask *mask bool force) {struct imsic_vector *old_vec, *new_vec; -struct imsic_vector tmp_vec; + +/* +* Requirements for the downstream irqdomains (or devices): +* +* 1) Downstream irqdomains (or devices) with atomic MSI update can +* happily do imsic_irq_set_affinity() in the process-context on +* any CPU so the irqchip of such irqdomains must not set the +* IRQCHIP_MOVE_DEFERRED flag. +* +* 2) Downstream irqdomains (or devices) with non-atomic MSI update +* must do imsic_irq_set_affinity() in the interrupt-context upon +* next interrupt so the irqchip of such irqdomains must set the +* IRQCHIP_MOVE_DEFERRED flag. +*/ old_vec =irq_data_get_irq_chip_data (d);if (WARN_ON (!old_vec))
@@ -117,31 +135,13 @@ static int imsic_irq_set_affinity(struct irq_data *d, const struct cpumask *maskreturn -ENOSPC ;/* -* Device having non-atomic MSI update might see an intermediate -* state when changing target IMSIC vector from one CPU to another. -* -* To avoid losing interrupt to some intermediate state, do the -* following (just like x86 APIC): -* -* 1) First write a temporary IMSIC vector to the device which -* has MSI address same as the old IMSIC vector but MSI data -* matches the new IMSIC vector. -* -* 2) Next write the new IMSIC vector to the device. -* -* Based on the above, the __imsic_local_sync() must check both -* old MSI data and new MSI data on the old CPU for pending +* Downstream irqdomains (or devices) with non-atomic MSI update +* may see an intermediate state when changing target IMSIC vector +* from one CPU to another but using the IRQCHIP_MOVE_DEFERRED +* flag this is taken care because imsic_irq_set_affinity() is +* called in the interrupt-context with interrupt masked. */ -if (new_vec->local_id != old_vec->local_id) { -/* Setup temporary vector */ - tmp_vec.cpu = old_vec->cpu; - tmp_vec.local_id = new_vec->local_id; - -/* Point device to the temporary vector */ -imsic_msi_update_msg (irq_get_irq_data (d->irq), &tmp_vec); - } -/* Point device to the new vector */ imsic_msi_update_msg (irq_get_irq_data (d->irq), new_vec);
@@ -198,6 +198,7 @@ static struct irq_chip imsic_irq_base_chip = { .irq_force_complete_move = imsic_irq_force_complete_move, #endif .irq_retrigger = imsic_irq_retrigger, + .irq_ack = imsic_irq_ack, .irq_compose_msi_msg = imsic_irq_compose_msg, .flags =IRQCHIP_SKIP_SET_WAKE | IRQCHIP_MASK_ON_SUSPEND,
@@ -217,7 +218,7 @@ static int imsic_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,return -ENOSPC ;irq_domain_set_info (domain, virq, virq, &imsic_irq_base_chip, vec, - handle_simple_irq,NULL ,NULL ); + handle_edge_irq,NULL ,NULL );irq_set_noprobe (virq);irq_set_affinity (virq, cpu_online_mask);irq_data_update_effective_affinity (irq_get_irq_data (virq),cpumask_of (vec->cpu));
@@ -256,15 +257,36 @@ static const struct irq_domain_ops imsic_base_domain_ops = {#endif }; +static bool imsic_init_dev_msi_info (struct device *dev, +struct irq_domain *domain, +struct irq_domain *real_parent, +struct msi_domain_info *info) +{ +if (!msi_lib_init_dev_msi_info (dev, domain, real_parent, info)) +return false; + +switch (info->bus_token) { +case DOMAIN_BUS_PCI_DEVICE_MSI : +case DOMAIN_BUS_PCI_DEVICE_MSIX : + info->chip->flags |=IRQCHIP_MOVE_DEFERRED ; +break ; +default : +break ; + } + +return true; +} +static const struct msi_parent_ops imsic_msi_parent_ops = { .supported_flags =MSI_GENERIC_FLAGS_MASK |MSI_FLAG_PCI_MSIX , .required_flags =MSI_FLAG_USE_DEF_DOM_OPS |MSI_FLAG_USE_DEF_CHIP_OPS |MSI_FLAG_PCI_MSI_MASK_PARENT , + .chip_flags =MSI_CHIP_FLAG_SET_ACK , .bus_select_token =DOMAIN_BUS_NEXUS , .bus_select_mask =MATCH_PCI_MSI |MATCH_PLATFORM_MSI , - .init_dev_msi_info = msi_lib_init_dev_msi_info, + .init_dev_msi_info = imsic_init_dev_msi_info, };int imsic_irqdomain_init (void )
drivers/irqchip/irq-riscv-imsic-state.c +2 -23
@@ -126,8 +126,8 @@ void __imsic_eix_update(unsigned long base_id, unsigned long num_id, bool pend,static bool __imsic_local_sync (struct imsic_local_priv *lpriv) { -struct imsic_local_config *tlocal, *mlocal; -struct imsic_vector *vec, *tvec, *mvec; +struct imsic_local_config *mlocal; +struct imsic_vector *vec, *mvec;bool ret = true;int i;
@@ -170,27 +170,6 @@ static bool __imsic_local_sync(struct imsic_local_priv *lpriv)*/ mvec =READ_ONCE (vec->move_next);if (mvec) { -/* -* Device having non-atomic MSI update might see an -* intermediate state so check both old ID and new ID -* for pending interrupts. -* -* For details, refer imsic_irq_set_affinity(). -*/ - - tvec = vec->local_id == mvec->local_id ? -NULL : &lpriv->vectors[mvec->local_id]; -if (tvec &&__imsic_id_read_clear_pending (tvec->local_id)) { -/* Retrigger temporary vector if it was already in-use */ -if (READ_ONCE (tvec->enable)) { - tlocal =per_cpu_ptr (imsic->global.local, tvec->cpu); -writel_relaxed (tvec->local_id, tlocal->msi_va); - } - - mlocal =per_cpu_ptr (imsic->global.local, mvec->cpu); -writel_relaxed (mvec->local_id, mlocal->msi_va); - } -if (__imsic_id_read_clear_pending (vec->local_id)) { mlocal =per_cpu_ptr (imsic->global.local, mvec->cpu);writel_relaxed (mvec->local_id, mlocal->msi_va);
Signature · 1 line
2.43.0
Anup!
Quoted 5 lines · Anup Patel
This series is based on recent discussion on LKML:
https://lore.kernel.org/lkml/[email protected]/It primarily focuses on moving to RISC-V IMSIC driver use common MSI lib and
GENERIC_PENDING_IRQ.
Can you please respin this series on top of rc1?
Thanks,
tglx
Anup Patel
maintainerQuoted 11 lines · Thomas Gleixner
Anup!
Quoted 5 lines · Anup Patel
This series is based on recent discussion on LKML:
https://lore.kernel.org/lkml/[email protected]/It primarily focuses on moving to RISC-V IMSIC driver use common MSI lib and
GENERIC_PENDING_IRQ.Can you please respin this series on top of rc1?
Sure, I will rebase and send v3 tomorrow.
Thanks,
Anup