diff options
| author | Ira Snyder <iws@ovro.caltech.edu> | 2012-01-26 06:00:14 -0500 |
|---|---|---|
| committer | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2012-02-26 19:33:59 -0500 |
| commit | 6c15d7afbb2f9e2d3114b513306dae736b56f535 (patch) | |
| tree | 17ad7aa4544ad1d83b50eb4929f77b7793b5f0f4 /drivers/misc | |
| parent | 75ff85a81680e5779383aa6210a4f89ed76e40ec (diff) | |
carma-fpga: fix race between data dumping and DMA callback
When the system is under heavy load, we occasionally saw a problem where
the system would get a legitimate interrupt when they should be
disabled.
This was caused by the data_dma_cb() DMA callback unconditionally
re-enabling FPGA interrupts even when data dumping is disabled. When
data dumping was re-enabled, the irq handler would fire while a DMA was
in progress. The "BUG_ON(priv->inflight != NULL);" during the second
invocation of the DMA callback caused the system to crash.
To fix the issue, the priv->enabled boolean is moved under the
protection of the priv->lock spinlock. The DMA callback checks the
boolean to know whether to re-enable FPGA interrupts before it returns.
Now that it is fixed, the driver keeps FPGA interrupts disabled when it
expects that they are disabled, fixing the bug.
Signed-off-by: Ira W. Snyder <iws@ovro.caltech.edu>
Cc: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Diffstat (limited to 'drivers/misc')
| -rw-r--r-- | drivers/misc/carma/carma-fpga.c | 101 |
1 files changed, 61 insertions, 40 deletions
diff --git a/drivers/misc/carma/carma-fpga.c b/drivers/misc/carma/carma-fpga.c index 4fd896deda0d..0cfc5bf13fa5 100644 --- a/drivers/misc/carma/carma-fpga.c +++ b/drivers/misc/carma/carma-fpga.c | |||
| @@ -560,6 +560,9 @@ static void data_enable_interrupts(struct fpga_device *priv) | |||
| 560 | 560 | ||
| 561 | /* flush the writes */ | 561 | /* flush the writes */ |
| 562 | fpga_read_reg(priv, 0, MMAP_REG_STATUS); | 562 | fpga_read_reg(priv, 0, MMAP_REG_STATUS); |
| 563 | fpga_read_reg(priv, 1, MMAP_REG_STATUS); | ||
| 564 | fpga_read_reg(priv, 2, MMAP_REG_STATUS); | ||
| 565 | fpga_read_reg(priv, 3, MMAP_REG_STATUS); | ||
| 563 | 566 | ||
| 564 | /* switch back to the external interrupt source */ | 567 | /* switch back to the external interrupt source */ |
| 565 | iowrite32be(0x3F, priv->regs + SYS_IRQ_SOURCE_CTL); | 568 | iowrite32be(0x3F, priv->regs + SYS_IRQ_SOURCE_CTL); |
| @@ -591,8 +594,12 @@ static void data_dma_cb(void *data) | |||
| 591 | list_move_tail(&priv->inflight->entry, &priv->used); | 594 | list_move_tail(&priv->inflight->entry, &priv->used); |
| 592 | priv->inflight = NULL; | 595 | priv->inflight = NULL; |
| 593 | 596 | ||
| 594 | /* clear the FPGA status and re-enable interrupts */ | 597 | /* |
| 595 | data_enable_interrupts(priv); | 598 | * If data dumping is still enabled, then clear the FPGA |
| 599 | * status registers and re-enable FPGA interrupts | ||
| 600 | */ | ||
| 601 | if (priv->enabled) | ||
| 602 | data_enable_interrupts(priv); | ||
| 596 | 603 | ||
| 597 | spin_unlock_irqrestore(&priv->lock, flags); | 604 | spin_unlock_irqrestore(&priv->lock, flags); |
| 598 | 605 | ||
| @@ -708,6 +715,15 @@ static irqreturn_t data_irq(int irq, void *dev_id) | |||
| 708 | 715 | ||
| 709 | spin_lock(&priv->lock); | 716 | spin_lock(&priv->lock); |
| 710 | 717 | ||
| 718 | /* | ||
| 719 | * This is an error case that should never happen. | ||
| 720 | * | ||
| 721 | * If this driver has a bug and manages to re-enable interrupts while | ||
| 722 | * a DMA is in progress, then we will hit this statement and should | ||
| 723 | * start paying attention immediately. | ||
| 724 | */ | ||
| 725 | BUG_ON(priv->inflight != NULL); | ||
| 726 | |||
| 711 | /* hide the interrupt by switching the IRQ driver to GPIO */ | 727 | /* hide the interrupt by switching the IRQ driver to GPIO */ |
| 712 | data_disable_interrupts(priv); | 728 | data_disable_interrupts(priv); |
| 713 | 729 | ||
| @@ -762,11 +778,15 @@ out: | |||
| 762 | */ | 778 | */ |
| 763 | static int data_device_enable(struct fpga_device *priv) | 779 | static int data_device_enable(struct fpga_device *priv) |
| 764 | { | 780 | { |
| 781 | bool enabled; | ||
| 765 | u32 val; | 782 | u32 val; |
| 766 | int ret; | 783 | int ret; |
| 767 | 784 | ||
| 768 | /* multiple enables are safe: they do nothing */ | 785 | /* multiple enables are safe: they do nothing */ |
| 769 | if (priv->enabled) | 786 | spin_lock_irq(&priv->lock); |
| 787 | enabled = priv->enabled; | ||
| 788 | spin_unlock_irq(&priv->lock); | ||
| 789 | if (enabled) | ||
| 770 | return 0; | 790 | return 0; |
| 771 | 791 | ||
| 772 | /* check that the FPGAs are programmed */ | 792 | /* check that the FPGAs are programmed */ |
| @@ -797,6 +817,9 @@ static int data_device_enable(struct fpga_device *priv) | |||
| 797 | goto out_error; | 817 | goto out_error; |
| 798 | } | 818 | } |
| 799 | 819 | ||
| 820 | /* prevent the FPGAs from generating interrupts */ | ||
| 821 | data_disable_interrupts(priv); | ||
| 822 | |||
| 800 | /* hookup the irq handler */ | 823 | /* hookup the irq handler */ |
| 801 | ret = request_irq(priv->irq, data_irq, IRQF_SHARED, drv_name, priv); | 824 | ret = request_irq(priv->irq, data_irq, IRQF_SHARED, drv_name, priv); |
| 802 | if (ret) { | 825 | if (ret) { |
| @@ -804,11 +827,13 @@ static int data_device_enable(struct fpga_device *priv) | |||
| 804 | goto out_error; | 827 | goto out_error; |
| 805 | } | 828 | } |
| 806 | 829 | ||
| 807 | /* switch to the external FPGA IRQ line */ | 830 | /* allow the DMA callback to re-enable FPGA interrupts */ |
| 808 | data_enable_interrupts(priv); | 831 | spin_lock_irq(&priv->lock); |
| 809 | |||
| 810 | /* success, we're enabled */ | ||
| 811 | priv->enabled = true; | 832 | priv->enabled = true; |
| 833 | spin_unlock_irq(&priv->lock); | ||
| 834 | |||
| 835 | /* allow the FPGAs to generate interrupts */ | ||
| 836 | data_enable_interrupts(priv); | ||
| 812 | return 0; | 837 | return 0; |
| 813 | 838 | ||
| 814 | out_error: | 839 | out_error: |
| @@ -834,41 +859,40 @@ out_error: | |||
| 834 | */ | 859 | */ |
| 835 | static int data_device_disable(struct fpga_device *priv) | 860 | static int data_device_disable(struct fpga_device *priv) |
| 836 | { | 861 | { |
| 837 | int ret; | 862 | spin_lock_irq(&priv->lock); |
| 838 | 863 | ||
| 839 | /* allow multiple disable */ | 864 | /* allow multiple disable */ |
| 840 | if (!priv->enabled) | 865 | if (!priv->enabled) { |
| 866 | spin_unlock_irq(&priv->lock); | ||
| 841 | return 0; | 867 | return 0; |
| 868 | } | ||
| 869 | |||
| 870 | /* | ||
| 871 | * Mark the device disabled | ||
| 872 | * | ||
| 873 | * This stops DMA callbacks from re-enabling interrupts | ||
| 874 | */ | ||
| 875 | priv->enabled = false; | ||
| 842 | 876 | ||
| 843 | /* switch to the internal GPIO IRQ line */ | 877 | /* prevent the FPGAs from generating interrupts */ |
| 844 | data_disable_interrupts(priv); | 878 | data_disable_interrupts(priv); |
| 845 | 879 | ||
| 880 | /* wait until all ongoing DMA has finished */ | ||
| 881 | while (priv->inflight != NULL) { | ||
| 882 | spin_unlock_irq(&priv->lock); | ||
| 883 | wait_event(priv->wait, priv->inflight == NULL); | ||
| 884 | spin_lock_irq(&priv->lock); | ||
| 885 | } | ||
| 886 | |||
| 887 | spin_unlock_irq(&priv->lock); | ||
| 888 | |||
| 846 | /* unhook the irq handler */ | 889 | /* unhook the irq handler */ |
| 847 | free_irq(priv->irq, priv); | 890 | free_irq(priv->irq, priv); |
| 848 | 891 | ||
| 849 | /* | ||
| 850 | * wait for all outstanding DMA to complete | ||
| 851 | * | ||
| 852 | * Device interrupts are disabled, therefore another buffer cannot | ||
| 853 | * be marked inflight. | ||
| 854 | */ | ||
| 855 | ret = wait_event_interruptible(priv->wait, priv->inflight == NULL); | ||
| 856 | if (ret) | ||
| 857 | return ret; | ||
| 858 | |||
| 859 | /* free the correlation table */ | 892 | /* free the correlation table */ |
| 860 | sg_free_table(&priv->corl_table); | 893 | sg_free_table(&priv->corl_table); |
| 861 | priv->corl_nents = 0; | 894 | priv->corl_nents = 0; |
| 862 | 895 | ||
| 863 | /* | ||
| 864 | * We are taking the spinlock not to protect priv->enabled, but instead | ||
| 865 | * to make sure that there are no readers in the process of altering | ||
| 866 | * the free or used lists while we are setting this flag. | ||
| 867 | */ | ||
| 868 | spin_lock_irq(&priv->lock); | ||
| 869 | priv->enabled = false; | ||
| 870 | spin_unlock_irq(&priv->lock); | ||
| 871 | |||
| 872 | /* free all buffers: the free and used lists are not being changed */ | 896 | /* free all buffers: the free and used lists are not being changed */ |
| 873 | data_free_buffers(priv); | 897 | data_free_buffers(priv); |
| 874 | return 0; | 898 | return 0; |
| @@ -896,15 +920,6 @@ static unsigned int list_num_entries(struct list_head *list) | |||
| 896 | static int data_debug_show(struct seq_file *f, void *offset) | 920 | static int data_debug_show(struct seq_file *f, void *offset) |
| 897 | { | 921 | { |
| 898 | struct fpga_device *priv = f->private; | 922 | struct fpga_device *priv = f->private; |
| 899 | int ret; | ||
| 900 | |||
| 901 | /* | ||
| 902 | * Lock the mutex first, so that we get an accurate value for enable | ||
| 903 | * Lock the spinlock next, to get accurate list counts | ||
| 904 | */ | ||
| 905 | ret = mutex_lock_interruptible(&priv->mutex); | ||
| 906 | if (ret) | ||
| 907 | return ret; | ||
| 908 | 923 | ||
| 909 | spin_lock_irq(&priv->lock); | 924 | spin_lock_irq(&priv->lock); |
| 910 | 925 | ||
| @@ -917,7 +932,6 @@ static int data_debug_show(struct seq_file *f, void *offset) | |||
| 917 | seq_printf(f, "num_dropped: %d\n", priv->num_dropped); | 932 | seq_printf(f, "num_dropped: %d\n", priv->num_dropped); |
| 918 | 933 | ||
| 919 | spin_unlock_irq(&priv->lock); | 934 | spin_unlock_irq(&priv->lock); |
| 920 | mutex_unlock(&priv->mutex); | ||
| 921 | return 0; | 935 | return 0; |
| 922 | } | 936 | } |
| 923 | 937 | ||
| @@ -970,7 +984,13 @@ static ssize_t data_en_show(struct device *dev, struct device_attribute *attr, | |||
| 970 | char *buf) | 984 | char *buf) |
| 971 | { | 985 | { |
| 972 | struct fpga_device *priv = dev_get_drvdata(dev); | 986 | struct fpga_device *priv = dev_get_drvdata(dev); |
| 973 | return snprintf(buf, PAGE_SIZE, "%u\n", priv->enabled); | 987 | int ret; |
| 988 | |||
| 989 | spin_lock_irq(&priv->lock); | ||
| 990 | ret = snprintf(buf, PAGE_SIZE, "%u\n", priv->enabled); | ||
| 991 | spin_unlock_irq(&priv->lock); | ||
| 992 | |||
| 993 | return ret; | ||
| 974 | } | 994 | } |
| 975 | 995 | ||
| 976 | static ssize_t data_en_set(struct device *dev, struct device_attribute *attr, | 996 | static ssize_t data_en_set(struct device *dev, struct device_attribute *attr, |
| @@ -986,6 +1006,7 @@ static ssize_t data_en_set(struct device *dev, struct device_attribute *attr, | |||
| 986 | return -EINVAL; | 1006 | return -EINVAL; |
| 987 | } | 1007 | } |
| 988 | 1008 | ||
| 1009 | /* protect against concurrent enable/disable */ | ||
| 989 | ret = mutex_lock_interruptible(&priv->mutex); | 1010 | ret = mutex_lock_interruptible(&priv->mutex); |
| 990 | if (ret) | 1011 | if (ret) |
| 991 | return ret; | 1012 | return ret; |
