diff options
author | Willem Penninckx <willem.penninckx@cs.kuleuven.be> | 2011-11-23 05:25:34 -0500 |
---|---|---|
committer | Jiri Kosina <jkosina@suse.cz> | 2011-11-23 06:30:25 -0500 |
commit | c196adf87514560f867492978ae350d4bbced0bd (patch) | |
tree | 9087524790420f16ed25c8a5245314d489bfc4af /drivers/hid/usbhid | |
parent | f2c4826c685b1ad9afdcef3649e3e60a3348491c (diff) |
HID: usbkbd: synchronize LED URB submission
usb_kbd_event() and usb_kbd_led() can be called concurrently, but they are not
synchronized. They both readwrite kbd->leds, and usb_kbd_event() originally just
checked the URB status field, while urb.h states that "It [status field] should
not be examined before the URB is returned to the completion handler."
To fix this unsynchronized behavior, this patch introduces a boolean
representing whether the URB is submitted, and a spinlock.
Signed-off-by: Willem Penninckx <willem.penninckx@cs.kuleuven.be>
Signed-off-by: Jiri Kosina <jkosina@suse.cz>
Diffstat (limited to 'drivers/hid/usbhid')
-rw-r--r-- | drivers/hid/usbhid/usbkbd.c | 63 |
1 files changed, 58 insertions, 5 deletions
diff --git a/drivers/hid/usbhid/usbkbd.c b/drivers/hid/usbhid/usbkbd.c index 065817329f03..052346f3ccb4 100644 --- a/drivers/hid/usbhid/usbkbd.c +++ b/drivers/hid/usbhid/usbkbd.c | |||
@@ -64,6 +64,32 @@ static const unsigned char usb_kbd_keycode[256] = { | |||
64 | 150,158,159,128,136,177,178,176,142,152,173,140 | 64 | 150,158,159,128,136,177,178,176,142,152,173,140 |
65 | }; | 65 | }; |
66 | 66 | ||
67 | |||
68 | /** | ||
69 | * struct usb_kbd - state of each attached keyboard | ||
70 | * @dev: input device associated with this keyboard | ||
71 | * @usbdev: usb device associated with this keyboard | ||
72 | * @old: data received in the past from the @irq URB representing which | ||
73 | * keys were pressed. By comparing with the current list of keys | ||
74 | * that are pressed, we are able to see key releases. | ||
75 | * @irq: URB for receiving a list of keys that are pressed when a | ||
76 | * new key is pressed or a key that was pressed is released. | ||
77 | * @led: URB for sending LEDs (e.g. numlock, ...) | ||
78 | * @newleds: data that will be sent with the @led URB representing which LEDs | ||
79 | should be on | ||
80 | * @name: Name of the keyboard. @dev's name field points to this buffer | ||
81 | * @phys: Physical path of the keyboard. @dev's phys field points to this | ||
82 | * buffer | ||
83 | * @new: Buffer for the @irq URB | ||
84 | * @cr: Control request for @led URB | ||
85 | * @leds: Buffer for the @led URB | ||
86 | * @new_dma: DMA address for @irq URB | ||
87 | * @leds_dma: DMA address for @led URB | ||
88 | * @leds_lock: spinlock that protects @leds, @newleds, and @led_urb_submitted | ||
89 | * @led_urb_submitted: indicates whether @led is in progress, i.e. it has been | ||
90 | * submitted and its completion handler has not returned yet | ||
91 | * without resubmitting @led | ||
92 | */ | ||
67 | struct usb_kbd { | 93 | struct usb_kbd { |
68 | struct input_dev *dev; | 94 | struct input_dev *dev; |
69 | struct usb_device *usbdev; | 95 | struct usb_device *usbdev; |
@@ -78,6 +104,10 @@ struct usb_kbd { | |||
78 | unsigned char *leds; | 104 | unsigned char *leds; |
79 | dma_addr_t new_dma; | 105 | dma_addr_t new_dma; |
80 | dma_addr_t leds_dma; | 106 | dma_addr_t leds_dma; |
107 | |||
108 | spinlock_t leds_lock; | ||
109 | bool led_urb_submitted; | ||
110 | |||
81 | }; | 111 | }; |
82 | 112 | ||
83 | static void usb_kbd_irq(struct urb *urb) | 113 | static void usb_kbd_irq(struct urb *urb) |
@@ -136,44 +166,66 @@ resubmit: | |||
136 | static int usb_kbd_event(struct input_dev *dev, unsigned int type, | 166 | static int usb_kbd_event(struct input_dev *dev, unsigned int type, |
137 | unsigned int code, int value) | 167 | unsigned int code, int value) |
138 | { | 168 | { |
169 | unsigned long flags; | ||
139 | struct usb_kbd *kbd = input_get_drvdata(dev); | 170 | struct usb_kbd *kbd = input_get_drvdata(dev); |
140 | 171 | ||
141 | if (type != EV_LED) | 172 | if (type != EV_LED) |
142 | return -1; | 173 | return -1; |
143 | 174 | ||
175 | spin_lock_irqsave(&kbd->leds_lock, flags); | ||
144 | kbd->newleds = (!!test_bit(LED_KANA, dev->led) << 3) | (!!test_bit(LED_COMPOSE, dev->led) << 3) | | 176 | kbd->newleds = (!!test_bit(LED_KANA, dev->led) << 3) | (!!test_bit(LED_COMPOSE, dev->led) << 3) | |
145 | (!!test_bit(LED_SCROLLL, dev->led) << 2) | (!!test_bit(LED_CAPSL, dev->led) << 1) | | 177 | (!!test_bit(LED_SCROLLL, dev->led) << 2) | (!!test_bit(LED_CAPSL, dev->led) << 1) | |
146 | (!!test_bit(LED_NUML, dev->led)); | 178 | (!!test_bit(LED_NUML, dev->led)); |
147 | 179 | ||
148 | if (kbd->led->status == -EINPROGRESS) | 180 | if (kbd->led_urb_submitted){ |
181 | spin_unlock_irqrestore(&kbd->leds_lock, flags); | ||
149 | return 0; | 182 | return 0; |
183 | } | ||
150 | 184 | ||
151 | if (*(kbd->leds) == kbd->newleds) | 185 | if (*(kbd->leds) == kbd->newleds){ |
186 | spin_unlock_irqrestore(&kbd->leds_lock, flags); | ||
152 | return 0; | 187 | return 0; |
188 | } | ||
153 | 189 | ||
154 | *(kbd->leds) = kbd->newleds; | 190 | *(kbd->leds) = kbd->newleds; |
191 | |||
155 | kbd->led->dev = kbd->usbdev; | 192 | kbd->led->dev = kbd->usbdev; |
156 | if (usb_submit_urb(kbd->led, GFP_ATOMIC)) | 193 | if (usb_submit_urb(kbd->led, GFP_ATOMIC)) |
157 | pr_err("usb_submit_urb(leds) failed\n"); | 194 | pr_err("usb_submit_urb(leds) failed\n"); |
158 | 195 | else | |
196 | kbd->led_urb_submitted = true; | ||
197 | |||
198 | spin_unlock_irqrestore(&kbd->leds_lock, flags); | ||
199 | |||
159 | return 0; | 200 | return 0; |
160 | } | 201 | } |
161 | 202 | ||
162 | static void usb_kbd_led(struct urb *urb) | 203 | static void usb_kbd_led(struct urb *urb) |
163 | { | 204 | { |
205 | unsigned long flags; | ||
164 | struct usb_kbd *kbd = urb->context; | 206 | struct usb_kbd *kbd = urb->context; |
165 | 207 | ||
166 | if (urb->status) | 208 | if (urb->status) |
167 | hid_warn(urb->dev, "led urb status %d received\n", | 209 | hid_warn(urb->dev, "led urb status %d received\n", |
168 | urb->status); | 210 | urb->status); |
169 | 211 | ||
170 | if (*(kbd->leds) == kbd->newleds) | 212 | spin_lock_irqsave(&kbd->leds_lock, flags); |
213 | |||
214 | if (*(kbd->leds) == kbd->newleds){ | ||
215 | kbd->led_urb_submitted = false; | ||
216 | spin_unlock_irqrestore(&kbd->leds_lock, flags); | ||
171 | return; | 217 | return; |
218 | } | ||
172 | 219 | ||
173 | *(kbd->leds) = kbd->newleds; | 220 | *(kbd->leds) = kbd->newleds; |
221 | |||
174 | kbd->led->dev = kbd->usbdev; | 222 | kbd->led->dev = kbd->usbdev; |
175 | if (usb_submit_urb(kbd->led, GFP_ATOMIC)) | 223 | if (usb_submit_urb(kbd->led, GFP_ATOMIC)){ |
176 | hid_err(urb->dev, "usb_submit_urb(leds) failed\n"); | 224 | hid_err(urb->dev, "usb_submit_urb(leds) failed\n"); |
225 | kbd->led_urb_submitted = false; | ||
226 | } | ||
227 | spin_unlock_irqrestore(&kbd->leds_lock, flags); | ||
228 | |||
177 | } | 229 | } |
178 | 230 | ||
179 | static int usb_kbd_open(struct input_dev *dev) | 231 | static int usb_kbd_open(struct input_dev *dev) |
@@ -252,6 +304,7 @@ static int usb_kbd_probe(struct usb_interface *iface, | |||
252 | 304 | ||
253 | kbd->usbdev = dev; | 305 | kbd->usbdev = dev; |
254 | kbd->dev = input_dev; | 306 | kbd->dev = input_dev; |
307 | spin_lock_init(&kbd->leds_lock); | ||
255 | 308 | ||
256 | if (dev->manufacturer) | 309 | if (dev->manufacturer) |
257 | strlcpy(kbd->name, dev->manufacturer, sizeof(kbd->name)); | 310 | strlcpy(kbd->name, dev->manufacturer, sizeof(kbd->name)); |