/* HIDP implementation for Linux Bluetooth stack (BlueZ). Copyright (C) 2003-2004 Marcel Holtmann <marcel@holtmann.org> Copyright (C) 2013 David Herrmann <dh.herrmann@gmail.com>
This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License version 2 as published by the Free Software Foundation;
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS SOFTWARE IS DISCLAIMED.
*/
switch (skb->data[0]) { case 0x01: /* Keyboard report */ for (i = 0; i < 8; i++)
input_report_key(dev, hidp_keycode[i + 224], (udata[0] >> i) & 1);
/* If all the key codes have been set to 0x01, it means
* too many keys were pressed at the same time. */ if (!memcmp(udata + 2, hidp_mkeyspat, 6)) break;
for (i = 2; i < 8; i++) { if (keys[i] > 3 && memscan(udata + 2, keys[i], 6) == udata + 8) { if (hidp_keycode[keys[i]])
input_report_key(dev, hidp_keycode[keys[i]], 0); else
BT_ERR("Unknown key (scancode %#x) released.", keys[i]);
}
if (mutex_lock_interruptible(&session->report_mutex)) return -ERESTARTSYS;
/* Set up our wait, and send the report request to the device. */
session->waiting_report_type = report_type & HIDP_DATA_RTYPE_MASK;
session->waiting_report_number = numbered_reports ? report_number : -1;
set_bit(HIDP_WAITING_FOR_RETURN, &session->flags);
data[0] = report_number;
ret = hidp_send_ctrl_message(session, report_type, data, 1); if (ret < 0) goto err;
/* Wait for the return of the report. The returned report
gets put in session->report_return. */ while (test_bit(HIDP_WAITING_FOR_RETURN, &session->flags) &&
!atomic_read(&session->terminate)) { int res;
res = wait_event_interruptible_timeout(session->report_queue,
!test_bit(HIDP_WAITING_FOR_RETURN, &session->flags)
|| atomic_read(&session->terminate),
5*HZ); if (res == 0) { /* timeout */
ret = -EIO; goto err;
} if (res < 0) { /* signal */
ret = -ERESTARTSYS; goto err;
}
}
skb = session->report_return; if (skb) {
len = skb->len < count ? skb->len : count;
memcpy(data, skb->data, len);
kfree_skb(skb);
session->report_return = NULL;
} else { /* Device returned a HANDSHAKE, indicating protocol error. */
len = -EIO;
}
if (mutex_lock_interruptible(&session->report_mutex)) return -ERESTARTSYS;
/* Set up our wait, and send the report request to the device. */
data[0] = reportnum;
set_bit(HIDP_WAITING_FOR_SEND_ACK, &session->flags);
ret = hidp_send_ctrl_message(session, report_type, data, count); if (ret < 0) goto err;
/* Wait for the ACK from the device. */ while (test_bit(HIDP_WAITING_FOR_SEND_ACK, &session->flags) &&
!atomic_read(&session->terminate)) { int res;
res = wait_event_interruptible_timeout(session->report_queue,
!test_bit(HIDP_WAITING_FOR_SEND_ACK, &session->flags)
|| atomic_read(&session->terminate),
10*HZ); if (res == 0) { /* timeout */
ret = -EIO; goto err;
} if (res < 0) { /* signal */
ret = -ERESTARTSYS; goto err;
}
}
if (!session->output_report_success) {
ret = -EIO; goto err;
}
/* The HIDP user-space API only contains calls to add and remove * devices. There is no way to forward events of any kind. Therefore, * we have to forcefully disconnect a device on idle-timeouts. This is * unfortunate and weird API design, but it is spec-compliant and * required for backwards-compatibility. Hence, on idle-timeout, we * signal driver-detach events, so poll() will be woken up with an * error-condition on both sockets.
*/
switch (param) { case HIDP_HSHK_SUCCESSFUL: /* FIXME: Call into SET_ GET_ handlers here */
session->output_report_success = 1; break;
case HIDP_HSHK_NOT_READY: case HIDP_HSHK_ERR_INVALID_REPORT_ID: case HIDP_HSHK_ERR_UNSUPPORTED_REQUEST: case HIDP_HSHK_ERR_INVALID_PARAMETER: if (test_and_clear_bit(HIDP_WAITING_FOR_RETURN, &session->flags))
wake_up_interruptible(&session->report_queue);
/* FIXME: Call into SET_ GET_ handlers here */ break;
case HIDP_HSHK_ERR_UNKNOWN: break;
case HIDP_HSHK_ERR_FATAL: /* Device requests a reboot, as this is the only way this error
* can be recovered. */
hidp_send_ctrl_message(session,
HIDP_TRANS_HID_CONTROL | HIDP_CTRL_SOFT_RESET, NULL, 0); break;
if (param == HIDP_CTRL_VIRTUAL_CABLE_UNPLUG) { /* Flush the transmit queues */
skb_queue_purge(&session->ctrl_transmit);
skb_queue_purge(&session->intr_transmit);
hidp_session_terminate(session);
}
}
/* Returns true if the passed-in skb should be freed by the caller. */ staticint hidp_process_data(struct hidp_session *session, struct sk_buff *skb, unsignedchar param)
{ int done_with_skb = 1;
BT_DBG("session %p skb %p len %u param 0x%02x", session, skb, skb->len, param);
switch (param) { case HIDP_DATA_RTYPE_INPUT:
hidp_set_timer(session);
if (session->input)
hidp_input_report(session, skb);
if (session->hid)
hidp_process_report(session, HID_INPUT_REPORT,
skb->data, skb->len, 0); break;
case HIDP_DATA_RTYPE_OTHER: case HIDP_DATA_RTYPE_OUPUT: case HIDP_DATA_RTYPE_FEATURE: break;
/* This function sets up the hid device. It does not add it
to the HID system. That is done in hidp_add_connection(). */ staticint hidp_setup_hid(struct hidp_session *session, conststruct hidp_connadd_req *req)
{ struct hid_device *hid; int err;
session->rd_data = memdup_user(req->rd_data, req->rd_size); if (IS_ERR(session->rd_data)) return PTR_ERR(session->rd_data);
/* NOTE: Some device modules depend on the dst address being stored in * uniq. Please be aware of this before making changes to this behavior.
*/
snprintf(hid->uniq, sizeof(hid->uniq), "%pMR",
&l2cap_pi(session->ctrl_sock->sk)->chan->dst);
/* True if device is blocked in drivers/hid/hid-quirks.c */ if (hid_ignore(hid)) {
hid_destroy_device(session->hid);
session->hid = NULL; return -ENODEV;
}
/* add HID/input devices to their underlying bus systems */ staticint hidp_session_dev_add(struct hidp_session *session)
{ int ret;
/* Both HID and input systems drop a ref-count when unregistering the * device but they don't take a ref-count when registering them. Work * around this by explicitly taking a refcount during registration
* which is dropped automatically by unregistering the devices. */
if (session->hid) {
ret = hid_add_device(session->hid); if (ret) return ret;
get_device(&session->hid->dev);
} elseif (session->input) {
ret = input_register_device(session->input); if (ret) return ret;
input_get_device(session->input);
}
return 0;
}
/* remove HID/input devices from their bus systems */ staticvoid hidp_session_dev_del(struct hidp_session *session)
{ if (session->hid)
hid_destroy_device(session->hid); elseif (session->input)
input_unregister_device(session->input);
}
/* * Asynchronous device registration * HID device drivers might want to perform I/O during initialization to * detect device types. Therefore, call device registration in a separate * worker so the HIDP thread can schedule I/O operations. * Note that this must be called after the worker thread was initialized * successfully. This will then add the devices and increase session state * on success, otherwise it will terminate the session thread.
*/ staticvoid hidp_session_dev_work(struct work_struct *work)
{ struct hidp_session *session = container_of(work, struct hidp_session,
dev_init); int ret;
ret = hidp_session_dev_add(session); if (!ret)
atomic_inc(&session->state); else
hidp_session_terminate(session);
}
/* * Create new session object * Allocate session object, initialize static fields, copy input data into the * object and take a reference to all sub-objects. * This returns 0 on success and puts a pointer to the new session object in * \out. Otherwise, an error code is returned. * The new session object has an initial ref-count of 1.
*/ staticint hidp_session_new(struct hidp_session **out, const bdaddr_t *bdaddr, struct socket *ctrl_sock, struct socket *intr_sock, conststruct hidp_connadd_req *req, struct l2cap_conn *conn)
{ struct hidp_session *session; int ret; struct bt_sock *ctrl, *intr;
/* decrease ref-count of the given session by one */ staticvoid hidp_session_put(struct hidp_session *session)
{
kref_put(&session->ref, session_free);
}
/* * Search the list of active sessions for a session with target address * \bdaddr. You must hold at least a read-lock on \hidp_session_sem. As long as * you do not release this lock, the session objects cannot vanish and you can * safely take a reference to the session yourself.
*/ staticstruct hidp_session *__hidp_session_find(const bdaddr_t *bdaddr)
{ struct hidp_session *session;
list_for_each_entry(session, &hidp_session_list, list) { if (!bacmp(bdaddr, &session->bdaddr)) return session;
}
return NULL;
}
/* * Same as __hidp_session_find() but no locks must be held. This also takes a * reference of the returned session (if non-NULL) so you must drop this * reference if you no longer use the object.
*/ staticstruct hidp_session *hidp_session_find(const bdaddr_t *bdaddr)
{ struct hidp_session *session;
down_read(&hidp_session_sem);
session = __hidp_session_find(bdaddr); if (session)
hidp_session_get(session);
up_read(&hidp_session_sem);
return session;
}
/* * Start session synchronously * This starts a session thread and waits until initialization * is done or returns an error if it couldn't be started. * If this returns 0 the session thread is up and running. You must call * hipd_session_stop_sync() before deleting any runtime resources.
*/ staticint hidp_session_start_sync(struct hidp_session *session)
{ unsignedint vendor, product;
while (atomic_read(&session->state) <= HIDP_SESSION_IDLING)
wait_event(session->state_queue,
atomic_read(&session->state) > HIDP_SESSION_IDLING);
return 0;
}
/* * Terminate session thread * Wake up session thread and notify it to stop. This is asynchronous and * returns immediately. Call this whenever a runtime error occurs and you want * the session to stop. * Note: wake_up_interruptible() performs any necessary memory-barriers for us.
*/ staticvoid hidp_session_terminate(struct hidp_session *session)
{
atomic_inc(&session->terminate); /* * See the comment preceding the call to wait_woken() * in hidp_session_run().
*/
wake_up_interruptible(&hidp_session_wq);
}
/* * Probe HIDP session * This is called from the l2cap_conn core when our l2cap_user object is bound * to the hci-connection. We get the session via the \user object and can now * start the session thread, link it into the global session list and * schedule HID/input device registration. * The global session-list owns its own reference to the session object so you * can drop your own reference after registering the l2cap_user object.
*/ staticint hidp_session_probe(struct l2cap_conn *conn, struct l2cap_user *user)
{ struct hidp_session *session = container_of(user, struct hidp_session,
user); struct hidp_session *s; int ret;
down_write(&hidp_session_sem);
/* check that no other session for this device exists */
s = __hidp_session_find(&session->bdaddr); if (s) {
ret = -EEXIST; goto out_unlock;
}
if (session->input) {
ret = hidp_session_dev_add(session); if (ret) goto out_unlock;
}
ret = hidp_session_start_sync(session); if (ret) goto out_del;
/* HID device registration is async to allow I/O during probe */ if (session->input)
atomic_inc(&session->state); else
schedule_work(&session->dev_init);
hidp_session_get(session);
list_add(&session->list, &hidp_session_list);
ret = 0; goto out_unlock;
out_del: if (session->input)
hidp_session_dev_del(session);
out_unlock:
up_write(&hidp_session_sem); return ret;
}
/* * Remove HIDP session * Called from the l2cap_conn core when either we explicitly unregistered * the l2cap_user object or if the underlying connection is shut down. * We signal the hidp-session thread to shut down, unregister the HID/input * devices and unlink the session from the global list. * This drops the reference to the session that is owned by the global * session-list. * Note: We _must_ not synchronosly wait for the session-thread to shut down. * This is, because the session-thread might be waiting for an HCI lock that is * held while we are called. Therefore, we only unregister the devices and * notify the session-thread to terminate. The thread itself owns a reference * to the session object so it can safely shut down.
*/ staticvoid hidp_session_remove(struct l2cap_conn *conn, struct l2cap_user *user)
{ struct hidp_session *session = container_of(user, struct hidp_session,
user);
down_write(&hidp_session_sem);
hidp_session_terminate(session);
cancel_work_sync(&session->dev_init); if (session->input ||
atomic_read(&session->state) > HIDP_SESSION_PREPARING)
hidp_session_dev_del(session);
list_del(&session->list);
up_write(&hidp_session_sem);
hidp_session_put(session);
}
/* * Session Worker * This performs the actual main-loop of the HIDP worker. We first check * whether the underlying connection is still alive, then parse all pending * messages and finally send all outstanding messages.
*/ staticvoid hidp_session_run(struct hidp_session *session)
{ struct sock *ctrl_sk = session->ctrl_sock->sk; struct sock *intr_sk = session->intr_sock->sk; struct sk_buff *skb;
DEFINE_WAIT_FUNC(wait, woken_wake_function);
add_wait_queue(&hidp_session_wq, &wait); for (;;) { /* * This thread can be woken up two ways: * - You call hidp_session_terminate() which sets the * session->terminate flag and wakes this thread up. * - Via modifying the socket state of ctrl/intr_sock. This * thread is woken up by ->sk_state_changed().
*/
if (atomic_read(&session->terminate)) break;
if (ctrl_sk->sk_state != BT_CONNECTED ||
intr_sk->sk_state != BT_CONNECTED) break;
/* parse incoming intr-skbs */ while ((skb = skb_dequeue(&intr_sk->sk_receive_queue))) {
skb_orphan(skb); if (!skb_linearize(skb))
hidp_recv_intr_frame(session, skb); else
kfree_skb(skb);
}
/* * wait_woken() performs the necessary memory barriers * for us; see the header comment for this primitive.
*/
wait_woken(&wait, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT);
}
remove_wait_queue(&hidp_session_wq, &wait);
/* * HIDP session thread * This thread runs the I/O for a single HIDP session. Startup is synchronous * which allows us to take references to ourself here instead of doing that in * the caller. * When we are ready to run we notify the caller and call hidp_session_run().
*/ staticint hidp_session_thread(void *arg)
{ struct hidp_session *session = arg;
DEFINE_WAIT_FUNC(ctrl_wait, hidp_session_wake_function);
DEFINE_WAIT_FUNC(intr_wait, hidp_session_wake_function);
add_wait_queue(sk_sleep(session->ctrl_sock->sk), &ctrl_wait);
add_wait_queue(sk_sleep(session->intr_sock->sk), &intr_wait); /* This memory barrier is paired with wq_has_sleeper(). See
* sock_poll_wait() for more information why this is needed. */
smp_mb__before_atomic();
/* notify synchronous startup that we're ready */
atomic_inc(&session->state);
wake_up(&session->state_queue);
/* * If we stopped ourself due to any internal signal, we should try to * unregister our own session here to avoid having it linger until the * parent l2cap_conn dies or user-space cleans it up. * This does not deadlock as we don't do any synchronous shutdown. * Instead, this call has the same semantics as if user-space tried to * delete the session.
*/
l2cap_unregister_user(session->conn, &session->user);
hidp_session_put(session);
if (ctrl->sk.sk_state != BT_CONNECTED ||
intr->sk.sk_state != BT_CONNECTED) return -EBADFD;
/* early session check, we check again during session registration */
session = hidp_session_find(&ctrl_chan->dst); if (session) {
hidp_session_put(session); return -EEXIST;
}
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.16Bemerkung:
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.