summaryrefslogtreecommitdiff
path: root/src/libembim/embim-device.c
blob: 740b84eecfec307099266ebeda8dc59d956965bf (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
 * libembim -- Library to control MBIM devices
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 * Copyright (C) 2015 Aleksander Morgado <aleksander@aleksander.es>
 */

#include <config.h>

#include <stdio.h>
#include <stdint.h>
#include <errno.h>
#include <assert.h>
#include <malloc.h>
#include <string.h>
#include <pthread.h>
#include <fcntl.h>
#include <unistd.h>

#include <sys/ioctl.h>
#define IOCTL_WDM_MAX_COMMAND _IOR('H', 0xA0, uint16_t)
#define FALLBACK_MAX_CONTROL_TRANSFER 4096

#include "embim-device.h"

/*****************************************************************************/

struct indication_s;
struct embim_device_s {
    /* The MBIM device path */
    char *path;
    /* Open device FD */
    int fd;
    /* Transaction ID */
    uint32_t transaction_id;
    /* Registered indication callbacks */
    struct indication_s *indications;
    size_t               indications_n;
    size_t               indications_size;
    pthread_mutex_t      indications_mutex;
};

static void cleanup_indications (embim_device_t *self);
static void notify_indication   (embim_device_t *self,
                                 const uint8_t  *indication);

/*****************************************************************************/
/* Basic device creation/destruction */

embim_device_t *
embim_device_new (const char *path)
{
    embim_device_t *self;

    assert (path);

    self = malloc (sizeof (struct embim_device_s));
    if (self) {
        self->path = strdup (path);
        if (!self->path) {
            free (self);
            self = NULL;
        }

        self->fd = -1;

        pthread_mutex_init (&self->indications_mutex, NULL);
        self->indications_n    = 0;
        self->indications_size = 0;
        self->indications      = NULL;
    }

    return self;
}

void
embim_device_free (embim_device_t *self)
{
    if (!self)
        return;

    embim_device_close (self);

    cleanup_indications (self);
    pthread_mutex_destroy (&self->indications_mutex);

    free (self->path);
    free (self);
}

/*****************************************************************************/
/* MBIM write */

embim_status_e
embim_device_write (embim_device_t *self,
                    const uint8_t  *message,
                    size_t          message_size)
{


}

/*****************************************************************************/
/* MBIM open */

embim_status_e
embim_device_open (embim_device_t *self)
{
    /* If already open, we're done */
    if (!(self->fd < 0))
        return EMBIM_STATUS_SUCCESS;

    self->fd = open (self->path, O_RDWR | FD_CLOEXEC);
    if (self->fd < 0) {
        /* Not having enough privileges is a very common error here. */
        if (errno == EACCESS)
            return EMBIM_STATUS_ERROR_UNAUTHORIZED;
        return EMBIM_STATUS_ERROR_GENERIC;
    }

    /* Query message size */
    if (ioctl (self->fd, IOCTL_WDM_MAX_COMMAND, &self->max_control_transfer) < 0)
        /* Fallback to a default value */
        self->max_control_transfer = FALLBACK_MAX_CONTROL_TRANSFER;

    notify_indication (self, NULL);

    return EMBIM_STATUS_SUCCESS;
}

int
embim_device_get_fd (embim_device_t *self)
{
    return self->fd;
}

uint16_t
embim_device_get_max_control_transfer (embim_device_t *self)
{
    return self->max_control_transfer;
}

/*****************************************************************************/
/* MBIM close */

embim_status_e
embim_device_close (embim_device_t *self)
{
    if (!(self->fd < 0)) {
        close (self->fd);
        self->fd = -1;
    }

    return EMBIM_STATUS_SUCCESS;
}

/*****************************************************************************/
/* Transaction ID management */

static uint32_t
get_next_transaction_id (embim_device_t *self)
{
    uint32_t next;

    next = self->transaction_id;

    if (self->transaction_id == (uint32_t) 0xFFFFFFFF)
        /* Reset! */
        self->priv->transaction_id = 0x01;
    else
        self->priv->transaction_id++;

    return next;
}

/*****************************************************************************/
/* Registered callbacks for indications */

struct indication_s {
    embim_device_indication_cb  cb;
    void                       *user_data;
};

static void
notify_indication (embim_device_t *self,
                   const uint8_t  *indication)
{
    size_t i;

    for (i = 0; i < self->indications_size; i++) {
        /* Notify if there is a callback registered */
        if (self->indications[i].cb)
            self->indications[i].cb (self, indication, self->indications[i].user_data);
    }
}

static void
cleanup_indications (embim_device_t *self)
{
    pthread_mutex_lock (&self->indications_mutex);
    {
        free (self->indications);
        self->indications      = NULL;
        self->indications_size = 0;
        self->indications_n    = 0;
    }
    pthread_mutex_unlock (&self->indications_mutex);
}

embim_status_e
embim_device_unregister_indication_cb (embim_device_t             *self,
                                       embim_device_indication_cb  indication_cb,
                                       void                       *indication_user_data)
{
    embim_status_e st = EMBIM_STATUS_ERROR_NOT_FOUND;

    pthread_mutex_lock (&self->indications_mutex);
    {
        size_t i;

        /* Look for the slot in the indications array */
        for (i = 0; i < self->indications_size; i++) {
            if ((self->indications[i].cb == indication_cb) &&
                (self->indications[i].user_data == indication_user_data)) {
                /* Found! */
                self->indications[i].cb        = NULL;
                self->indications[i].user_data = NULL;
                st = EMBIM_STATUS_SUCCESS;
                assert (self->indications_n > 0);
                self->indications_n--;
                break;
            }
        }
    }
    pthread_mutex_unlock (&self->indications_mutex);

    return st;
}

embim_status_e
embim_device_register_indication_cb (embim_device_t             *self,
                                     embim_device_indication_cb  indication_cb,
                                     void                       *indication_user_data)
{
    embim_status_e st = EMBIM_STATUS_SUCCESS;

    pthread_mutex_lock (&self->indications_mutex);
    {
        size_t  i;
        ssize_t empty_slot = -1;

        /* Iterate all the array, looking both for a free slot and for a duplicate */
        for (i = 0; i < self->indications_size; i++) {
            /* Error out if it already exists */
            if ((self->indications[i].cb == indication_cb) &&
                (self->indications[i].user_data == indication_user_data)) {
                st = EMBIM_STATUS_ERROR_EXISTS;
                goto err;
            }

            /* Register empty slot when first found */
            if (!self->indications[i].cb && empty_slot < 0)
                empty_slot = i;
        }

        /* Need more slots, resize */
        if (empty_slot < 0) {
            struct indication_s *resized;
            size_t               resized_size;

            resized_size = self->indications_size ? (self->indications_size * 2) : 1;
            resized = (struct indication_s *) realloc (self->indications, resized_size * sizeof (struct indication_s));
            if (!resized) {
                st = EMBIM_STATUS_ERROR_NO_MEMORY;
                goto err;
            }

            memset (&resized[self->indications_size], 0, (resized_size - self->indications_size) * sizeof (struct indication_s));

            /* Empty slot will be the first one in the resized chunk */
            empty_slot = self->indications_size;

            self->indications      = resized;
            self->indications_size = resized_size;
        }

        /* Register it */
        self->indications[empty_slot].cb        = indication_cb;
        self->indications[empty_slot].user_data = indication_user_data;
        self->indications_n++;
    }

err:
    pthread_mutex_unlock (&self->indications_mutex);
    return st;
}