Merge firewire branches to be released post v2.6.35

Conflicts:
	drivers/firewire/core-card.c
	drivers/firewire/core-cdev.c

and forgotten #include <linux/time.h> in drivers/firewire/ohci.c

Signed-off-by: Stefan Richter <stefanr@s5r6.in-berlin.de>
This commit is contained in:
Stefan Richter
2010-08-02 09:33:25 +02:00
31 changed files with 4308 additions and 576 deletions

View File

@@ -18,6 +18,7 @@
* Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include <linux/bug.h>
#include <linux/compat.h>
#include <linux/delay.h>
#include <linux/device.h>
@@ -33,7 +34,7 @@
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/poll.h>
#include <linux/sched.h>
#include <linux/sched.h> /* required for linux/wait.h */
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/string.h>
@@ -47,6 +48,13 @@
#include "core.h"
/*
* ABI version history is documented in linux/firewire-cdev.h.
*/
#define FW_CDEV_KERNEL_VERSION 4
#define FW_CDEV_VERSION_EVENT_REQUEST2 4
#define FW_CDEV_VERSION_ALLOCATE_REGION_END 4
struct client {
u32 version;
struct fw_device *device;
@@ -63,6 +71,9 @@ struct client {
struct fw_iso_buffer buffer;
unsigned long vm_start;
struct list_head phy_receiver_link;
u64 phy_receiver_closure;
struct list_head link;
struct kref kref;
};
@@ -107,6 +118,7 @@ struct outbound_transaction_resource {
struct inbound_transaction_resource {
struct client_resource resource;
struct fw_card *card;
struct fw_request *request;
void *data;
size_t length;
@@ -171,7 +183,10 @@ struct outbound_transaction_event {
struct inbound_transaction_event {
struct event event;
struct fw_cdev_event_request request;
union {
struct fw_cdev_event_request request;
struct fw_cdev_event_request2 request2;
} req;
};
struct iso_interrupt_event {
@@ -179,11 +194,28 @@ struct iso_interrupt_event {
struct fw_cdev_event_iso_interrupt interrupt;
};
struct iso_interrupt_mc_event {
struct event event;
struct fw_cdev_event_iso_interrupt_mc interrupt;
};
struct iso_resource_event {
struct event event;
struct fw_cdev_event_iso_resource iso_resource;
};
struct outbound_phy_packet_event {
struct event event;
struct client *client;
struct fw_packet p;
struct fw_cdev_event_phy_packet phy_packet;
};
struct inbound_phy_packet_event {
struct event event;
struct fw_cdev_event_phy_packet phy_packet;
};
static inline void __user *u64_to_uptr(__u64 value)
{
return (void __user *)(unsigned long)value;
@@ -219,6 +251,7 @@ static int fw_device_op_open(struct inode *inode, struct file *file)
idr_init(&client->resource_idr);
INIT_LIST_HEAD(&client->event_list);
init_waitqueue_head(&client->wait);
INIT_LIST_HEAD(&client->phy_receiver_link);
kref_init(&client->kref);
file->private_data = client;
@@ -309,7 +342,7 @@ static void fill_bus_reset_event(struct fw_cdev_event_bus_reset *event,
event->generation = client->device->generation;
event->node_id = client->device->node_id;
event->local_node_id = card->local_node->node_id;
event->bm_node_id = 0; /* FIXME: We don't track the BM. */
event->bm_node_id = card->bm_node_id;
event->irm_node_id = card->irm_node->node_id;
event->root_node_id = card->root_node->node_id;
@@ -340,7 +373,7 @@ static void queue_bus_reset_event(struct client *client)
e = kzalloc(sizeof(*e), GFP_KERNEL);
if (e == NULL) {
fw_notify("Out of memory when allocating bus reset event\n");
fw_notify("Out of memory when allocating event\n");
return;
}
@@ -386,6 +419,9 @@ union ioctl_arg {
struct fw_cdev_allocate_iso_resource allocate_iso_resource;
struct fw_cdev_send_stream_packet send_stream_packet;
struct fw_cdev_get_cycle_timer2 get_cycle_timer2;
struct fw_cdev_send_phy_packet send_phy_packet;
struct fw_cdev_receive_phy_packets receive_phy_packets;
struct fw_cdev_set_iso_channels set_iso_channels;
};
static int ioctl_get_info(struct client *client, union ioctl_arg *arg)
@@ -395,7 +431,7 @@ static int ioctl_get_info(struct client *client, union ioctl_arg *arg)
unsigned long ret = 0;
client->version = a->version;
a->version = FW_CDEV_VERSION;
a->version = FW_CDEV_KERNEL_VERSION;
a->card = client->device->card->index;
down_read(&fw_device_rwsem);
@@ -554,6 +590,10 @@ static int init_request(struct client *client,
(request->length > 4096 || request->length > 512 << speed))
return -EIO;
if (request->tcode == TCODE_WRITE_QUADLET_REQUEST &&
request->length < 4)
return -EINVAL;
e = kmalloc(sizeof(*e) + request->length, GFP_KERNEL);
if (e == NULL)
return -ENOMEM;
@@ -626,28 +666,34 @@ static void release_request(struct client *client,
if (is_fcp_request(r->request))
kfree(r->data);
else
fw_send_response(client->device->card, r->request,
RCODE_CONFLICT_ERROR);
fw_send_response(r->card, r->request, RCODE_CONFLICT_ERROR);
fw_card_put(r->card);
kfree(r);
}
static void handle_request(struct fw_card *card, struct fw_request *request,
int tcode, int destination, int source,
int generation, int speed,
unsigned long long offset,
int generation, unsigned long long offset,
void *payload, size_t length, void *callback_data)
{
struct address_handler_resource *handler = callback_data;
struct inbound_transaction_resource *r;
struct inbound_transaction_event *e;
size_t event_size0;
void *fcp_frame = NULL;
int ret;
/* card may be different from handler->client->device->card */
fw_card_get(card);
r = kmalloc(sizeof(*r), GFP_ATOMIC);
e = kmalloc(sizeof(*e), GFP_ATOMIC);
if (r == NULL || e == NULL)
if (r == NULL || e == NULL) {
fw_notify("Out of memory when allocating event\n");
goto failed;
}
r->card = card;
r->request = request;
r->data = payload;
r->length = length;
@@ -669,15 +715,37 @@ static void handle_request(struct fw_card *card, struct fw_request *request,
if (ret < 0)
goto failed;
e->request.type = FW_CDEV_EVENT_REQUEST;
e->request.tcode = tcode;
e->request.offset = offset;
e->request.length = length;
e->request.handle = r->resource.handle;
e->request.closure = handler->closure;
if (handler->client->version < FW_CDEV_VERSION_EVENT_REQUEST2) {
struct fw_cdev_event_request *req = &e->req.request;
if (tcode & 0x10)
tcode = TCODE_LOCK_REQUEST;
req->type = FW_CDEV_EVENT_REQUEST;
req->tcode = tcode;
req->offset = offset;
req->length = length;
req->handle = r->resource.handle;
req->closure = handler->closure;
event_size0 = sizeof(*req);
} else {
struct fw_cdev_event_request2 *req = &e->req.request2;
req->type = FW_CDEV_EVENT_REQUEST2;
req->tcode = tcode;
req->offset = offset;
req->source_node_id = source;
req->destination_node_id = destination;
req->card = card->index;
req->generation = generation;
req->length = length;
req->handle = r->resource.handle;
req->closure = handler->closure;
event_size0 = sizeof(*req);
}
queue_event(handler->client, &e->event,
&e->request, sizeof(e->request), r->data, length);
&e->req, event_size0, r->data, length);
return;
failed:
@@ -687,6 +755,8 @@ static void handle_request(struct fw_card *card, struct fw_request *request,
if (!is_fcp_request(request))
fw_send_response(card, request, RCODE_CONFLICT_ERROR);
fw_card_put(card);
}
static void release_address_handler(struct client *client,
@@ -711,7 +781,11 @@ static int ioctl_allocate(struct client *client, union ioctl_arg *arg)
return -ENOMEM;
region.start = a->offset;
region.end = a->offset + a->length;
if (client->version < FW_CDEV_VERSION_ALLOCATE_REGION_END)
region.end = a->offset + a->length;
else
region.end = a->region_end;
r->handler.length = a->length;
r->handler.address_callback = handle_request;
r->handler.callback_data = r;
@@ -723,6 +797,7 @@ static int ioctl_allocate(struct client *client, union ioctl_arg *arg)
kfree(r);
return ret;
}
a->offset = r->handler.offset;
r->resource.release = release_address_handler;
ret = add_client_resource(client, &r->resource, GFP_KERNEL);
@@ -757,15 +832,19 @@ static int ioctl_send_response(struct client *client, union ioctl_arg *arg)
if (is_fcp_request(r->request))
goto out;
if (a->length < r->length)
r->length = a->length;
if (copy_from_user(r->data, u64_to_uptr(a->data), r->length)) {
if (a->length != fw_get_response_length(r->request)) {
ret = -EINVAL;
kfree(r->request);
goto out;
}
if (copy_from_user(r->data, u64_to_uptr(a->data), a->length)) {
ret = -EFAULT;
kfree(r->request);
goto out;
}
fw_send_response(client->device->card, r->request, a->rcode);
fw_send_response(r->card, r->request, a->rcode);
out:
fw_card_put(r->card);
kfree(r);
return ret;
@@ -773,8 +852,9 @@ static int ioctl_send_response(struct client *client, union ioctl_arg *arg)
static int ioctl_initiate_bus_reset(struct client *client, union ioctl_arg *arg)
{
return fw_core_initiate_bus_reset(client->device->card,
fw_schedule_bus_reset(client->device->card, true,
arg->initiate_bus_reset.type == FW_CDEV_SHORT_RESET);
return 0;
}
static void release_descriptor(struct client *client,
@@ -845,10 +925,11 @@ static void iso_callback(struct fw_iso_context *context, u32 cycle,
struct client *client = data;
struct iso_interrupt_event *e;
e = kzalloc(sizeof(*e) + header_length, GFP_ATOMIC);
if (e == NULL)
e = kmalloc(sizeof(*e) + header_length, GFP_ATOMIC);
if (e == NULL) {
fw_notify("Out of memory when allocating event\n");
return;
}
e->interrupt.type = FW_CDEV_EVENT_ISO_INTERRUPT;
e->interrupt.closure = client->iso_closure;
e->interrupt.cycle = cycle;
@@ -858,27 +939,54 @@ static void iso_callback(struct fw_iso_context *context, u32 cycle,
sizeof(e->interrupt) + header_length, NULL, 0);
}
static void iso_mc_callback(struct fw_iso_context *context,
dma_addr_t completed, void *data)
{
struct client *client = data;
struct iso_interrupt_mc_event *e;
e = kmalloc(sizeof(*e), GFP_ATOMIC);
if (e == NULL) {
fw_notify("Out of memory when allocating event\n");
return;
}
e->interrupt.type = FW_CDEV_EVENT_ISO_INTERRUPT_MULTICHANNEL;
e->interrupt.closure = client->iso_closure;
e->interrupt.completed = fw_iso_buffer_lookup(&client->buffer,
completed);
queue_event(client, &e->event, &e->interrupt,
sizeof(e->interrupt), NULL, 0);
}
static int ioctl_create_iso_context(struct client *client, union ioctl_arg *arg)
{
struct fw_cdev_create_iso_context *a = &arg->create_iso_context;
struct fw_iso_context *context;
fw_iso_callback_t cb;
/* We only support one context at this time. */
if (client->iso_context != NULL)
return -EBUSY;
if (a->channel > 63)
return -EINVAL;
BUILD_BUG_ON(FW_CDEV_ISO_CONTEXT_TRANSMIT != FW_ISO_CONTEXT_TRANSMIT ||
FW_CDEV_ISO_CONTEXT_RECEIVE != FW_ISO_CONTEXT_RECEIVE ||
FW_CDEV_ISO_CONTEXT_RECEIVE_MULTICHANNEL !=
FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL);
switch (a->type) {
case FW_ISO_CONTEXT_RECEIVE:
if (a->header_size < 4 || (a->header_size & 3))
case FW_ISO_CONTEXT_TRANSMIT:
if (a->speed > SCODE_3200 || a->channel > 63)
return -EINVAL;
cb = iso_callback;
break;
case FW_ISO_CONTEXT_TRANSMIT:
if (a->speed > SCODE_3200)
case FW_ISO_CONTEXT_RECEIVE:
if (a->header_size < 4 || (a->header_size & 3) ||
a->channel > 63)
return -EINVAL;
cb = iso_callback;
break;
case FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL:
cb = (fw_iso_callback_t)iso_mc_callback;
break;
default:
@@ -886,20 +994,37 @@ static int ioctl_create_iso_context(struct client *client, union ioctl_arg *arg)
}
context = fw_iso_context_create(client->device->card, a->type,
a->channel, a->speed, a->header_size,
iso_callback, client);
a->channel, a->speed, a->header_size, cb, client);
if (IS_ERR(context))
return PTR_ERR(context);
/* We only support one context at this time. */
spin_lock_irq(&client->lock);
if (client->iso_context != NULL) {
spin_unlock_irq(&client->lock);
fw_iso_context_destroy(context);
return -EBUSY;
}
client->iso_closure = a->closure;
client->iso_context = context;
spin_unlock_irq(&client->lock);
/* We only support one context at this time. */
a->handle = 0;
return 0;
}
static int ioctl_set_iso_channels(struct client *client, union ioctl_arg *arg)
{
struct fw_cdev_set_iso_channels *a = &arg->set_iso_channels;
struct fw_iso_context *ctx = client->iso_context;
if (ctx == NULL || a->handle != 0)
return -EINVAL;
return fw_iso_context_set_channels(ctx, &a->channels);
}
/* Macros for decoding the iso packet control header. */
#define GET_PAYLOAD_LENGTH(v) ((v) & 0xffff)
#define GET_INTERRUPT(v) (((v) >> 16) & 0x01)
@@ -913,7 +1038,7 @@ static int ioctl_queue_iso(struct client *client, union ioctl_arg *arg)
struct fw_cdev_queue_iso *a = &arg->queue_iso;
struct fw_cdev_iso_packet __user *p, *end, *next;
struct fw_iso_context *ctx = client->iso_context;
unsigned long payload, buffer_end, header_length;
unsigned long payload, buffer_end, transmit_header_bytes = 0;
u32 control;
int count;
struct {
@@ -933,7 +1058,6 @@ static int ioctl_queue_iso(struct client *client, union ioctl_arg *arg)
* use the indirect payload, the iso buffer need not be mapped
* and the a->data pointer is ignored.
*/
payload = (unsigned long)a->data - client->vm_start;
buffer_end = client->buffer.page_count << PAGE_SHIFT;
if (a->data == 0 || client->buffer.pages == NULL ||
@@ -942,8 +1066,10 @@ static int ioctl_queue_iso(struct client *client, union ioctl_arg *arg)
buffer_end = 0;
}
p = (struct fw_cdev_iso_packet __user *)u64_to_uptr(a->packets);
if (ctx->type == FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL && payload & 3)
return -EINVAL;
p = (struct fw_cdev_iso_packet __user *)u64_to_uptr(a->packets);
if (!access_ok(VERIFY_READ, p, a->size))
return -EFAULT;
@@ -959,31 +1085,32 @@ static int ioctl_queue_iso(struct client *client, union ioctl_arg *arg)
u.packet.sy = GET_SY(control);
u.packet.header_length = GET_HEADER_LENGTH(control);
if (ctx->type == FW_ISO_CONTEXT_TRANSMIT) {
if (u.packet.header_length % 4 != 0)
switch (ctx->type) {
case FW_ISO_CONTEXT_TRANSMIT:
if (u.packet.header_length & 3)
return -EINVAL;
header_length = u.packet.header_length;
} else {
/*
* We require that header_length is a multiple of
* the fixed header size, ctx->header_size.
*/
if (ctx->header_size == 0) {
if (u.packet.header_length > 0)
return -EINVAL;
} else if (u.packet.header_length == 0 ||
u.packet.header_length % ctx->header_size != 0) {
transmit_header_bytes = u.packet.header_length;
break;
case FW_ISO_CONTEXT_RECEIVE:
if (u.packet.header_length == 0 ||
u.packet.header_length % ctx->header_size != 0)
return -EINVAL;
}
header_length = 0;
break;
case FW_ISO_CONTEXT_RECEIVE_MULTICHANNEL:
if (u.packet.payload_length == 0 ||
u.packet.payload_length & 3)
return -EINVAL;
break;
}
next = (struct fw_cdev_iso_packet __user *)
&p->header[header_length / 4];
&p->header[transmit_header_bytes / 4];
if (next > end)
return -EINVAL;
if (__copy_from_user
(u.packet.header, p->header, header_length))
(u.packet.header, p->header, transmit_header_bytes))
return -EFAULT;
if (u.packet.skip && ctx->type == FW_ISO_CONTEXT_TRANSMIT &&
u.packet.header_length + u.packet.payload_length > 0)
@@ -1011,6 +1138,13 @@ static int ioctl_start_iso(struct client *client, union ioctl_arg *arg)
{
struct fw_cdev_start_iso *a = &arg->start_iso;
BUILD_BUG_ON(
FW_CDEV_ISO_CONTEXT_MATCH_TAG0 != FW_ISO_CONTEXT_MATCH_TAG0 ||
FW_CDEV_ISO_CONTEXT_MATCH_TAG1 != FW_ISO_CONTEXT_MATCH_TAG1 ||
FW_CDEV_ISO_CONTEXT_MATCH_TAG2 != FW_ISO_CONTEXT_MATCH_TAG2 ||
FW_CDEV_ISO_CONTEXT_MATCH_TAG3 != FW_ISO_CONTEXT_MATCH_TAG3 ||
FW_CDEV_ISO_CONTEXT_MATCH_ALL_TAGS != FW_ISO_CONTEXT_MATCH_ALL_TAGS);
if (client->iso_context == NULL || a->handle != 0)
return -EINVAL;
@@ -1042,7 +1176,7 @@ static int ioctl_get_cycle_timer2(struct client *client, union ioctl_arg *arg)
local_irq_disable();
cycle_time = card->driver->get_cycle_time(card);
cycle_time = card->driver->read_csr(card, CSR_CYCLE_TIME);
switch (a->clk_id) {
case CLOCK_REALTIME: getnstimeofday(&ts); break;
@@ -1323,28 +1457,135 @@ static int ioctl_send_stream_packet(struct client *client, union ioctl_arg *arg)
return init_request(client, &request, dest, a->speed);
}
static void outbound_phy_packet_callback(struct fw_packet *packet,
struct fw_card *card, int status)
{
struct outbound_phy_packet_event *e =
container_of(packet, struct outbound_phy_packet_event, p);
switch (status) {
/* expected: */
case ACK_COMPLETE: e->phy_packet.rcode = RCODE_COMPLETE; break;
/* should never happen with PHY packets: */
case ACK_PENDING: e->phy_packet.rcode = RCODE_COMPLETE; break;
case ACK_BUSY_X:
case ACK_BUSY_A:
case ACK_BUSY_B: e->phy_packet.rcode = RCODE_BUSY; break;
case ACK_DATA_ERROR: e->phy_packet.rcode = RCODE_DATA_ERROR; break;
case ACK_TYPE_ERROR: e->phy_packet.rcode = RCODE_TYPE_ERROR; break;
/* stale generation; cancelled; on certain controllers: no ack */
default: e->phy_packet.rcode = status; break;
}
e->phy_packet.data[0] = packet->timestamp;
queue_event(e->client, &e->event, &e->phy_packet,
sizeof(e->phy_packet) + e->phy_packet.length, NULL, 0);
client_put(e->client);
}
static int ioctl_send_phy_packet(struct client *client, union ioctl_arg *arg)
{
struct fw_cdev_send_phy_packet *a = &arg->send_phy_packet;
struct fw_card *card = client->device->card;
struct outbound_phy_packet_event *e;
/* Access policy: Allow this ioctl only on local nodes' device files. */
if (!client->device->is_local)
return -ENOSYS;
e = kzalloc(sizeof(*e) + 4, GFP_KERNEL);
if (e == NULL)
return -ENOMEM;
client_get(client);
e->client = client;
e->p.speed = SCODE_100;
e->p.generation = a->generation;
e->p.header[0] = a->data[0];
e->p.header[1] = a->data[1];
e->p.header_length = 8;
e->p.callback = outbound_phy_packet_callback;
e->phy_packet.closure = a->closure;
e->phy_packet.type = FW_CDEV_EVENT_PHY_PACKET_SENT;
if (is_ping_packet(a->data))
e->phy_packet.length = 4;
card->driver->send_request(card, &e->p);
return 0;
}
static int ioctl_receive_phy_packets(struct client *client, union ioctl_arg *arg)
{
struct fw_cdev_receive_phy_packets *a = &arg->receive_phy_packets;
struct fw_card *card = client->device->card;
/* Access policy: Allow this ioctl only on local nodes' device files. */
if (!client->device->is_local)
return -ENOSYS;
spin_lock_irq(&card->lock);
list_move_tail(&client->phy_receiver_link, &card->phy_receiver_list);
client->phy_receiver_closure = a->closure;
spin_unlock_irq(&card->lock);
return 0;
}
void fw_cdev_handle_phy_packet(struct fw_card *card, struct fw_packet *p)
{
struct client *client;
struct inbound_phy_packet_event *e;
unsigned long flags;
spin_lock_irqsave(&card->lock, flags);
list_for_each_entry(client, &card->phy_receiver_list, phy_receiver_link) {
e = kmalloc(sizeof(*e) + 8, GFP_ATOMIC);
if (e == NULL) {
fw_notify("Out of memory when allocating event\n");
break;
}
e->phy_packet.closure = client->phy_receiver_closure;
e->phy_packet.type = FW_CDEV_EVENT_PHY_PACKET_RECEIVED;
e->phy_packet.rcode = RCODE_COMPLETE;
e->phy_packet.length = 8;
e->phy_packet.data[0] = p->header[1];
e->phy_packet.data[1] = p->header[2];
queue_event(client, &e->event,
&e->phy_packet, sizeof(e->phy_packet) + 8, NULL, 0);
}
spin_unlock_irqrestore(&card->lock, flags);
}
static int (* const ioctl_handlers[])(struct client *, union ioctl_arg *) = {
ioctl_get_info,
ioctl_send_request,
ioctl_allocate,
ioctl_deallocate,
ioctl_send_response,
ioctl_initiate_bus_reset,
ioctl_add_descriptor,
ioctl_remove_descriptor,
ioctl_create_iso_context,
ioctl_queue_iso,
ioctl_start_iso,
ioctl_stop_iso,
ioctl_get_cycle_timer,
ioctl_allocate_iso_resource,
ioctl_deallocate_iso_resource,
ioctl_allocate_iso_resource_once,
ioctl_deallocate_iso_resource_once,
ioctl_get_speed,
ioctl_send_broadcast_request,
ioctl_send_stream_packet,
ioctl_get_cycle_timer2,
[0x00] = ioctl_get_info,
[0x01] = ioctl_send_request,
[0x02] = ioctl_allocate,
[0x03] = ioctl_deallocate,
[0x04] = ioctl_send_response,
[0x05] = ioctl_initiate_bus_reset,
[0x06] = ioctl_add_descriptor,
[0x07] = ioctl_remove_descriptor,
[0x08] = ioctl_create_iso_context,
[0x09] = ioctl_queue_iso,
[0x0a] = ioctl_start_iso,
[0x0b] = ioctl_stop_iso,
[0x0c] = ioctl_get_cycle_timer,
[0x0d] = ioctl_allocate_iso_resource,
[0x0e] = ioctl_deallocate_iso_resource,
[0x0f] = ioctl_allocate_iso_resource_once,
[0x10] = ioctl_deallocate_iso_resource_once,
[0x11] = ioctl_get_speed,
[0x12] = ioctl_send_broadcast_request,
[0x13] = ioctl_send_stream_packet,
[0x14] = ioctl_get_cycle_timer2,
[0x15] = ioctl_send_phy_packet,
[0x16] = ioctl_receive_phy_packets,
[0x17] = ioctl_set_iso_channels,
};
static int dispatch_ioctl(struct client *client,
@@ -1452,6 +1693,10 @@ static int fw_device_op_release(struct inode *inode, struct file *file)
struct client *client = file->private_data;
struct event *event, *next_event;
spin_lock_irq(&client->device->card->lock);
list_del(&client->phy_receiver_link);
spin_unlock_irq(&client->device->card->lock);
mutex_lock(&client->device->client_list_mutex);
list_del(&client->link);
mutex_unlock(&client->device->client_list_mutex);