From 2934e194f3ffe754e18477113c870a7b98f88454 Mon Sep 17 00:00:00 2001 From: Luiz Augusto von Dentz Date: Mon, 3 Dec 2007 22:41:29 +0000 Subject: Handle new ipc messages properly and adapt the plugins. --- audio/unix.c | 581 ++++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 458 insertions(+), 123 deletions(-) (limited to 'audio/unix.c') diff --git a/audio/unix.c b/audio/unix.c index ea96bd4f..c321b99b 100644 --- a/audio/unix.c +++ b/audio/unix.c @@ -71,7 +71,7 @@ struct headset_data { struct unix_client { struct device *dev; - struct avdtp_service_capability *media_codec; + GSList *caps; service_type_t type; char *interface; union { @@ -83,18 +83,13 @@ struct unix_client { int data_fd; /* To be deleted once two phase configuration is fully implemented */ unsigned int req_id; unsigned int cb_id; - gboolean (*cancel_stream) (struct device *dev, unsigned int id); + gboolean (*cancel) (struct device *dev, unsigned int id); }; static GSList *clients = NULL; static int unix_sock = -1; -static void unix_ipc_sendmsg(struct unix_client *client, - const bt_audio_msg_header_t *msg); - -static void send_getcapabilities_rsp_error(struct unix_client *client, int err); - static void client_free(struct unix_client *client) { struct a2dp_data *a2dp; @@ -118,8 +113,10 @@ static void client_free(struct unix_client *client) if (client->sock >= 0) close(client->sock); - if (client->media_codec) - g_free(client->media_codec); + if (client->caps) { + g_slist_foreach(client->caps, (GFunc) g_free, NULL); + g_slist_free(client->caps); + } g_free(client->interface); g_free(client); @@ -152,6 +149,27 @@ static int unix_sendmsg_fd(int sock, int fd) return sendmsg(sock, &msgh, MSG_NOSIGNAL); } +static void unix_ipc_sendmsg(struct unix_client *client, + const bt_audio_msg_header_t *msg) +{ + info("Audio API: sending %s", bt_audio_strmsg(msg->msg_type)); + + if (send(client->sock, msg, BT_AUDIO_IPC_PACKET_SIZE, 0) < 0) + error("Error %s(%d)", strerror(errno), errno); +} + +static void unix_ipc_error(struct unix_client *client, int type, int err) +{ + char buf[BT_AUDIO_IPC_PACKET_SIZE]; + struct bt_getcapabilities_rsp *rsp = (void *) buf; + + memset(buf, 0, sizeof(buf)); + rsp->h.msg_type = type; + rsp->posix_errno = err; + + unix_ipc_sendmsg(client, &rsp->h); +} + static service_type_t select_service(struct device *dev, const char *interface) { if (!interface) { @@ -198,8 +216,8 @@ static void stream_state_changed(struct avdtp_stream *stream, break; } } - -static void headset_setup_complete(struct device *dev, void *user_data) +/* +static void headset_discovery_complete(struct device *dev, void *user_data) { struct unix_client *client = user_data; char buf[BT_AUDIO_IPC_PACKET_SIZE]; @@ -209,7 +227,7 @@ static void headset_setup_complete(struct device *dev, void *user_data) client->req_id = 0; if (!dev) { - send_getcapabilities_rsp_error(client, EIO); + unix_ipc_error(client, BT_GETCAPABILITIES_RSP, EIO); client->dev = NULL; return; } @@ -231,7 +249,7 @@ static void headset_setup_complete(struct device *dev, void *user_data) if (!headset_lock(dev, hs->lock)) { error("Unable to lock headset"); - send_getcapabilities_rsp_error(client, EIO); + unix_ipc_error(client, BT_GETCAPABILITIES_RSP, EIO); client->dev = NULL; return; } @@ -240,25 +258,131 @@ static void headset_setup_complete(struct device *dev, void *user_data) rsp->h.msg_type = BT_GETCAPABILITIES_RSP; rsp->transport = BT_CAPABILITIES_TRANSPORT_SCO; - rsp->access_mode = client->access_mode; - rsp->link_mtu = 48; rsp->sampling_rate = 8000; client->data_fd = headset_get_sco_fd(dev); unix_ipc_sendmsg(client, &rsp->h); } +*/ +static void headset_setup_complete(struct device *dev, void *user_data) +{ + struct unix_client *client = user_data; + char buf[BT_AUDIO_IPC_PACKET_SIZE]; + struct bt_setconfiguration_rsp *rsp = (void *) buf; + struct headset_data *hs = &client->d.hs; -static void a2dp_setup_complete(struct avdtp *session, struct a2dp_sep *sep, - struct avdtp_stream *stream, - void *user_data, struct avdtp_error *err) + client->req_id = 0; + + if (!dev) { + unix_ipc_error(client, BT_GETCAPABILITIES_RSP, EIO); + client->dev = NULL; + return; + } + + switch (client->access_mode) { + case BT_CAPABILITIES_ACCESS_MODE_READ: + hs->lock = HEADSET_LOCK_READ; + break; + case BT_CAPABILITIES_ACCESS_MODE_WRITE: + hs->lock = HEADSET_LOCK_WRITE; + break; + case BT_CAPABILITIES_ACCESS_MODE_READWRITE: + hs->lock = HEADSET_LOCK_READ | HEADSET_LOCK_WRITE; + break; + default: + hs->lock = 0; + break; + } + + if (!headset_lock(dev, hs->lock)) { + error("Unable to lock headset"); + unix_ipc_error(client, BT_GETCAPABILITIES_RSP, EIO); + client->dev = NULL; + return; + } + + memset(buf, 0, sizeof(buf)); + + rsp->h.msg_type = BT_SETCONFIGURATION_RSP; + rsp->transport = BT_CAPABILITIES_TRANSPORT_SCO; + rsp->access_mode = client->access_mode; + + client->data_fd = headset_get_sco_fd(dev); + + unix_ipc_sendmsg(client, &rsp->h); +} + +static void a2dp_discovery_complete(struct avdtp *session, GSList *seps, + struct avdtp_error *err, + void *user_data) { struct unix_client *client = user_data; char buf[BT_AUDIO_IPC_PACKET_SIZE]; struct bt_getcapabilities_rsp *rsp = (void *) buf; - struct avdtp_service_capability *cap; - struct avdtp_media_codec_capability *codec_cap; - struct sbc_codec_cap *sbc_cap; + struct a2dp_data *a2dp = &client->d.a2dp; + struct sbc_codec_cap *sbc_cap = NULL; + GSList *l; + + if (err) + goto failed; + + memset(buf, 0, sizeof(buf)); + client->req_id = 0; + + rsp->h.msg_type = BT_GETCAPABILITIES_RSP; + rsp->transport = BT_CAPABILITIES_TRANSPORT_A2DP; + + for (l = seps; l; l = g_slist_next(l)) { + struct avdtp_remote_sep *rsep = l->data; + struct avdtp_service_capability *cap; + struct avdtp_media_codec_capability *codec_cap; + + cap = avdtp_get_codec(rsep); + + if (cap->category != AVDTP_MEDIA_CODEC) + continue; + + codec_cap = (void *) cap->data; + + if (codec_cap->media_codec_type == A2DP_CODEC_SBC && !sbc_cap) + sbc_cap = (void *) codec_cap; + + } + + /* endianess prevent direct cast */ + if (sbc_cap) { + rsp->sbc_capabilities.channel_mode = sbc_cap->channel_mode; + rsp->sbc_capabilities.frequency = sbc_cap->frequency; + rsp->sbc_capabilities.allocation_method = sbc_cap->allocation_method; + rsp->sbc_capabilities.subbands = sbc_cap->subbands; + rsp->sbc_capabilities.block_length = sbc_cap->block_length; + rsp->sbc_capabilities.min_bitpool = sbc_cap->min_bitpool; + rsp->sbc_capabilities.max_bitpool = sbc_cap->max_bitpool; + } + + unix_ipc_sendmsg(client, &rsp->h); + + return; + +failed: + error("discovery failed"); + unix_ipc_error(client, BT_GETCAPABILITIES_RSP, EIO); + + avdtp_unref(a2dp->session); + + a2dp->session = NULL; + a2dp->stream = NULL; +} + +static void a2dp_config_complete(struct avdtp *session, struct a2dp_sep *sep, + struct avdtp_stream *stream, + struct avdtp_error *err, + void *user_data) +{ + struct unix_client *client = user_data; + char buf[BT_AUDIO_IPC_PACKET_SIZE]; + struct bt_setconfiguration_rsp *rsp = (void *) buf; struct a2dp_data *a2dp = &client->d.a2dp; uint16_t imtu, omtu; GSList *caps; @@ -282,40 +406,13 @@ static void a2dp_setup_complete(struct avdtp *session, struct a2dp_sep *sep, goto failed; } - for (codec_cap = NULL; caps; caps = g_slist_next(caps)) { - cap = caps->data; - if (cap->category == AVDTP_MEDIA_CODEC) { - codec_cap = (void *) cap->data; - break; - } - } - - if (codec_cap == NULL || - codec_cap->media_codec_type != A2DP_CODEC_SBC) { - error("Unable to find matching codec capability"); - goto failed; - } - - rsp->h.msg_type = BT_GETCAPABILITIES_RSP; + rsp->h.msg_type = BT_SETCONFIGURATION_RSP; rsp->transport = BT_CAPABILITIES_TRANSPORT_A2DP; client->access_mode = BT_CAPABILITIES_ACCESS_MODE_WRITE; rsp->access_mode = client->access_mode; /* FIXME: Use imtu when fd_opt is CFG_FD_OPT_READ */ rsp->link_mtu = omtu; - sbc_cap = (void *) codec_cap; - - /* assignations below are ok as soon as newipc.h and a2dp.h are kept */ - /* in sync. However it is not possible to cast a struct to another */ - /* dues to endianess issues */ - rsp->sbc_capabilities.channel_mode = sbc_cap->channel_mode; - rsp->sbc_capabilities.frequency = sbc_cap->frequency; - rsp->sbc_capabilities.allocation_method = sbc_cap->allocation_method; - rsp->sbc_capabilities.subbands = sbc_cap->subbands; - rsp->sbc_capabilities.block_length = sbc_cap->block_length; - rsp->sbc_capabilities.min_bitpool = sbc_cap->min_bitpool; - rsp->sbc_capabilities.max_bitpool = sbc_cap->max_bitpool; - unix_ipc_sendmsg(client, &rsp->h); client->cb_id = avdtp_stream_add_cb(session, stream, @@ -324,12 +421,86 @@ static void a2dp_setup_complete(struct avdtp *session, struct a2dp_sep *sep, return; failed: - error("stream setup failed"); + error("setup failed"); + + if (a2dp->sep) { + a2dp_sep_unlock(a2dp->sep, a2dp->session); + a2dp->sep = NULL; + } + unix_ipc_error(client, BT_SETCONFIGURATION_RSP, EIO); + + avdtp_unref(a2dp->session); + + a2dp->session = NULL; + a2dp->stream = NULL; +} + +static void a2dp_resume_complete(struct avdtp *session, + struct avdtp_error *err, void *user_data) +{ + struct unix_client *client = user_data; + char buf[BT_AUDIO_IPC_PACKET_SIZE]; + struct bt_streamstart_rsp *rsp = (void *) buf; + struct bt_datafd_ind *ind = (void *) buf; + struct a2dp_data *a2dp = &client->d.a2dp; + + memset(buf, 0, sizeof(buf)); + rsp->h.msg_type = BT_STREAMSTART_RSP; + rsp->posix_errno = 0; + unix_ipc_sendmsg(client, &rsp->h); + + memset(buf, 0, sizeof(buf)); + ind->h.msg_type = BT_STREAMFD_IND; + unix_ipc_sendmsg(client, &ind->h); + + if (unix_sendmsg_fd(client->sock, client->data_fd) < 0) { + error("unix_sendmsg_fd: %s(%d)", strerror(errno), errno); + goto failed; + } + + return; + +failed: + error("resume failed"); + + if (a2dp->sep) { + a2dp_sep_unlock(a2dp->sep, a2dp->session); + a2dp->sep = NULL; + } + unix_ipc_error(client, BT_STREAMSTART_REQ, EIO); + + avdtp_unref(a2dp->session); + + a2dp->session = NULL; + a2dp->stream = NULL; +} + +static void a2dp_suspend_complete(struct avdtp *session, + struct avdtp_error *err, void *user_data) +{ + struct unix_client *client = user_data; + char buf[BT_AUDIO_IPC_PACKET_SIZE]; + struct bt_streamstart_rsp *rsp = (void *) buf; + struct a2dp_data *a2dp = &client->d.a2dp; + + if (err) + goto failed; + + memset(buf, 0, sizeof(buf)); + rsp->h.msg_type = BT_STREAMSTOP_RSP; + rsp->posix_errno = 0; + unix_ipc_sendmsg(client, &rsp->h); + + return; + +failed: + error("suspend failed"); + if (a2dp->sep) { a2dp_sep_unlock(a2dp->sep, a2dp->session); a2dp->sep = NULL; } - send_getcapabilities_rsp_error(client, EIO); + unix_ipc_error(client, BT_STREAMSTOP_REQ, EIO); avdtp_unref(a2dp->session); @@ -337,10 +508,11 @@ failed: a2dp->stream = NULL; } -static void create_stream(struct device *dev, struct unix_client *client) +static void start_discovery(struct device *dev, struct unix_client *client) { struct a2dp_data *a2dp; unsigned int id; + int err = 0; client->type = select_service(dev, client->interface); @@ -356,18 +528,55 @@ static void create_stream(struct device *dev, struct unix_client *client) goto failed; } - /* FIXME: The provided media_codec breaks bitpool - selection. So disable it. This needs fixing */ - id = a2dp_source_request_stream(a2dp->session, - TRUE, a2dp_setup_complete, - client, - NULL/*client->media_codec*/); - client->cancel_stream = a2dp_source_cancel_stream; + err = avdtp_discover(a2dp->session, a2dp_discovery_complete, + client); + if (err) + goto failed; break; case TYPE_HEADSET: id = headset_request_stream(dev, headset_setup_complete, client); - client->cancel_stream = headset_cancel_stream; + client->cancel = headset_cancel_stream; + break; + + default: + error("No known services for device"); + goto failed; + } + + return; + +failed: + unix_ipc_error(client, BT_GETCAPABILITIES_RSP, err ? : EIO); +} + +static void start_config(struct device *dev, struct unix_client *client) +{ + struct a2dp_data *a2dp; + unsigned int id; + + client->type = select_service(dev, client->interface); + + switch (client->type) { + case TYPE_SINK: + a2dp = &client->d.a2dp; + + if (!a2dp->session) + a2dp->session = avdtp_get(&dev->src, &dev->dst); + + if (!a2dp->session) { + error("Unable to get a session"); + goto failed; + } + + id = a2dp_source_config(a2dp->session, a2dp_config_complete, + client->caps, client); + client->cancel = a2dp_source_cancel; + break; + + case TYPE_HEADSET: + id = headset_request_stream(dev, headset_setup_complete, client); + client->cancel = headset_cancel_stream; break; default: @@ -376,7 +585,7 @@ static void create_stream(struct device *dev, struct unix_client *client) } if (id == 0) { - error("request_stream failed"); + error("config failed"); goto failed; } @@ -386,37 +595,117 @@ static void create_stream(struct device *dev, struct unix_client *client) return; failed: - send_getcapabilities_rsp_error(client, EIO); + unix_ipc_error(client, BT_SETCONFIGURATION_RSP, EIO); } -static void create_cb(struct device *dev, void *user_data) +static void start_resume(struct device *dev, struct unix_client *client) { - struct unix_client *client = user_data; + struct a2dp_data *a2dp; + unsigned int id; - if (!dev) - send_getcapabilities_rsp_error(client, EIO); - else - create_stream(dev, client); + client->type = select_service(dev, client->interface); + + switch (client->type) { + case TYPE_SINK: + a2dp = &client->d.a2dp; + + if (!a2dp->session) + a2dp->session = avdtp_get(&dev->src, &dev->dst); + + if (!a2dp->session) { + error("Unable to get a session"); + goto failed; + } + + if (!a2dp->sep) { + error("Unable to get a sep"); + goto failed; + } + + id = a2dp_source_resume(a2dp->session, a2dp->sep, + a2dp_resume_complete, client); + client->cancel = a2dp_source_cancel; + break; + + case TYPE_HEADSET: + id = headset_request_stream(dev, headset_setup_complete, client); + client->cancel = headset_cancel_stream; + break; + + default: + error("No known services for device"); + goto failed; + } + + if (id == 0) { + error("resume failed"); + goto failed; + } + + return; + +failed: + unix_ipc_error(client, BT_STREAMSTART_RSP, EIO); } -static void unix_ipc_sendmsg(struct unix_client *client, - const bt_audio_msg_header_t *msg) +static void start_suspend(struct device *dev, struct unix_client *client) { - info("Audio API: sending %s", bt_audio_strmsg(msg->msg_type)); - if (send(client->sock, msg, BT_AUDIO_IPC_PACKET_SIZE, 0) < 0) - error("Error %s(%d)", strerror(errno), errno); + struct a2dp_data *a2dp; + unsigned int id; + + client->type = select_service(dev, client->interface); + + switch (client->type) { + case TYPE_SINK: + a2dp = &client->d.a2dp; + + if (!a2dp->session) + a2dp->session = avdtp_get(&dev->src, &dev->dst); + + if (!a2dp->session) { + error("Unable to get a session"); + goto failed; + } + + if (!a2dp->sep) { + error("Unable to get a sep"); + goto failed; + } + + id = a2dp_source_suspend(a2dp->session, a2dp->sep, + a2dp_suspend_complete, client); + client->cancel = a2dp_source_cancel; + break; + + case TYPE_HEADSET: + id = headset_request_stream(dev, headset_setup_complete, client); + client->cancel = headset_cancel_stream; + break; + + default: + error("No known services for device"); + goto failed; + } + + if (id == 0) { + error("suspend failed"); + goto failed; + } + + return; + +failed: + unix_ipc_error(client, BT_STREAMSTOP_RSP, EIO); } -static void send_getcapabilities_rsp_error(struct unix_client *client, int err) +static void create_cb(struct device *dev, void *user_data) { - char buf[BT_AUDIO_IPC_PACKET_SIZE]; - struct bt_getcapabilities_rsp *rsp = (void *) buf; - - memset(buf, 0, sizeof(buf)); - rsp->h.msg_type = BT_GETCAPABILITIES_RSP; - rsp->posix_errno = err; + struct unix_client *client = user_data; - unix_ipc_sendmsg(client, &rsp->h); + if (!dev) + unix_ipc_error(client, BT_GETCAPABILITIES_RSP, EIO); + else + start_discovery(dev, client); } static void handle_getcapabilities_req(struct unix_client *client, @@ -427,13 +716,6 @@ static void handle_getcapabilities_req(struct unix_client *client, str2ba(req->device, &bdaddr); - if (!req->access_mode) { - send_getcapabilities_rsp_error(client, EINVAL); - return; - } - - client->access_mode = req->access_mode; - if (client->interface) { g_free(client->interface); client->interface = NULL; @@ -444,8 +726,6 @@ static void handle_getcapabilities_req(struct unix_client *client, else if (req->transport == BT_CAPABILITIES_TRANSPORT_A2DP) client->interface = g_strdup(AUDIO_SINK_INTERFACE); - client->media_codec = 0; - if (!manager_find_device(&bdaddr, NULL, FALSE)) { if (!bacmp(&bdaddr, BDADDR_ANY)) goto failed; @@ -461,63 +741,118 @@ static void handle_getcapabilities_req(struct unix_client *client, if (!dev) goto failed; - create_stream(dev, client); + start_discovery(dev, client); return; failed: - send_getcapabilities_rsp_error(client, EIO); + unix_ipc_error(client, BT_GETCAPABILITIES_RSP, EIO); } static void handle_setconfiguration_req(struct unix_client *client, struct bt_setconfiguration_req *req) { - /* FIXME: for now we just blindly assume that we receive is the - only valid configuration sent.*/ - char buf[BT_AUDIO_IPC_PACKET_SIZE]; - struct bt_setconfiguration_rsp *rsp = (void *) buf; + struct avdtp_service_capability *media_transport, *media_codec; + struct sbc_codec_cap sbc_cap; + struct device *dev; + bdaddr_t bdaddr; + int err = 0; - memset(buf, 0, sizeof(buf)); - rsp->h.msg_type = BT_SETCONFIGURATION_RSP; - rsp->posix_errno = 0; + if (!req->access_mode) { + err = EINVAL; + goto failed; + } - unix_ipc_sendmsg(client, &rsp->h); + str2ba(req->device, &bdaddr); + + if (client->interface) { + g_free(client->interface); + client->interface = NULL; + } + + if (req->transport == BT_CAPABILITIES_TRANSPORT_SCO) + client->interface = g_strdup(AUDIO_HEADSET_INTERFACE); + else if (req->transport == BT_CAPABILITIES_TRANSPORT_A2DP) + client->interface = g_strdup(AUDIO_SINK_INTERFACE); + + if (!manager_find_device(&bdaddr, NULL, FALSE)) { + if (!bacmp(&bdaddr, BDADDR_ANY)) + goto failed; + if (!manager_create_device(&bdaddr, create_cb, client)) + goto failed; + return; + } + + dev = manager_find_device(&bdaddr, client->interface, TRUE); + if (!dev) + dev = manager_find_device(&bdaddr, client->interface, FALSE); + + if (!dev) + goto failed; + + client->access_mode = req->access_mode; + + if (client->caps) { + g_slist_foreach(client->caps, (GFunc) g_free, NULL); + g_slist_free(client->caps); + } + + media_transport = avdtp_service_cap_new(AVDTP_MEDIA_TRANSPORT, + NULL, 0); + + client->caps = g_slist_append(client->caps, media_transport); + + memset(&sbc_cap, 0, sizeof(sbc_cap)); + + sbc_cap.cap.media_type = AVDTP_MEDIA_TYPE_AUDIO; + sbc_cap.cap.media_codec_type = A2DP_CODEC_SBC; + sbc_cap.channel_mode = req->sbc_capabilities.channel_mode; + sbc_cap.frequency = req->sbc_capabilities.frequency; + sbc_cap.allocation_method = req->sbc_capabilities.allocation_method; + sbc_cap.subbands = req->sbc_capabilities.subbands; + sbc_cap.block_length = req->sbc_capabilities.block_length ; + sbc_cap.min_bitpool = req->sbc_capabilities.min_bitpool; + sbc_cap.max_bitpool = req->sbc_capabilities.max_bitpool; + + media_codec = avdtp_service_cap_new(AVDTP_MEDIA_CODEC, &sbc_cap, + sizeof(sbc_cap)); + + client->caps = g_slist_append(client->caps, media_codec); + + start_config(dev, client); + + return; + +failed: + unix_ipc_error(client, BT_SETCONFIGURATION_RSP, err ? : EIO); } static void handle_streamstart_req(struct unix_client *client, struct bt_streamstart_req *req) { - /* FIXME : to be really implemented */ - char buf[BT_AUDIO_IPC_PACKET_SIZE]; - struct bt_streamstart_rsp *rsp = (void *) buf; - struct bt_datafd_ind *ind = (void *) buf; - - memset(buf, 0, sizeof(buf)); - rsp->h.msg_type = BT_STREAMSTART_RSP; - rsp->posix_errno = 0; - unix_ipc_sendmsg(client, &rsp->h); + if (!client->dev) + goto failed; - memset(buf, 0, sizeof(buf)); - ind->h.msg_type = BT_STREAMFD_IND; - unix_ipc_sendmsg(client, &ind->h); + start_resume(client->dev, client); - if (unix_sendmsg_fd(client->sock, client->data_fd) < 0) - error("unix_sendmsg_fd: %s(%d)", strerror(errno), errno); + return; +failed: + unix_ipc_error(client, BT_STREAMSTART_REQ, EIO); } static void handle_streamstop_req(struct unix_client *client, struct bt_streamstop_req *req) { - /* FIXME : to be implemented */ - char buf[BT_AUDIO_IPC_PACKET_SIZE]; - struct bt_streamstop_rsp *rsp = (void *) buf; + if (!client->dev) + goto failed; - memset(buf, 0, sizeof(buf)); - rsp->h.msg_type = BT_STREAMSTOP_RSP; - rsp->posix_errno = 0; + start_suspend(client->dev, client); - unix_ipc_sendmsg(client, &rsp->h); + return; + +failed: + unix_ipc_error(client, BT_STREAMSTOP_REQ, EIO); } static void handle_control_req(struct unix_client *client, @@ -565,8 +900,8 @@ static gboolean client_cb(GIOChannel *chan, GIOCondition cond, gpointer data) break; } - if (client->cancel_stream && client->req_id > 0) - client->cancel_stream(client->dev, client->req_id); + if (client->cancel && client->req_id > 0) + client->cancel(client->dev, client->req_id); goto failed; } -- cgit