diff options
author | Michael Buesch <mb@bu3sch.de> | 2007-02-12 03:53:26 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2007-02-12 12:48:35 -0500 |
commit | 053b47ff249b9e0a634dae807f81465205e7c228 (patch) | |
tree | 220695a694f2be08c26a836435e4ba9f093b9af8 | |
parent | a871fe858c5437ff8798fbaef52b6a88110b64a1 (diff) |
[PATCH] Workaround CAPI subsystem locking issue
I think the following patch should go into the kernel, until the ISDN/CAPI
guys create the real fix for this issue.
The issue is a concurrency issue with some internal CAPI data structure
which can crash the kernel.
On my FritzCard DSL with the AVM driver it crashes about once a day without
this workaround patch. With this workaround patch it's rock-stable (at
least on UP, but I don't see why this shouldn't work on SMP as well. But
maybe I'm missing something.)
This workaround is kind of a sledgehammer which inserts a global lock to
wrap around all the critical sections. Of course, this is a scalability
issue, if you have many ISDN/CAPI cards. But it prevents a crash. So I
vote for this fix to get merged, until people come up with a better
solution. Better have a stable kernel that's less scalable, than a
crashing and useless kernel.
This bug is in the kernel since 2.6.15 (at least).
Signed-off-by: Michael Buesch <mb@bu3sch.de>
Cc: Kai Germaschewski <kai.germaschewski@gmx.de>
Cc: Karsten Keil <kkeil@suse.de>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r-- | drivers/isdn/capi/capi.c | 35 |
1 files changed, 35 insertions, 0 deletions
diff --git a/drivers/isdn/capi/capi.c b/drivers/isdn/capi/capi.c index 38045910ca94..9e48bb5a3c09 100644 --- a/drivers/isdn/capi/capi.c +++ b/drivers/isdn/capi/capi.c | |||
@@ -118,6 +118,15 @@ struct capiminor { | |||
118 | }; | 118 | }; |
119 | #endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */ | 119 | #endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */ |
120 | 120 | ||
121 | /* FIXME: The following lock is a sledgehammer-workaround to a | ||
122 | * locking issue with the capiminor (and maybe other) data structure(s). | ||
123 | * Access to this data is done in a racy way and crashes the machine with | ||
124 | * a FritzCard DSL driver; sooner or later. This is a workaround | ||
125 | * which trades scalability vs stability, so it doesn't crash the kernel anymore. | ||
126 | * The correct (and scalable) fix for the issue seems to require | ||
127 | * an API change to the drivers... . */ | ||
128 | static DEFINE_SPINLOCK(workaround_lock); | ||
129 | |||
121 | struct capincci { | 130 | struct capincci { |
122 | struct capincci *next; | 131 | struct capincci *next; |
123 | u32 ncci; | 132 | u32 ncci; |
@@ -589,6 +598,7 @@ static void capi_recv_message(struct capi20_appl *ap, struct sk_buff *skb) | |||
589 | #endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */ | 598 | #endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */ |
590 | struct capincci *np; | 599 | struct capincci *np; |
591 | u32 ncci; | 600 | u32 ncci; |
601 | unsigned long flags; | ||
592 | 602 | ||
593 | if (CAPIMSG_CMD(skb->data) == CAPI_CONNECT_B3_CONF) { | 603 | if (CAPIMSG_CMD(skb->data) == CAPI_CONNECT_B3_CONF) { |
594 | u16 info = CAPIMSG_U16(skb->data, 12); // Info field | 604 | u16 info = CAPIMSG_U16(skb->data, 12); // Info field |
@@ -603,9 +613,11 @@ static void capi_recv_message(struct capi20_appl *ap, struct sk_buff *skb) | |||
603 | capincci_alloc(cdev, CAPIMSG_NCCI(skb->data)); | 613 | capincci_alloc(cdev, CAPIMSG_NCCI(skb->data)); |
604 | up(&cdev->ncci_list_sem); | 614 | up(&cdev->ncci_list_sem); |
605 | } | 615 | } |
616 | spin_lock_irqsave(&workaround_lock, flags); | ||
606 | if (CAPIMSG_COMMAND(skb->data) != CAPI_DATA_B3) { | 617 | if (CAPIMSG_COMMAND(skb->data) != CAPI_DATA_B3) { |
607 | skb_queue_tail(&cdev->recvqueue, skb); | 618 | skb_queue_tail(&cdev->recvqueue, skb); |
608 | wake_up_interruptible(&cdev->recvwait); | 619 | wake_up_interruptible(&cdev->recvwait); |
620 | spin_unlock_irqrestore(&workaround_lock, flags); | ||
609 | return; | 621 | return; |
610 | } | 622 | } |
611 | ncci = CAPIMSG_CONTROL(skb->data); | 623 | ncci = CAPIMSG_CONTROL(skb->data); |
@@ -615,6 +627,7 @@ static void capi_recv_message(struct capi20_appl *ap, struct sk_buff *skb) | |||
615 | printk(KERN_ERR "BUG: capi_signal: ncci not found\n"); | 627 | printk(KERN_ERR "BUG: capi_signal: ncci not found\n"); |
616 | skb_queue_tail(&cdev->recvqueue, skb); | 628 | skb_queue_tail(&cdev->recvqueue, skb); |
617 | wake_up_interruptible(&cdev->recvwait); | 629 | wake_up_interruptible(&cdev->recvwait); |
630 | spin_unlock_irqrestore(&workaround_lock, flags); | ||
618 | return; | 631 | return; |
619 | } | 632 | } |
620 | #ifndef CONFIG_ISDN_CAPI_MIDDLEWARE | 633 | #ifndef CONFIG_ISDN_CAPI_MIDDLEWARE |
@@ -625,6 +638,7 @@ static void capi_recv_message(struct capi20_appl *ap, struct sk_buff *skb) | |||
625 | if (!mp) { | 638 | if (!mp) { |
626 | skb_queue_tail(&cdev->recvqueue, skb); | 639 | skb_queue_tail(&cdev->recvqueue, skb); |
627 | wake_up_interruptible(&cdev->recvwait); | 640 | wake_up_interruptible(&cdev->recvwait); |
641 | spin_unlock_irqrestore(&workaround_lock, flags); | ||
628 | return; | 642 | return; |
629 | } | 643 | } |
630 | 644 | ||
@@ -660,6 +674,7 @@ static void capi_recv_message(struct capi20_appl *ap, struct sk_buff *skb) | |||
660 | wake_up_interruptible(&cdev->recvwait); | 674 | wake_up_interruptible(&cdev->recvwait); |
661 | } | 675 | } |
662 | #endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */ | 676 | #endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */ |
677 | spin_unlock_irqrestore(&workaround_lock, flags); | ||
663 | } | 678 | } |
664 | 679 | ||
665 | /* -------- file_operations for capidev ----------------------------- */ | 680 | /* -------- file_operations for capidev ----------------------------- */ |
@@ -1006,6 +1021,7 @@ static struct file_operations capi_fops = | |||
1006 | static int capinc_tty_open(struct tty_struct * tty, struct file * file) | 1021 | static int capinc_tty_open(struct tty_struct * tty, struct file * file) |
1007 | { | 1022 | { |
1008 | struct capiminor *mp; | 1023 | struct capiminor *mp; |
1024 | unsigned long flags; | ||
1009 | 1025 | ||
1010 | if ((mp = capiminor_find(iminor(file->f_path.dentry->d_inode))) == 0) | 1026 | if ((mp = capiminor_find(iminor(file->f_path.dentry->d_inode))) == 0) |
1011 | return -ENXIO; | 1027 | return -ENXIO; |
@@ -1014,6 +1030,7 @@ static int capinc_tty_open(struct tty_struct * tty, struct file * file) | |||
1014 | 1030 | ||
1015 | tty->driver_data = (void *)mp; | 1031 | tty->driver_data = (void *)mp; |
1016 | 1032 | ||
1033 | spin_lock_irqsave(&workaround_lock, flags); | ||
1017 | if (atomic_read(&mp->ttyopencount) == 0) | 1034 | if (atomic_read(&mp->ttyopencount) == 0) |
1018 | mp->tty = tty; | 1035 | mp->tty = tty; |
1019 | atomic_inc(&mp->ttyopencount); | 1036 | atomic_inc(&mp->ttyopencount); |
@@ -1021,6 +1038,7 @@ static int capinc_tty_open(struct tty_struct * tty, struct file * file) | |||
1021 | printk(KERN_DEBUG "capinc_tty_open ocount=%d\n", atomic_read(&mp->ttyopencount)); | 1038 | printk(KERN_DEBUG "capinc_tty_open ocount=%d\n", atomic_read(&mp->ttyopencount)); |
1022 | #endif | 1039 | #endif |
1023 | handle_minor_recv(mp); | 1040 | handle_minor_recv(mp); |
1041 | spin_unlock_irqrestore(&workaround_lock, flags); | ||
1024 | return 0; | 1042 | return 0; |
1025 | } | 1043 | } |
1026 | 1044 | ||
@@ -1054,6 +1072,7 @@ static int capinc_tty_write(struct tty_struct * tty, | |||
1054 | { | 1072 | { |
1055 | struct capiminor *mp = (struct capiminor *)tty->driver_data; | 1073 | struct capiminor *mp = (struct capiminor *)tty->driver_data; |
1056 | struct sk_buff *skb; | 1074 | struct sk_buff *skb; |
1075 | unsigned long flags; | ||
1057 | 1076 | ||
1058 | #ifdef _DEBUG_TTYFUNCS | 1077 | #ifdef _DEBUG_TTYFUNCS |
1059 | printk(KERN_DEBUG "capinc_tty_write(count=%d)\n", count); | 1078 | printk(KERN_DEBUG "capinc_tty_write(count=%d)\n", count); |
@@ -1066,6 +1085,7 @@ static int capinc_tty_write(struct tty_struct * tty, | |||
1066 | return 0; | 1085 | return 0; |
1067 | } | 1086 | } |
1068 | 1087 | ||
1088 | spin_lock_irqsave(&workaround_lock, flags); | ||
1069 | skb = mp->ttyskb; | 1089 | skb = mp->ttyskb; |
1070 | if (skb) { | 1090 | if (skb) { |
1071 | mp->ttyskb = NULL; | 1091 | mp->ttyskb = NULL; |
@@ -1076,6 +1096,7 @@ static int capinc_tty_write(struct tty_struct * tty, | |||
1076 | skb = alloc_skb(CAPI_DATA_B3_REQ_LEN+count, GFP_ATOMIC); | 1096 | skb = alloc_skb(CAPI_DATA_B3_REQ_LEN+count, GFP_ATOMIC); |
1077 | if (!skb) { | 1097 | if (!skb) { |
1078 | printk(KERN_ERR "capinc_tty_write: alloc_skb failed\n"); | 1098 | printk(KERN_ERR "capinc_tty_write: alloc_skb failed\n"); |
1099 | spin_unlock_irqrestore(&workaround_lock, flags); | ||
1079 | return -ENOMEM; | 1100 | return -ENOMEM; |
1080 | } | 1101 | } |
1081 | 1102 | ||
@@ -1086,6 +1107,7 @@ static int capinc_tty_write(struct tty_struct * tty, | |||
1086 | mp->outbytes += skb->len; | 1107 | mp->outbytes += skb->len; |
1087 | (void)handle_minor_send(mp); | 1108 | (void)handle_minor_send(mp); |
1088 | (void)handle_minor_recv(mp); | 1109 | (void)handle_minor_recv(mp); |
1110 | spin_unlock_irqrestore(&workaround_lock, flags); | ||
1089 | return count; | 1111 | return count; |
1090 | } | 1112 | } |
1091 | 1113 | ||
@@ -1093,6 +1115,7 @@ static void capinc_tty_put_char(struct tty_struct *tty, unsigned char ch) | |||
1093 | { | 1115 | { |
1094 | struct capiminor *mp = (struct capiminor *)tty->driver_data; | 1116 | struct capiminor *mp = (struct capiminor *)tty->driver_data; |
1095 | struct sk_buff *skb; | 1117 | struct sk_buff *skb; |
1118 | unsigned long flags; | ||
1096 | 1119 | ||
1097 | #ifdef _DEBUG_TTYFUNCS | 1120 | #ifdef _DEBUG_TTYFUNCS |
1098 | printk(KERN_DEBUG "capinc_put_char(%u)\n", ch); | 1121 | printk(KERN_DEBUG "capinc_put_char(%u)\n", ch); |
@@ -1105,10 +1128,12 @@ static void capinc_tty_put_char(struct tty_struct *tty, unsigned char ch) | |||
1105 | return; | 1128 | return; |
1106 | } | 1129 | } |
1107 | 1130 | ||
1131 | spin_lock_irqsave(&workaround_lock, flags); | ||
1108 | skb = mp->ttyskb; | 1132 | skb = mp->ttyskb; |
1109 | if (skb) { | 1133 | if (skb) { |
1110 | if (skb_tailroom(skb) > 0) { | 1134 | if (skb_tailroom(skb) > 0) { |
1111 | *(skb_put(skb, 1)) = ch; | 1135 | *(skb_put(skb, 1)) = ch; |
1136 | spin_unlock_irqrestore(&workaround_lock, flags); | ||
1112 | return; | 1137 | return; |
1113 | } | 1138 | } |
1114 | mp->ttyskb = NULL; | 1139 | mp->ttyskb = NULL; |
@@ -1124,12 +1149,14 @@ static void capinc_tty_put_char(struct tty_struct *tty, unsigned char ch) | |||
1124 | } else { | 1149 | } else { |
1125 | printk(KERN_ERR "capinc_put_char: char %u lost\n", ch); | 1150 | printk(KERN_ERR "capinc_put_char: char %u lost\n", ch); |
1126 | } | 1151 | } |
1152 | spin_unlock_irqrestore(&workaround_lock, flags); | ||
1127 | } | 1153 | } |
1128 | 1154 | ||
1129 | static void capinc_tty_flush_chars(struct tty_struct *tty) | 1155 | static void capinc_tty_flush_chars(struct tty_struct *tty) |
1130 | { | 1156 | { |
1131 | struct capiminor *mp = (struct capiminor *)tty->driver_data; | 1157 | struct capiminor *mp = (struct capiminor *)tty->driver_data; |
1132 | struct sk_buff *skb; | 1158 | struct sk_buff *skb; |
1159 | unsigned long flags; | ||
1133 | 1160 | ||
1134 | #ifdef _DEBUG_TTYFUNCS | 1161 | #ifdef _DEBUG_TTYFUNCS |
1135 | printk(KERN_DEBUG "capinc_tty_flush_chars\n"); | 1162 | printk(KERN_DEBUG "capinc_tty_flush_chars\n"); |
@@ -1142,6 +1169,7 @@ static void capinc_tty_flush_chars(struct tty_struct *tty) | |||
1142 | return; | 1169 | return; |
1143 | } | 1170 | } |
1144 | 1171 | ||
1172 | spin_lock_irqsave(&workaround_lock, flags); | ||
1145 | skb = mp->ttyskb; | 1173 | skb = mp->ttyskb; |
1146 | if (skb) { | 1174 | if (skb) { |
1147 | mp->ttyskb = NULL; | 1175 | mp->ttyskb = NULL; |
@@ -1150,6 +1178,7 @@ static void capinc_tty_flush_chars(struct tty_struct *tty) | |||
1150 | (void)handle_minor_send(mp); | 1178 | (void)handle_minor_send(mp); |
1151 | } | 1179 | } |
1152 | (void)handle_minor_recv(mp); | 1180 | (void)handle_minor_recv(mp); |
1181 | spin_unlock_irqrestore(&workaround_lock, flags); | ||
1153 | } | 1182 | } |
1154 | 1183 | ||
1155 | static int capinc_tty_write_room(struct tty_struct *tty) | 1184 | static int capinc_tty_write_room(struct tty_struct *tty) |
@@ -1220,12 +1249,15 @@ static void capinc_tty_throttle(struct tty_struct * tty) | |||
1220 | static void capinc_tty_unthrottle(struct tty_struct * tty) | 1249 | static void capinc_tty_unthrottle(struct tty_struct * tty) |
1221 | { | 1250 | { |
1222 | struct capiminor *mp = (struct capiminor *)tty->driver_data; | 1251 | struct capiminor *mp = (struct capiminor *)tty->driver_data; |
1252 | unsigned long flags; | ||
1223 | #ifdef _DEBUG_TTYFUNCS | 1253 | #ifdef _DEBUG_TTYFUNCS |
1224 | printk(KERN_DEBUG "capinc_tty_unthrottle\n"); | 1254 | printk(KERN_DEBUG "capinc_tty_unthrottle\n"); |
1225 | #endif | 1255 | #endif |
1226 | if (mp) { | 1256 | if (mp) { |
1257 | spin_lock_irqsave(&workaround_lock, flags); | ||
1227 | mp->ttyinstop = 0; | 1258 | mp->ttyinstop = 0; |
1228 | handle_minor_recv(mp); | 1259 | handle_minor_recv(mp); |
1260 | spin_unlock_irqrestore(&workaround_lock, flags); | ||
1229 | } | 1261 | } |
1230 | } | 1262 | } |
1231 | 1263 | ||
@@ -1243,12 +1275,15 @@ static void capinc_tty_stop(struct tty_struct *tty) | |||
1243 | static void capinc_tty_start(struct tty_struct *tty) | 1275 | static void capinc_tty_start(struct tty_struct *tty) |
1244 | { | 1276 | { |
1245 | struct capiminor *mp = (struct capiminor *)tty->driver_data; | 1277 | struct capiminor *mp = (struct capiminor *)tty->driver_data; |
1278 | unsigned long flags; | ||
1246 | #ifdef _DEBUG_TTYFUNCS | 1279 | #ifdef _DEBUG_TTYFUNCS |
1247 | printk(KERN_DEBUG "capinc_tty_start\n"); | 1280 | printk(KERN_DEBUG "capinc_tty_start\n"); |
1248 | #endif | 1281 | #endif |
1249 | if (mp) { | 1282 | if (mp) { |
1283 | spin_lock_irqsave(&workaround_lock, flags); | ||
1250 | mp->ttyoutstop = 0; | 1284 | mp->ttyoutstop = 0; |
1251 | (void)handle_minor_send(mp); | 1285 | (void)handle_minor_send(mp); |
1286 | spin_unlock_irqrestore(&workaround_lock, flags); | ||
1252 | } | 1287 | } |
1253 | } | 1288 | } |
1254 | 1289 | ||