aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGuennadi Liakhovetski <g.liakhovetski@gmx.de>2010-09-03 03:20:23 -0400
committerPaul Mundt <lethal@linux-sh.org>2010-09-14 04:23:21 -0400
commit6de9edd5bde0cdfea12e9948690e53ec669c3018 (patch)
tree638602a3d7726b27ae6ab85ef45f4f11c38c0283
parent89712699d7bc9cc93602407e0e9bc2490b771400 (diff)
fbdev: sh_mobile_hdmi: implement locking
The SH-Mobile HDMI driver runs in several contexts: ISR, delayed work-queue, task context, when called from the sh_mobile_lcdc framebuffer driver. This creates ample race possibilities. Even though most these races are purely theoretical, it is better to close them. To trace fb_info validity we install a notification callback in the HDMI driver, and the only way for it to get to driver internal data is by using struct sh_mobile_lcdc_chan, therefore it had to be extracted into a separate common header. Signed-off-by: Guennadi Liakhovetski <g.liakhovetski@gmx.de> Signed-off-by: Paul Mundt <lethal@linux-sh.org>
-rw-r--r--drivers/video/sh_mobile_hdmi.c103
-rw-r--r--drivers/video/sh_mobile_lcdcfb.c46
-rw-r--r--drivers/video/sh_mobile_lcdcfb.h37
-rw-r--r--include/video/sh_mobile_lcdc.h2
4 files changed, 141 insertions, 47 deletions
diff --git a/drivers/video/sh_mobile_hdmi.c b/drivers/video/sh_mobile_hdmi.c
index a8cf9a510f30..56e44fd0a3af 100644
--- a/drivers/video/sh_mobile_hdmi.c
+++ b/drivers/video/sh_mobile_hdmi.c
@@ -26,6 +26,8 @@
26#include <video/sh_mobile_hdmi.h> 26#include <video/sh_mobile_hdmi.h>
27#include <video/sh_mobile_lcdc.h> 27#include <video/sh_mobile_lcdc.h>
28 28
29#include "sh_mobile_lcdcfb.h"
30
29#define HDMI_SYSTEM_CTRL 0x00 /* System control */ 31#define HDMI_SYSTEM_CTRL 0x00 /* System control */
30#define HDMI_L_R_DATA_SWAP_CTRL_RPKT 0x01 /* L/R data swap control, 32#define HDMI_L_R_DATA_SWAP_CTRL_RPKT 0x01 /* L/R data swap control,
31 bits 19..16 of 20-bit N for Audio Clock Regeneration packet */ 33 bits 19..16 of 20-bit N for Audio Clock Regeneration packet */
@@ -209,6 +211,7 @@ struct sh_hdmi {
209 struct clk *hdmi_clk; 211 struct clk *hdmi_clk;
210 struct device *dev; 212 struct device *dev;
211 struct fb_info *info; 213 struct fb_info *info;
214 struct mutex mutex; /* Protect the info pointer */
212 struct delayed_work edid_work; 215 struct delayed_work edid_work;
213 struct fb_var_screeninfo var; 216 struct fb_var_screeninfo var;
214}; 217};
@@ -720,17 +723,21 @@ static irqreturn_t sh_hdmi_hotplug(int irq, void *dev_id)
720 return IRQ_HANDLED; 723 return IRQ_HANDLED;
721} 724}
722 725
726/* locking: called with info->lock held, or before register_framebuffer() */
723static void hdmi_display_on(void *arg, struct fb_info *info) 727static void hdmi_display_on(void *arg, struct fb_info *info)
724{ 728{
729 /*
730 * info is guaranteed to be valid, when we are called, because our
731 * FB_EVENT_FB_UNBIND notify is also called with info->lock held
732 */
725 struct sh_hdmi *hdmi = arg; 733 struct sh_hdmi *hdmi = arg;
726 struct sh_mobile_hdmi_info *pdata = hdmi->dev->platform_data; 734 struct sh_mobile_hdmi_info *pdata = hdmi->dev->platform_data;
727 735
728 pr_debug("%s(%p): state %x\n", __func__, pdata->lcd_dev, info->state); 736 pr_debug("%s(%p): state %x\n", __func__, pdata->lcd_dev, info->state);
729 /* 737
730 * FIXME: not a good place to store fb_info. And we cannot nullify it 738 /* No need to lock */
731 * even on monitor disconnect. What should the lifecycle be?
732 */
733 hdmi->info = info; 739 hdmi->info = info;
740
734 switch (hdmi->hp_state) { 741 switch (hdmi->hp_state) {
735 case HDMI_HOTPLUG_EDID_DONE: 742 case HDMI_HOTPLUG_EDID_DONE:
736 /* PS mode d->e. All functions are active */ 743 /* PS mode d->e. All functions are active */
@@ -744,6 +751,7 @@ static void hdmi_display_on(void *arg, struct fb_info *info)
744 } 751 }
745} 752}
746 753
754/* locking: called with info->lock held */
747static void hdmi_display_off(void *arg) 755static void hdmi_display_off(void *arg)
748{ 756{
749 struct sh_hdmi *hdmi = arg; 757 struct sh_hdmi *hdmi = arg;
@@ -766,6 +774,8 @@ static void edid_work_fn(struct work_struct *work)
766 if (!pdata->lcd_dev) 774 if (!pdata->lcd_dev)
767 return; 775 return;
768 776
777 mutex_lock(&hdmi->mutex);
778
769 if (hdmi->hp_state == HDMI_HOTPLUG_EDID_DONE) { 779 if (hdmi->hp_state == HDMI_HOTPLUG_EDID_DONE) {
770 pm_runtime_get_sync(hdmi->dev); 780 pm_runtime_get_sync(hdmi->dev);
771 /* A device has been plugged in */ 781 /* A device has been plugged in */
@@ -776,21 +786,25 @@ static void edid_work_fn(struct work_struct *work)
776 msleep(10); 786 msleep(10);
777 787
778 if (!hdmi->info) 788 if (!hdmi->info)
779 return; 789 goto out;
780 790
781 acquire_console_sem(); 791 acquire_console_sem();
782 792
783 /* HDMI plug in */ 793 /* HDMI plug in */
784 hdmi->info->var = hdmi->var; 794 hdmi->info->var = hdmi->var;
785 if (hdmi->info->state != FBINFO_STATE_RUNNING) 795 if (hdmi->info->state != FBINFO_STATE_RUNNING) {
786 fb_set_suspend(hdmi->info, 0); 796 fb_set_suspend(hdmi->info, 0);
787 else 797 } else {
788 hdmi_display_on(hdmi, hdmi->info); 798 if (lock_fb_info(hdmi->info)) {
799 hdmi_display_on(hdmi, hdmi->info);
800 unlock_fb_info(hdmi->info);
801 }
802 }
789 803
790 release_console_sem(); 804 release_console_sem();
791 } else { 805 } else {
792 if (!hdmi->info) 806 if (!hdmi->info)
793 return; 807 goto out;
794 808
795 acquire_console_sem(); 809 acquire_console_sem();
796 810
@@ -801,13 +815,61 @@ static void edid_work_fn(struct work_struct *work)
801 pm_runtime_put(hdmi->dev); 815 pm_runtime_put(hdmi->dev);
802 } 816 }
803 817
818out:
819 mutex_unlock(&hdmi->mutex);
820
804 pr_debug("%s(%p): end\n", __func__, pdata->lcd_dev); 821 pr_debug("%s(%p): end\n", __func__, pdata->lcd_dev);
805} 822}
806 823
824static int sh_hdmi_notify(struct notifier_block *nb,
825 unsigned long action, void *data);
826
827static struct notifier_block sh_hdmi_notifier = {
828 .notifier_call = sh_hdmi_notify,
829};
830
831static int sh_hdmi_notify(struct notifier_block *nb,
832 unsigned long action, void *data)
833{
834 struct fb_event *event = data;
835 struct fb_info *info = event->info;
836 struct sh_mobile_lcdc_chan *ch = info->par;
837 struct sh_mobile_lcdc_board_cfg *board_cfg = &ch->cfg.board_cfg;
838 struct sh_hdmi *hdmi = board_cfg->board_data;
839
840 if (nb != &sh_hdmi_notifier || !hdmi || hdmi->info != info)
841 return NOTIFY_DONE;
842
843 switch(action) {
844 case FB_EVENT_FB_REGISTERED:
845 /* Unneeded, activation taken care by hdmi_display_on() */
846 break;
847 case FB_EVENT_FB_UNREGISTERED:
848 /*
849 * We are called from unregister_framebuffer() with the
850 * info->lock held. This is bad for us, because we can race with
851 * the scheduled work, which has to call fb_set_suspend(), which
852 * takes info->lock internally, so, edid_work_fn() cannot take
853 * and hold info->lock for the whole function duration. Using an
854 * additional lock creates a classical AB-BA lock up. Therefore,
855 * we have to release the info->lock temporarily, synchronise
856 * with the work queue and re-acquire the info->lock.
857 */
858 unlock_fb_info(hdmi->info);
859 mutex_lock(&hdmi->mutex);
860 hdmi->info = NULL;
861 mutex_unlock(&hdmi->mutex);
862 lock_fb_info(hdmi->info);
863 return NOTIFY_OK;
864 }
865 return NOTIFY_DONE;
866}
867
807static int __init sh_hdmi_probe(struct platform_device *pdev) 868static int __init sh_hdmi_probe(struct platform_device *pdev)
808{ 869{
809 struct sh_mobile_hdmi_info *pdata = pdev->dev.platform_data; 870 struct sh_mobile_hdmi_info *pdata = pdev->dev.platform_data;
810 struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 871 struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
872 struct sh_mobile_lcdc_board_cfg *board_cfg;
811 int irq = platform_get_irq(pdev, 0), ret; 873 int irq = platform_get_irq(pdev, 0), ret;
812 struct sh_hdmi *hdmi; 874 struct sh_hdmi *hdmi;
813 long rate; 875 long rate;
@@ -821,6 +883,7 @@ static int __init sh_hdmi_probe(struct platform_device *pdev)
821 return -ENOMEM; 883 return -ENOMEM;
822 } 884 }
823 885
886 mutex_init(&hdmi->mutex);
824 hdmi->dev = &pdev->dev; 887 hdmi->dev = &pdev->dev;
825 888
826 hdmi->hdmi_clk = clk_get(&pdev->dev, "ick"); 889 hdmi->hdmi_clk = clk_get(&pdev->dev, "ick");
@@ -878,9 +941,11 @@ static int __init sh_hdmi_probe(struct platform_device *pdev)
878#endif 941#endif
879 942
880 /* Set up LCDC callbacks */ 943 /* Set up LCDC callbacks */
881 pdata->lcd_chan->board_cfg.board_data = hdmi; 944 board_cfg = &pdata->lcd_chan->board_cfg;
882 pdata->lcd_chan->board_cfg.display_on = hdmi_display_on; 945 board_cfg->owner = THIS_MODULE;
883 pdata->lcd_chan->board_cfg.display_off = hdmi_display_off; 946 board_cfg->board_data = hdmi;
947 board_cfg->display_on = hdmi_display_on;
948 board_cfg->display_off = hdmi_display_off;
884 949
885 INIT_DELAYED_WORK(&hdmi->edid_work, edid_work_fn); 950 INIT_DELAYED_WORK(&hdmi->edid_work, edid_work_fn);
886 951
@@ -907,6 +972,7 @@ eclkenable:
907erate: 972erate:
908 clk_put(hdmi->hdmi_clk); 973 clk_put(hdmi->hdmi_clk);
909egetclk: 974egetclk:
975 mutex_destroy(&hdmi->mutex);
910 kfree(hdmi); 976 kfree(hdmi);
911 977
912 return ret; 978 return ret;
@@ -917,19 +983,24 @@ static int __exit sh_hdmi_remove(struct platform_device *pdev)
917 struct sh_mobile_hdmi_info *pdata = pdev->dev.platform_data; 983 struct sh_mobile_hdmi_info *pdata = pdev->dev.platform_data;
918 struct sh_hdmi *hdmi = platform_get_drvdata(pdev); 984 struct sh_hdmi *hdmi = platform_get_drvdata(pdev);
919 struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 985 struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
986 struct sh_mobile_lcdc_board_cfg *board_cfg = &pdata->lcd_chan->board_cfg;
920 int irq = platform_get_irq(pdev, 0); 987 int irq = platform_get_irq(pdev, 0);
921 988
922 pdata->lcd_chan->board_cfg.display_on = NULL; 989 board_cfg->display_on = NULL;
923 pdata->lcd_chan->board_cfg.display_off = NULL; 990 board_cfg->display_off = NULL;
924 pdata->lcd_chan->board_cfg.board_data = NULL; 991 board_cfg->board_data = NULL;
992 board_cfg->owner = NULL;
925 993
994 /* No new work will be scheduled, wait for running ISR */
926 free_irq(irq, hdmi); 995 free_irq(irq, hdmi);
927 pm_runtime_disable(&pdev->dev); 996 /* Wait for already scheduled work */
928 cancel_delayed_work_sync(&hdmi->edid_work); 997 cancel_delayed_work_sync(&hdmi->edid_work);
998 pm_runtime_disable(&pdev->dev);
929 clk_disable(hdmi->hdmi_clk); 999 clk_disable(hdmi->hdmi_clk);
930 clk_put(hdmi->hdmi_clk); 1000 clk_put(hdmi->hdmi_clk);
931 iounmap(hdmi->base); 1001 iounmap(hdmi->base);
932 release_mem_region(res->start, resource_size(res)); 1002 release_mem_region(res->start, resource_size(res));
1003 mutex_destroy(&hdmi->mutex);
933 kfree(hdmi); 1004 kfree(hdmi);
934 1005
935 return 0; 1006 return 0;
diff --git a/drivers/video/sh_mobile_lcdcfb.c b/drivers/video/sh_mobile_lcdcfb.c
index ddd6c4669bd7..29d7ce7e7a1c 100644
--- a/drivers/video/sh_mobile_lcdcfb.c
+++ b/drivers/video/sh_mobile_lcdcfb.c
@@ -12,7 +12,6 @@
12#include <linux/init.h> 12#include <linux/init.h>
13#include <linux/delay.h> 13#include <linux/delay.h>
14#include <linux/mm.h> 14#include <linux/mm.h>
15#include <linux/fb.h>
16#include <linux/clk.h> 15#include <linux/clk.h>
17#include <linux/pm_runtime.h> 16#include <linux/pm_runtime.h>
18#include <linux/platform_device.h> 17#include <linux/platform_device.h>
@@ -24,7 +23,8 @@
24#include <video/sh_mobile_lcdc.h> 23#include <video/sh_mobile_lcdc.h>
25#include <asm/atomic.h> 24#include <asm/atomic.h>
26 25
27#define PALETTE_NR 16 26#include "sh_mobile_lcdcfb.h"
27
28#define SIDE_B_OFFSET 0x1000 28#define SIDE_B_OFFSET 0x1000
29#define MIRROR_OFFSET 0x2000 29#define MIRROR_OFFSET 0x2000
30 30
@@ -53,12 +53,6 @@ static int lcdc_shared_regs[] = {
53}; 53};
54#define NR_SHARED_REGS ARRAY_SIZE(lcdc_shared_regs) 54#define NR_SHARED_REGS ARRAY_SIZE(lcdc_shared_regs)
55 55
56/* per-channel registers */
57enum { LDDCKPAT1R, LDDCKPAT2R, LDMT1R, LDMT2R, LDMT3R, LDDFR, LDSM1R,
58 LDSM2R, LDSA1R, LDMLSR, LDHCNR, LDHSYNR, LDVLNR, LDVSYNR, LDPMR,
59 LDHAJR,
60 NR_CH_REGS };
61
62static unsigned long lcdc_offs_mainlcd[NR_CH_REGS] = { 56static unsigned long lcdc_offs_mainlcd[NR_CH_REGS] = {
63 [LDDCKPAT1R] = 0x400, 57 [LDDCKPAT1R] = 0x400,
64 [LDDCKPAT2R] = 0x404, 58 [LDDCKPAT2R] = 0x404,
@@ -112,25 +106,6 @@ static unsigned long lcdc_offs_sublcd[NR_CH_REGS] = {
112#define LDRCNTR_MRC 0x00000001 106#define LDRCNTR_MRC 0x00000001
113#define LDSR_MRS 0x00000100 107#define LDSR_MRS 0x00000100
114 108
115struct sh_mobile_lcdc_priv;
116struct sh_mobile_lcdc_chan {
117 struct sh_mobile_lcdc_priv *lcdc;
118 unsigned long *reg_offs;
119 unsigned long ldmt1r_value;
120 unsigned long enabled; /* ME and SE in LDCNT2R */
121 struct sh_mobile_lcdc_chan_cfg cfg;
122 u32 pseudo_palette[PALETTE_NR];
123 unsigned long saved_ch_regs[NR_CH_REGS];
124 struct fb_info *info;
125 dma_addr_t dma_handle;
126 struct fb_deferred_io defio;
127 struct scatterlist *sglist;
128 unsigned long frame_end;
129 unsigned long pan_offset;
130 wait_queue_head_t frame_end_wait;
131 struct completion vsync_completion;
132};
133
134struct sh_mobile_lcdc_priv { 109struct sh_mobile_lcdc_priv {
135 void __iomem *base; 110 void __iomem *base;
136 int irq; 111 int irq;
@@ -589,8 +564,10 @@ static int sh_mobile_lcdc_start(struct sh_mobile_lcdc_priv *priv)
589 continue; 564 continue;
590 565
591 board_cfg = &ch->cfg.board_cfg; 566 board_cfg = &ch->cfg.board_cfg;
592 if (board_cfg->display_on) 567 if (try_module_get(board_cfg->owner) && board_cfg->display_on) {
593 board_cfg->display_on(board_cfg->board_data, ch->info); 568 board_cfg->display_on(board_cfg->board_data, ch->info);
569 module_put(board_cfg->owner);
570 }
594 } 571 }
595 572
596 return 0; 573 return 0;
@@ -622,8 +599,10 @@ static void sh_mobile_lcdc_stop(struct sh_mobile_lcdc_priv *priv)
622 } 599 }
623 600
624 board_cfg = &ch->cfg.board_cfg; 601 board_cfg = &ch->cfg.board_cfg;
625 if (board_cfg->display_off) 602 if (try_module_get(board_cfg->owner) && board_cfg->display_off) {
626 board_cfg->display_off(board_cfg->board_data); 603 board_cfg->display_off(board_cfg->board_data);
604 module_put(board_cfg->owner);
605 }
627 } 606 }
628 607
629 /* stop the lcdc */ 608 /* stop the lcdc */
@@ -954,6 +933,7 @@ static const struct dev_pm_ops sh_mobile_lcdc_dev_pm_ops = {
954 .runtime_resume = sh_mobile_lcdc_runtime_resume, 933 .runtime_resume = sh_mobile_lcdc_runtime_resume,
955}; 934};
956 935
936/* locking: called with info->lock held */
957static int sh_mobile_lcdc_notify(struct notifier_block *nb, 937static int sh_mobile_lcdc_notify(struct notifier_block *nb,
958 unsigned long action, void *data) 938 unsigned long action, void *data)
959{ 939{
@@ -971,16 +951,20 @@ static int sh_mobile_lcdc_notify(struct notifier_block *nb,
971 951
972 switch(action) { 952 switch(action) {
973 case FB_EVENT_SUSPEND: 953 case FB_EVENT_SUSPEND:
974 if (board_cfg->display_off) 954 if (try_module_get(board_cfg->owner) && board_cfg->display_off) {
975 board_cfg->display_off(board_cfg->board_data); 955 board_cfg->display_off(board_cfg->board_data);
956 module_put(board_cfg->owner);
957 }
976 pm_runtime_put(info->device); 958 pm_runtime_put(info->device);
977 break; 959 break;
978 case FB_EVENT_RESUME: 960 case FB_EVENT_RESUME:
979 var = &info->var; 961 var = &info->var;
980 962
981 /* HDMI must be enabled before LCDC configuration */ 963 /* HDMI must be enabled before LCDC configuration */
982 if (board_cfg->display_on) 964 if (try_module_get(board_cfg->owner) && board_cfg->display_on) {
983 board_cfg->display_on(board_cfg->board_data, ch->info); 965 board_cfg->display_on(board_cfg->board_data, ch->info);
966 module_put(board_cfg->owner);
967 }
984 968
985 /* Check if the new display is not in our modelist */ 969 /* Check if the new display is not in our modelist */
986 if (ch->info->modelist.next && 970 if (ch->info->modelist.next &&
diff --git a/drivers/video/sh_mobile_lcdcfb.h b/drivers/video/sh_mobile_lcdcfb.h
new file mode 100644
index 000000000000..6fcfc0ffe3f8
--- /dev/null
+++ b/drivers/video/sh_mobile_lcdcfb.h
@@ -0,0 +1,37 @@
1#ifndef SH_MOBILE_LCDCFB_H
2#define SH_MOBILE_LCDCFB_H
3
4#include <linux/completion.h>
5#include <linux/fb.h>
6#include <linux/wait.h>
7
8/* per-channel registers */
9enum { LDDCKPAT1R, LDDCKPAT2R, LDMT1R, LDMT2R, LDMT3R, LDDFR, LDSM1R,
10 LDSM2R, LDSA1R, LDMLSR, LDHCNR, LDHSYNR, LDVLNR, LDVSYNR, LDPMR,
11 LDHAJR,
12 NR_CH_REGS };
13
14#define PALETTE_NR 16
15
16struct sh_mobile_lcdc_priv;
17struct fb_info;
18
19struct sh_mobile_lcdc_chan {
20 struct sh_mobile_lcdc_priv *lcdc;
21 unsigned long *reg_offs;
22 unsigned long ldmt1r_value;
23 unsigned long enabled; /* ME and SE in LDCNT2R */
24 struct sh_mobile_lcdc_chan_cfg cfg;
25 u32 pseudo_palette[PALETTE_NR];
26 unsigned long saved_ch_regs[NR_CH_REGS];
27 struct fb_info *info;
28 dma_addr_t dma_handle;
29 struct fb_deferred_io defio;
30 struct scatterlist *sglist;
31 unsigned long frame_end;
32 unsigned long pan_offset;
33 wait_queue_head_t frame_end_wait;
34 struct completion vsync_completion;
35};
36
37#endif
diff --git a/include/video/sh_mobile_lcdc.h b/include/video/sh_mobile_lcdc.h
index 19c69d788f3b..daabae5817c6 100644
--- a/include/video/sh_mobile_lcdc.h
+++ b/include/video/sh_mobile_lcdc.h
@@ -49,7 +49,9 @@ struct sh_mobile_lcdc_sys_bus_ops {
49 unsigned long (*read_data)(void *handle); 49 unsigned long (*read_data)(void *handle);
50}; 50};
51 51
52struct module;
52struct sh_mobile_lcdc_board_cfg { 53struct sh_mobile_lcdc_board_cfg {
54 struct module *owner;
53 void *board_data; 55 void *board_data;
54 int (*setup_sys)(void *board_data, void *sys_ops_handle, 56 int (*setup_sys)(void *board_data, void *sys_ops_handle,
55 struct sh_mobile_lcdc_sys_bus_ops *sys_ops); 57 struct sh_mobile_lcdc_sys_bus_ops *sys_ops);