source: mainline/uspace/drv/char/ps2mouse/ps2mouse.c@ 8bb9540

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 8bb9540 was 8bb9540, checked in by Jan Vesely <jano.vesely@…>, 14 years ago

i8042, ps2mouse, xtkbd: Drop optical separators.

Requested on ML.

  • Property mode set to 100644
File size: 11.9 KB
Line 
1/*
2 * Copyright (c) 2011 Jan Vesely
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28/** @addtogroup drvmouse
29 * @{
30 */
31/** @file
32 * @brief ps2 mouse driver.
33 */
34
35#include <bool.h>
36#include <errno.h>
37#include <devman.h>
38#include <device/char_dev.h>
39#include <ddf/log.h>
40#include <io/keycode.h>
41#include <io/console.h>
42#include <ipc/mouseev.h>
43#include <abi/ipc/methods.h>
44
45#include "ps2mouse.h"
46
47#define PS2_MOUSE_GET_DEVICE_ID 0xf2
48#define PS2_MOUSE_SET_SAMPLE_RATE 0xf3
49#define PS2_MOUSE_ENABLE_DATA_REPORT 0xf4
50#define PS2_MOUSE_ACK 0xfa
51
52#define PS2_BUFSIZE 3
53#define INTELLIMOUSE_BUFSIZE 4
54
55#define Z_SIGN (1 << 3) /* 4th byte */
56#define X_SIGN (1 << 4) /* 1st byte */
57#define Y_SIGN (1 << 5) /* 1st byte */
58#define X_OVERFLOW (1 << 6) /* 1st byte */
59#define Y_OVERFLOW (1 << 7) /* 1st byte */
60
61#define BUTTON_LEFT 0
62#define BUTTON_RIGHT 1
63#define BUTTON_MIDDLE 2
64#define PS2_BUTTON_COUNT 3
65
66#define INTELLIMOUSE_ALWAYS_ZERO (0xc0)
67#define INTELLIMOUSE_BUTTON_4 (1 << 4) /* 4th byte */
68#define INTELLIMOUSE_BUTTON_5 (1 << 5) /* 4th byte */
69#define INTELLIMOUSE_BUTTON_COUNT 5
70
71#define PS2_BUTTON_MASK(button) (1 << button)
72
73#define MOUSE_READ_BYTE_TEST(sess, value) \
74do { \
75 uint8_t data = 0; \
76 const ssize_t size = char_dev_read(session, &data, 1); \
77 if (size != 1) { \
78 ddf_msg(LVL_ERROR, "Failed reading byte: %d)", size);\
79 return size < 0 ? size : EIO; \
80 } \
81 if (data != (value)) { \
82 ddf_msg(LVL_ERROR, "Failed testing byte: got %hhx vs. %hhx)", \
83 data, (value)); \
84 return EIO; \
85 } \
86} while (0)
87#define MOUSE_WRITE_BYTE(sess, value) \
88do { \
89 uint8_t data = (value); \
90 const ssize_t size = char_dev_write(session, &data, 1); \
91 if (size < 0 ) { \
92 ddf_msg(LVL_ERROR, "Failed writing byte: %hhx", value); \
93 return size; \
94 } \
95} while (0)
96
97static int polling_ps2(void *);
98static int polling_intellimouse(void *);
99static int probe_intellimouse(async_sess_t *, bool);
100static void default_connection_handler(ddf_fun_t *, ipc_callid_t, ipc_call_t *);
101
102/** ps/2 mouse driver ops. */
103static ddf_dev_ops_t mouse_ops = {
104 .default_handler = default_connection_handler
105};
106
107/** Initialize mouse driver structure.
108 * @param kbd Mouse driver structure to initialize.
109 * @param dev DDF device structure.
110 *
111 * Connects to parent, creates keyboard function, starts polling fibril.
112 */
113int ps2_mouse_init(ps2_mouse_t *mouse, ddf_dev_t *dev)
114{
115 assert(mouse);
116 assert(dev);
117 mouse->input_sess = NULL;
118 mouse->parent_sess = devman_parent_device_connect(EXCHANGE_SERIALIZE,
119 dev->handle, IPC_FLAG_BLOCKING);
120 if (!mouse->parent_sess)
121 return ENOMEM;
122
123 mouse->mouse_fun = ddf_fun_create(dev, fun_exposed, "mouse");
124 if (!mouse->mouse_fun) {
125 async_hangup(mouse->parent_sess);
126 return ENOMEM;
127 }
128 mouse->mouse_fun->ops = &mouse_ops;
129 mouse->mouse_fun->driver_data = mouse;
130
131 int ret = ddf_fun_bind(mouse->mouse_fun);
132 if (ret != EOK) {
133 async_hangup(mouse->parent_sess);
134 mouse->mouse_fun->driver_data = NULL;
135 ddf_fun_destroy(mouse->mouse_fun);
136 return ENOMEM;
137 }
138
139 ret = ddf_fun_add_to_category(mouse->mouse_fun, "mouse");
140 if (ret != EOK) {
141 async_hangup(mouse->parent_sess);
142 ddf_fun_unbind(mouse->mouse_fun);
143 mouse->mouse_fun->driver_data = NULL;
144 ddf_fun_destroy(mouse->mouse_fun);
145 return ENOMEM;
146 }
147 /* Probe IntelliMouse extensions. */
148 int (*polling_f)(void*) = polling_ps2;
149 if (probe_intellimouse(mouse->parent_sess, false) == EOK) {
150 ddf_msg(LVL_NOTE, "Enabled IntelliMouse extensions");
151 polling_f = polling_intellimouse;
152 if (probe_intellimouse(mouse->parent_sess, true) == EOK)
153 ddf_msg(LVL_NOTE, "Enabled 4th and 5th button.");
154 }
155 /* Enable mouse data reporting. */
156 uint8_t report = PS2_MOUSE_ENABLE_DATA_REPORT;
157 ssize_t size = char_dev_write(mouse->parent_sess, &report, 1);
158 if (size != 1) {
159 ddf_msg(LVL_ERROR, "Failed to enable data reporting.");
160 async_hangup(mouse->parent_sess);
161 ddf_fun_unbind(mouse->mouse_fun);
162 mouse->mouse_fun->driver_data = NULL;
163 ddf_fun_destroy(mouse->mouse_fun);
164 return EIO;
165 }
166
167 size = char_dev_read(mouse->parent_sess, &report, 1);
168 if (size != 1 || report != PS2_MOUSE_ACK) {
169 ddf_msg(LVL_ERROR, "Failed to confirm data reporting: %hhx.",
170 report);
171 async_hangup(mouse->parent_sess);
172 ddf_fun_unbind(mouse->mouse_fun);
173 mouse->mouse_fun->driver_data = NULL;
174 ddf_fun_destroy(mouse->mouse_fun);
175 return EIO;
176 }
177
178 mouse->polling_fibril = fibril_create(polling_f, mouse);
179 if (!mouse->polling_fibril) {
180 async_hangup(mouse->parent_sess);
181 ddf_fun_unbind(mouse->mouse_fun);
182 mouse->mouse_fun->driver_data = NULL;
183 ddf_fun_destroy(mouse->mouse_fun);
184 return ENOMEM;
185 }
186 fibril_add_ready(mouse->polling_fibril);
187 return EOK;
188}
189
190/** Get data and parse ps2 protocol packets.
191 * @param arg Pointer to ps2_mouse_t structure.
192 * @return Never.
193 */
194int polling_ps2(void *arg)
195{
196 assert(arg);
197 const ps2_mouse_t *mouse = arg;
198
199 assert(mouse->parent_sess);
200 bool buttons[PS2_BUTTON_COUNT] = {};
201 while (1) {
202
203 uint8_t packet[PS2_BUFSIZE] = {};
204 const ssize_t size =
205 char_dev_read(mouse->parent_sess, packet, PS2_BUFSIZE);
206
207 if (size != PS2_BUFSIZE) {
208 ddf_msg(LVL_WARN, "Incorrect packet size: %zd.", size);
209 continue;
210 }
211 ddf_msg(LVL_DEBUG2, "Got packet: %hhx:%hhx:%hhx.",
212 packet[0], packet[1], packet[2]);
213
214 async_exch_t *exch =
215 async_exchange_begin(mouse->input_sess);
216 if (!exch) {
217 ddf_msg(LVL_ERROR,
218 "Failed to create input exchange.");
219 continue;
220 }
221
222 /* Buttons */
223 for (unsigned i = 0; i < PS2_BUTTON_COUNT; ++i) {
224 const bool status = (packet[0] & PS2_BUTTON_MASK(i));
225 if (buttons[i] != status) {
226 buttons[i] = status;
227 async_msg_2(exch, MOUSEEV_BUTTON_EVENT, i + 1,
228 buttons[i]);
229 }
230 }
231
232 /* Movement */
233 const int16_t move_x =
234 ((packet[0] & X_SIGN) ? 0xff00 : 0) | packet[1];
235 const int16_t move_y =
236 (((packet[0] & Y_SIGN) ? 0xff00 : 0) | packet[2]);
237 //TODO: Consider overflow bit
238 if (move_x != 0 || move_y != 0) {
239 async_msg_2(exch, MOUSEEV_MOVE_EVENT, move_x, -move_y);
240 }
241 async_exchange_end(exch);
242 }
243}
244
245/** Get data and parse ps2 protocol with IntelliMouse extension packets.
246 * @param arg Pointer to ps2_mouse_t structure.
247 * @return Never.
248 */
249static int polling_intellimouse(void *arg)
250{
251 assert(arg);
252 const ps2_mouse_t *mouse = arg;
253
254 assert(mouse->parent_sess);
255 bool buttons[INTELLIMOUSE_BUTTON_COUNT] = {};
256 while (1) {
257
258 uint8_t packet[INTELLIMOUSE_BUFSIZE] = {};
259 const ssize_t size = char_dev_read(
260 mouse->parent_sess, packet, INTELLIMOUSE_BUFSIZE);
261
262 if (size != INTELLIMOUSE_BUFSIZE) {
263 ddf_msg(LVL_WARN, "Incorrect packet size: %zd.", size);
264 continue;
265 }
266 ddf_msg(LVL_DEBUG2, "Got packet: %hhx:%hhx:%hhx:%hhx.",
267 packet[0], packet[1], packet[2], packet[3]);
268
269 async_exch_t *exch =
270 async_exchange_begin(mouse->input_sess);
271 if (!exch) {
272 ddf_msg(LVL_ERROR,
273 "Failed to create input exchange.");
274 continue;
275 }
276
277 /* Buttons */
278 /* NOTE: Parsing 4th and 5th button works even if this extension
279 * is not supported and whole 4th byte should be interpreted
280 * as Z-axis movement. the upper 4 bits are just a sign
281 * extension then. + sign is interpreted as "button up"
282 * (i.e no change since that is the default) and - sign fails
283 * the "imb" condition. Thus 4th and 5th buttons are never
284 * down on wheel only extension. */
285 const bool imb = (packet[3] & INTELLIMOUSE_ALWAYS_ZERO) == 0;
286 const bool status[] = {
287 [0] = packet[0] & PS2_BUTTON_MASK(0),
288 [1] = packet[0] & PS2_BUTTON_MASK(1),
289 [2] = packet[0] & PS2_BUTTON_MASK(2),
290 [3] = (packet[3] & INTELLIMOUSE_BUTTON_4) && imb,
291 [4] = (packet[3] & INTELLIMOUSE_BUTTON_5) && imb,
292 };
293 for (unsigned i = 0; i < INTELLIMOUSE_BUTTON_COUNT; ++i) {
294 if (buttons[i] != status[i]) {
295 buttons[i] = status[i];
296 async_msg_2(exch, MOUSEEV_BUTTON_EVENT, i + 1,
297 buttons[i]);
298 }
299 }
300
301 /* Movement */
302 const int16_t move_x =
303 ((packet[0] & X_SIGN) ? 0xff00 : 0) | packet[1];
304 const int16_t move_y =
305 (((packet[0] & Y_SIGN) ? 0xff00 : 0) | packet[2]);
306 const int8_t move_z =
307 (((packet[3] & Z_SIGN) ? 0xf0 : 0) | (packet[3] & 0xf));
308 ddf_msg(LVL_DEBUG2, "Parsed moves: %d:%d:%hhd", move_x, move_y,
309 move_z);
310 //TODO: Consider overflow bit
311 if (move_x != 0 || move_y != 0 || move_z != 0) {
312 async_msg_3(exch, MOUSEEV_MOVE_EVENT,
313 move_x, -move_y, -move_z);
314 }
315 async_exchange_end(exch);
316 }
317}
318
319/** Send magic sequence to initialize IntelliMouse extensions.
320 * @param session IPC session to the parent device.
321 * @param buttons True selects magic sequence for 4th and 5th button,
322 * false selects wheel support magic sequence.
323 * See http://www.computer-engineering.org/ps2mouse/ for details.
324 */
325static int probe_intellimouse(async_sess_t *session, bool buttons)
326{
327 assert(session);
328
329 MOUSE_WRITE_BYTE(session, PS2_MOUSE_SET_SAMPLE_RATE);
330 MOUSE_READ_BYTE_TEST(session, PS2_MOUSE_ACK);
331 MOUSE_WRITE_BYTE(session, 200);
332 MOUSE_READ_BYTE_TEST(session, PS2_MOUSE_ACK);
333
334 MOUSE_WRITE_BYTE(session, PS2_MOUSE_SET_SAMPLE_RATE);
335 MOUSE_READ_BYTE_TEST(session, PS2_MOUSE_ACK);
336 MOUSE_WRITE_BYTE(session, buttons ? 200 : 100);
337 MOUSE_READ_BYTE_TEST(session, PS2_MOUSE_ACK);
338
339 MOUSE_WRITE_BYTE(session, PS2_MOUSE_SET_SAMPLE_RATE);
340 MOUSE_READ_BYTE_TEST(session, PS2_MOUSE_ACK);
341 MOUSE_WRITE_BYTE(session, 80);
342 MOUSE_READ_BYTE_TEST(session, PS2_MOUSE_ACK);
343
344 MOUSE_WRITE_BYTE(session, PS2_MOUSE_GET_DEVICE_ID);
345 MOUSE_READ_BYTE_TEST(session, PS2_MOUSE_ACK);
346 MOUSE_READ_BYTE_TEST(session, buttons ? 4 : 3);
347
348 return EOK;
349}
350
351/** Default handler for IPC methods not handled by DDF.
352 *
353 * @param fun Device function handling the call.
354 * @param icallid Call id.
355 * @param icall Call data.
356 */
357void default_connection_handler(ddf_fun_t *fun,
358 ipc_callid_t icallid, ipc_call_t *icall)
359{
360 if (fun == NULL || fun->driver_data == NULL) {
361 ddf_msg(LVL_ERROR, "%s: Missing parameter.", __FUNCTION__);
362 async_answer_0(icallid, EINVAL);
363 return;
364 }
365
366 const sysarg_t method = IPC_GET_IMETHOD(*icall);
367 ps2_mouse_t *mouse = fun->driver_data;
368
369 switch (method) {
370 /* This might be ugly but async_callback_receive_start makes no
371 * difference for incorrect call and malloc failure. */
372 case IPC_M_CONNECT_TO_ME: {
373 async_sess_t *sess =
374 async_callback_receive_start(EXCHANGE_SERIALIZE, icall);
375 /* Probably ENOMEM error, try again. */
376 if (sess == NULL) {
377 ddf_msg(LVL_WARN,
378 "Failed to create start input session");
379 async_answer_0(icallid, EAGAIN);
380 break;
381 }
382 if (mouse->input_sess == NULL) {
383 mouse->input_sess = sess;
384 ddf_msg(LVL_DEBUG, "Set input session");
385 async_answer_0(icallid, EOK);
386 } else {
387 ddf_msg(LVL_ERROR, "Input session already set");
388 async_answer_0(icallid, ELIMIT);
389 }
390 break;
391 }
392 default:
393 ddf_msg(LVL_ERROR, "Unknown method: %d.", (int)method);
394 async_answer_0(icallid, EINVAL);
395 break;
396 }
397}
Note: See TracBrowser for help on using the repository browser.