diff options
author | Éric Piel <eric.piel@tremplin-utc.net> | 2011-10-31 20:10:31 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2011-10-31 20:30:51 -0400 |
commit | 1510dd5954be5070e46b155eb32362dc73d9e9cb (patch) | |
tree | 75d9a020f23c015739e3c17d742482b0866024b0 /drivers/misc/lis3lv02d/lis3lv02d.c | |
parent | 65807044760e03ebf766973c5e94a2ea1d57937b (diff) |
lis3lv02d: avoid divide by zero due to unchecked
After an "unexpected" reboot, I found this Oops in my logs:
divide error: 0000 [#1] PREEMPT SMP=20
CPU 0=20
Modules linked in: lis3lv02d hp_wmi input_polldev [...]
Pid: 390, comm: modprobe Tainted: G C 2.6.39-rc7-wl+=20
RIP: 0010:[<ffffffffa014b427>] [<ffffffffa014b427>]
lis3lv02d_poweron+0x4e/0x94 [lis3lv02d]
RSP: 0018:ffff8801d6407cf8 EFLAGS: 00010246
RAX: 0000000000000bb8 RBX: ffffffffa014e000 RCX: 0000000000000000
RDX: 0000000000000000 RSI: ffffea00066e4708 RDI: ffff8801df002700
RBP: ffff8801d6407d18 R08: ffffea00066c5a30 R09: ffffffff812498c9
R10: ffff8801d7bfcea0 R11: ffff8801d7bfce10 R12: 0000000000000bb8
R13: 00000000ffffffda R14: ffffffffa0154120 R15: ffffffffa0154030
=46S: 00007fc0705db700(0000) GS:ffff8801dfa00000(0000) knlGS:0
CS: 0010 DS: 0000 ES: 0000 CR0: 000000008005003b
CR2: 00007f33549174f0 CR3: 00000001d65c9000 CR4: 00000000000406f0
Process modprobe (pid: 390, threadinfo ffff8801d6406000, task ffff8801d6b40=
000)
Stack:
ffffffffa0154120 62ffffffa0154030 ffffffffa014e000 00000000ffffffea
ffff8801d6407d58 ffffffffa014bcc1 0000000000000000 0000000000000048
ffff8801d8bae800 00000000ffffffea 00000000ffffffda ffffffffa0154120
Call Trace:
[<ffffffffa014bcc1>] lis3lv02d_init_device+0x1ce/0x496 [lis3lv02d]
[<ffffffffa01522ff>] lis3lv02d_add+0x10f/0x17c [hp_accel]
[<ffffffff81233e11>] acpi_device_probe+0x49/0x117
[...]
Code: 3a 75 06 80 4d ef 50 eb 04 80 4d ef 40 0f b6 55 ef be 21
00 00 00 48 89 df ff 53 18 44 8b 63 6c e8 3e fc ff ff 89 c1 44
89 e0 99 <f7> f9 89 c7 e8 93 82 ef e0 48 83 7b 30 00 74 2d 45
31 e4 80 7b=20
RIP [<ffffffffa014b427>] lis3lv02d_poweron+0x4e/0x94 [lis3lv02d]
RSP <ffff8801d6407cf8>
>From my POV, it looks like the hardware is not working as expected
and returns a bogus data rate. The driver doesn't check the result
and directly uses it as some sort of divisor in some places:
msleep(lis3->pwron_delay / lis3lv02d_get_odr());
Under this circumstances, this could very well cause the
"divide by zero" exception from above.
For now, I fixed it the easiest and most obvious way:
Check if the result is sane and if it isn't use a sane default
instead. I went for "100" in the latter case, simply because
/sys/devices/platform/lis3lv02d/rate returns it on a successful
boot.
Signed-off-by: Christian Lamparter <chunkeey@googlemail.com>
Signed-off-by: Éric Piel <eric.piel@tremplin-utc.net>
Cc: Matthew Garrett <mjg@redhat.com>
Cc: Witold Pilat <witold.pilat@gmail.com>
Cc: Lyall Pearce <lyall.pearce@hp.com>
Cc: Malte Starostik <m-starostik@versanet.de>
Cc: Ilkka Koskinen <ilkka.koskinen@nokia.com>
Cc: Thadeu Lima de Souza Cascardo <cascardo@holoscopio.com>
Cc: Christian Lamparter <chunkeey@googlemail.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers/misc/lis3lv02d/lis3lv02d.c')
-rw-r--r-- | drivers/misc/lis3lv02d/lis3lv02d.c | 36 |
1 files changed, 30 insertions, 6 deletions
diff --git a/drivers/misc/lis3lv02d/lis3lv02d.c b/drivers/misc/lis3lv02d/lis3lv02d.c index 8b51cd62d067..1fc671566562 100644 --- a/drivers/misc/lis3lv02d/lis3lv02d.c +++ b/drivers/misc/lis3lv02d/lis3lv02d.c | |||
@@ -206,6 +206,18 @@ static int lis3lv02d_get_odr(void) | |||
206 | return lis3_dev.odrs[(ctrl >> shift)]; | 206 | return lis3_dev.odrs[(ctrl >> shift)]; |
207 | } | 207 | } |
208 | 208 | ||
209 | static int lis3lv02d_get_pwron_wait(struct lis3lv02d *lis3) | ||
210 | { | ||
211 | int div = lis3lv02d_get_odr(); | ||
212 | |||
213 | if (WARN_ONCE(div == 0, "device returned spurious data")) | ||
214 | return -ENXIO; | ||
215 | |||
216 | /* LIS3 power on delay is quite long */ | ||
217 | msleep(lis3->pwron_delay / div); | ||
218 | return 0; | ||
219 | } | ||
220 | |||
209 | static int lis3lv02d_set_odr(int rate) | 221 | static int lis3lv02d_set_odr(int rate) |
210 | { | 222 | { |
211 | u8 ctrl; | 223 | u8 ctrl; |
@@ -266,7 +278,9 @@ static int lis3lv02d_selftest(struct lis3lv02d *lis3, s16 results[3]) | |||
266 | 278 | ||
267 | lis3->read(lis3, ctlreg, ®); | 279 | lis3->read(lis3, ctlreg, ®); |
268 | lis3->write(lis3, ctlreg, (reg | selftest)); | 280 | lis3->write(lis3, ctlreg, (reg | selftest)); |
269 | msleep(lis3->pwron_delay / lis3lv02d_get_odr()); | 281 | ret = lis3lv02d_get_pwron_wait(lis3); |
282 | if (ret) | ||
283 | goto fail; | ||
270 | 284 | ||
271 | /* Read directly to avoid axis remap */ | 285 | /* Read directly to avoid axis remap */ |
272 | x = lis3->read_data(lis3, OUTX); | 286 | x = lis3->read_data(lis3, OUTX); |
@@ -275,7 +289,9 @@ static int lis3lv02d_selftest(struct lis3lv02d *lis3, s16 results[3]) | |||
275 | 289 | ||
276 | /* back to normal settings */ | 290 | /* back to normal settings */ |
277 | lis3->write(lis3, ctlreg, reg); | 291 | lis3->write(lis3, ctlreg, reg); |
278 | msleep(lis3->pwron_delay / lis3lv02d_get_odr()); | 292 | ret = lis3lv02d_get_pwron_wait(lis3); |
293 | if (ret) | ||
294 | goto fail; | ||
279 | 295 | ||
280 | results[0] = x - lis3->read_data(lis3, OUTX); | 296 | results[0] = x - lis3->read_data(lis3, OUTX); |
281 | results[1] = y - lis3->read_data(lis3, OUTY); | 297 | results[1] = y - lis3->read_data(lis3, OUTY); |
@@ -363,8 +379,9 @@ void lis3lv02d_poweroff(struct lis3lv02d *lis3) | |||
363 | } | 379 | } |
364 | EXPORT_SYMBOL_GPL(lis3lv02d_poweroff); | 380 | EXPORT_SYMBOL_GPL(lis3lv02d_poweroff); |
365 | 381 | ||
366 | void lis3lv02d_poweron(struct lis3lv02d *lis3) | 382 | int lis3lv02d_poweron(struct lis3lv02d *lis3) |
367 | { | 383 | { |
384 | int err; | ||
368 | u8 reg; | 385 | u8 reg; |
369 | 386 | ||
370 | lis3->init(lis3); | 387 | lis3->init(lis3); |
@@ -384,11 +401,14 @@ void lis3lv02d_poweron(struct lis3lv02d *lis3) | |||
384 | lis3->write(lis3, CTRL_REG2, reg); | 401 | lis3->write(lis3, CTRL_REG2, reg); |
385 | } | 402 | } |
386 | 403 | ||
387 | /* LIS3 power on delay is quite long */ | 404 | err = lis3lv02d_get_pwron_wait(lis3); |
388 | msleep(lis3->pwron_delay / lis3lv02d_get_odr()); | 405 | if (err) |
406 | return err; | ||
389 | 407 | ||
390 | if (lis3->reg_ctrl) | 408 | if (lis3->reg_ctrl) |
391 | lis3_context_restore(lis3); | 409 | lis3_context_restore(lis3); |
410 | |||
411 | return 0; | ||
392 | } | 412 | } |
393 | EXPORT_SYMBOL_GPL(lis3lv02d_poweron); | 413 | EXPORT_SYMBOL_GPL(lis3lv02d_poweron); |
394 | 414 | ||
@@ -928,7 +948,11 @@ int lis3lv02d_init_device(struct lis3lv02d *dev) | |||
928 | atomic_set(&dev->wake_thread, 0); | 948 | atomic_set(&dev->wake_thread, 0); |
929 | 949 | ||
930 | lis3lv02d_add_fs(dev); | 950 | lis3lv02d_add_fs(dev); |
931 | lis3lv02d_poweron(dev); | 951 | err = lis3lv02d_poweron(dev); |
952 | if (err) { | ||
953 | lis3lv02d_remove_fs(dev); | ||
954 | return err; | ||
955 | } | ||
932 | 956 | ||
933 | if (dev->pm_dev) { | 957 | if (dev->pm_dev) { |
934 | pm_runtime_set_active(dev->pm_dev); | 958 | pm_runtime_set_active(dev->pm_dev); |