4abfdf1a6bb7c0c7df94df17ca5ce2549989f2b2
[platform/core/system/initrd-flash.git] / src / dfu.c
1 /*
2  * flash-manager - Tizen kernel-level image flashing solution
3  *
4  * Licensed under the Apache License, Version 2.0 (the License);
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include <stdio.h>
18 #include <string.h>
19 #include <stdlib.h>
20 #include <unistd.h>
21 #include <errno.h>
22 #include <sys/stat.h>
23 #include <sys/types.h>
24 #include <sys/queue.h>
25 #include <sys/wait.h>
26 #include <sys/fcntl.h>
27 #include <sys/mount.h>
28 #include <pthread.h>
29 #include <blkid/blkid.h>
30
31 #include "dfu.h"
32 #include "thor-proto.h"
33
34 static char *dfu_info[DFU_INFO_NUM][DFU_INFO_MAX] = {NULL,};
35
36 void *dfu_get_buffer(unsigned long size)
37 {
38         int wait = 100000; /* 100ms */
39         void *buf;
40
41         while (!(buf = malloc(size))) {
42                 /*
43                  * In the case of not enough memory, instead of returning error,
44                  * transfer thread waits until dfu writes and frees buffers.
45                  */
46                 usleep(wait);
47                 wait *= 2;
48         }
49
50         return buf;
51 }
52
53 void dfu_put_buffer(void *ptr)
54 {
55         free(ptr);
56 }
57
58 static int find_match(const char *name)
59 {
60         int i;
61
62         for (i = 0; i < DFU_INFO_NUM; i++) {
63                 char *entry = dfu_info[i][DFU_INFO_NAME];
64
65                 if (entry && !strncmp(entry, name, strlen(entry)))
66                         return i;
67         }
68
69         return -ENOENT;
70 }
71
72 int dfu_request_io(struct tfm_context *ctx, unsigned long len)
73 {
74         int notify = 0;
75         struct dfu_frame *frame;
76
77         frame = malloc(sizeof(*frame));
78         if (!frame)
79                 return -ENOMEM;
80
81         frame->buf = ctx->transfer_buffer;
82         frame->len = len;
83
84         pthread_mutex_lock(&ctx->dfu_mutex);
85
86         if (TAILQ_EMPTY(&ctx->dfu_ioq_head))
87                 notify = 1;
88
89         /* dfu_thread_main() de-queues i/o request and processes it */
90         TAILQ_INSERT_TAIL(&ctx->dfu_ioq_head, frame, entry);
91
92         pthread_mutex_unlock(&ctx->dfu_mutex);
93
94         if (notify)
95                 pthread_cond_signal(&ctx->dfu_data_arrive);
96
97         return 0;
98 }
99
100 static void mount_dev(const char *dev, const char *fstype)
101 {
102         int ret;
103
104         ret = mkdir(DFU_MOUNT_PATH,  0600);
105         if (ret < 0) {
106                 fprintf(stderr, "Failed to create target directory\n");
107                 exit(-1);
108         }
109
110         ret = mount(dev, DFU_MOUNT_PATH, fstype, 0, NULL);
111         if (ret < 0) {
112                 fprintf(stderr, "Failed to mount target partition\n");
113                 rmdir(DFU_MOUNT_PATH);
114                 exit(-1);
115         }
116 }
117
118 static void umount_dev(void)
119 {
120         int ret;
121
122         ret = umount(DFU_MOUNT_PATH);
123         if (ret < 0) {
124                 fprintf(stderr, "Failed to mount target partition\n");
125
126                 /* umount is failed, but anyway try to delete mount point */
127                 rmdir(DFU_MOUNT_PATH);
128                 exit(-1);
129         }
130
131         rmdir(DFU_MOUNT_PATH);
132 }
133
134 void dfu_sync(struct tfm_context *ctx)
135 {
136         char **info = ctx->dfu_info;
137
138         pthread_mutex_lock(&ctx->dfu_sync_mutex);
139         if (!ctx->transfer_done)
140                 pthread_cond_wait(&ctx->dfu_write_done, &ctx->dfu_sync_mutex);
141         pthread_mutex_unlock(&ctx->dfu_sync_mutex);
142
143         switch (*info[DFU_INFO_MODE]) {
144         case 'f':
145                 fsync(ctx->dfu_fd);
146                 close(ctx->dfu_fd);
147                 umount_dev();
148                 break;
149         case 'p':
150                 close(ctx->dfu_fd);
151                 break;
152         default:
153                 break;
154         }
155
156         pthread_cond_signal(&ctx->dfu_sync_done);
157
158         fprintf(stdout, "finished\n");
159 }
160
161 static char *get_partition_devname(const char *label)
162 {
163         blkid_dev_iterate dev_iter;
164         blkid_tag_iterate tag_iter;
165         blkid_dev dev;
166         blkid_cache cache = NULL;
167         const char *type, *value;
168         int ret;
169
170         ret = blkid_get_cache(&cache, NULL);
171         if (ret < 0)
172                 return NULL;
173
174         blkid_probe_all(cache);
175
176         dev_iter = blkid_dev_iterate_begin(cache);
177         blkid_dev_set_search(dev_iter, NULL, NULL);
178         while (blkid_dev_next(dev_iter, &dev) == 0) {
179                 dev = blkid_verify(cache, dev);
180                 if (!dev)
181                         continue;
182
183                 tag_iter = blkid_tag_iterate_begin(dev);
184                 while (blkid_tag_next(tag_iter, &type, &value) == 0) {
185                         if (!strncmp(type, "LABEL", 5) && !strncmp(value, label, strlen(label))) {
186                                 char *devname = strdup(blkid_dev_devname(dev));
187
188                                 /*
189                                  * To distinguish physical partition and ram mounted partition,
190                                  * continue to search the next entry if the devname includes
191                                  * 'ram'(eg. /dev/ram0).
192                                  */
193                                 if (strstr(devname, "ram")) {
194                                         free(devname);
195                                         continue;
196                                 }
197
198                                 blkid_tag_iterate_end(tag_iter);
199                                 blkid_dev_iterate_end(dev_iter);
200                                 blkid_put_cache(cache);
201                                 return devname;
202                         }
203                 }
204                 blkid_tag_iterate_end(tag_iter);
205         }
206         blkid_dev_iterate_end(dev_iter);
207         blkid_put_cache(cache);
208
209         return NULL;
210 }
211
212 static char *get_partition_fstype(const char *devname)
213 {
214         blkid_tag_iterate tag_iter;
215         blkid_dev dev;
216         blkid_cache cache = NULL;
217         const char *type, *value;
218         int ret;
219
220         ret = blkid_get_cache(&cache, NULL);
221         if (ret < 0)
222                 return NULL;
223
224         blkid_probe_all(cache);
225
226         dev = blkid_get_dev(cache, devname, 0);
227         if (!dev)
228                 return NULL;
229
230         dev = blkid_verify(cache, dev);
231         if (!dev)
232                 return NULL;
233
234         tag_iter = blkid_tag_iterate_begin(dev);
235         while (blkid_tag_next(tag_iter, &type, &value) == 0) {
236                 if (!strncmp(type, "TYPE", 4)) {
237                         char *fstype = strdup(value);
238
239                         blkid_tag_iterate_end(tag_iter);
240                         blkid_put_cache(cache);
241                         return fstype;
242                 }
243         }
244         blkid_tag_iterate_end(tag_iter);
245         blkid_put_cache(cache);
246
247         return NULL;
248 }
249
250 static int dfu_start_entity(struct tfm_context *ctx, int idx, unsigned long size)
251 {
252         char **info = dfu_info[idx];
253         char *file;
254         int fd;
255
256         switch (*info[DFU_INFO_MODE]) {
257         case 'p':
258                 file = get_partition_devname(info[DFU_INFO_LABEL]);
259                 if (!file) {
260                         fprintf(stderr, "failed to get partition devname: %s", info[DFU_INFO_LABEL]);
261                         return -EINVAL;
262                 }
263                 break;
264         case 'f':
265         {
266                 int path_prefix = strlen(DFU_MOUNT_PATH);
267                 int path_suffix = strlen(info[DFU_INFO_PATH]);
268                 int path_name = strlen(info[DFU_INFO_NAME]);
269                 char *devname, *fstype;
270
271                 devname = get_partition_devname(info[DFU_INFO_LABEL]);
272                 if (!devname) {
273                         fprintf(stderr, "failed to get partition devname: %s", info[DFU_INFO_LABEL]);
274                         return -EINVAL;
275                 }
276
277                 fstype = get_partition_fstype(devname);
278                 if (!fstype) {
279                         fprintf(stderr, "failed to get partition filesystem type: %s", devname);
280                         return -EINVAL;
281                 }
282
283                 mount_dev(devname, fstype);
284                 free(devname);
285                 free(fstype);
286
287                 file = malloc(path_prefix + path_suffix + path_name + 1);
288                 if (!file)
289                         return -ENOMEM;
290
291                 strncpy(file, DFU_MOUNT_PATH, path_prefix + 1);
292                 strncat(file, info[DFU_INFO_PATH], path_suffix);
293                 strncat(file, info[DFU_INFO_NAME], path_name);
294                 break;
295         }
296         default:
297                 fprintf(stderr, "flash entry '%s' has wrong mode\n", info[DFU_INFO_NAME]);
298                 return -EINVAL;
299         }
300
301         fd = open(file, O_WRONLY);
302         if (fd < 0) {
303                 fprintf(stderr, "cannot open target: %s\n", info[DFU_INFO_NAME]);
304                 free(file);
305                 return -EIO;
306         }
307
308         ctx->dfu_fd = fd;
309
310         ctx->dfu_info = info;
311         ctx->transfer_done = 0;
312
313         free(file);
314
315         return 0;
316 }
317
318
319 int dfu_start(struct tfm_context *ctx, const char *entity)
320 {
321         unsigned long size = ctx->thor_file_size;
322         int idx, ret;
323
324         idx = find_match(entity);
325         if (idx < 0) {
326                 fprintf(stderr, "Cannot find dfu info for %s\n", entity);
327                 return -EINVAL;
328         }
329
330         ret = dfu_start_entity(ctx, idx, size);
331         if (ret < 0) {
332                 fprintf(stderr, "Cannot start download: %s\n", entity);
333                 return -EINVAL;
334         }
335
336         fprintf(stdout, "Start download: %s...", entity);
337
338         return 0;
339 }
340
341 static void dfu_thread_cleanup(void *ptr)
342 {
343         struct tfm_context *ctx = ptr;
344         struct dfu_frame *frame;
345
346         while (!TAILQ_EMPTY(&ctx->dfu_ioq_head)) {
347                 frame = TAILQ_FIRST(&ctx->dfu_ioq_head);
348
349                 TAILQ_REMOVE(&ctx->dfu_ioq_head, frame, entry);
350
351                 dfu_put_buffer(frame->buf);
352                 free(frame);
353         }
354 }
355
356 static void *dfu_thread_main(void *ptr)
357 {
358         struct tfm_context *ctx = ptr;
359         struct dfu_frame *frame;
360         int state = DFU_THREAD_STATE_IDLE;
361         uint64_t progress = 0;
362         int ret;
363
364         pthread_cleanup_push(dfu_thread_cleanup, ptr);
365
366         while (state != DFU_THREAD_STATE_ERROR) {
367                 pthread_mutex_lock(&ctx->dfu_mutex);
368
369                 while (TAILQ_EMPTY(&ctx->dfu_ioq_head)) {
370                         pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
371                         pthread_cond_wait(&ctx->dfu_data_arrive, &ctx->dfu_mutex);
372                 }
373
374                 if (state == DFU_THREAD_STATE_IDLE) {
375                         pthread_mutex_unlock(&ctx->dfu_mutex);
376                         state = DFU_THREAD_STATE_FLASHING;
377                         pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
378                         pthread_mutex_lock(&ctx->dfu_mutex);
379                 }
380
381                 frame = TAILQ_FIRST(&ctx->dfu_ioq_head);
382
383                 TAILQ_REMOVE(&ctx->dfu_ioq_head, frame, entry);
384
385                 pthread_mutex_unlock(&ctx->dfu_mutex);
386
387                 ret = write(ctx->dfu_fd, frame->buf, frame->len);
388
389                 if (ret < frame->len) {
390                         fprintf(stdout, "Error occurs while flashing\n");
391                         state = DFU_THREAD_STATE_ERROR;
392                 }
393
394                 progress += frame->len;
395
396                 dfu_put_buffer(frame->buf);
397                 free(frame);
398
399                 /* transfer finished */
400                 if (state != DFU_THREAD_STATE_ERROR && progress >= ctx->thor_file_size) {
401                         progress = 0;
402                         ctx->transfer_done = 1;
403
404                         state = DFU_THREAD_STATE_IDLE;
405                         pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
406
407                         pthread_cond_signal(&ctx->dfu_write_done);
408
409                         pthread_mutex_lock(&ctx->dfu_sync_mutex);
410                         pthread_cond_wait(&ctx->dfu_sync_done,
411                                         &ctx->dfu_sync_mutex);
412                         pthread_mutex_unlock(&ctx->dfu_sync_mutex);
413                 }
414         }
415
416         pthread_cleanup_pop(1);
417
418         return NULL;
419 }
420
421 static int parse_dfu_info(char *buf, char **info)
422 {
423         char *ptr;
424         int i;
425
426         for (i = 0; i < DFU_INFO_MAX; i++) {
427                 ptr = strsep(&buf, DFU_DELIMITER);
428                 if (!ptr)
429                         return -EINVAL;
430
431                 info[i] = strdup(ptr);
432                 if (!info[i])
433                         return -ENOMEM;
434         }
435
436         return 0;
437 }
438
439 static void destroy_dfu_info(void)
440 {
441         int i, j;
442
443         for (i = 0; i < DFU_INFO_NUM; i++) {
444                 for (j = 0; j < DFU_INFO_MAX; j++)
445                         if (dfu_info[i][j]) {
446                                 free(dfu_info[i][j]);
447                                 dfu_info[i][j] = NULL;
448                         }
449         }
450 }
451
452
453 static int init_dfu_info(const char *dfu_info_file)
454 {
455         FILE *fp;
456         char buf[1024];
457         int i = 0;
458         int ret;
459
460         fp = fopen(dfu_info_file, "r");
461         if (!fp)
462                 return -ENOENT;
463
464         while (i < DFU_INFO_NUM && !feof(fp)) {
465                 if (fgets(buf, 1024, fp) == NULL)
466                         break;
467
468                 ret = parse_dfu_info(buf, dfu_info[i++]);
469                 if (ret < 0) {
470                         fprintf(stderr, "cannot parse dfu info");
471                         goto err_free_all;
472                 }
473         }
474
475         fclose(fp);
476
477         return 0;
478
479 err_free_all:
480         fclose(fp);
481         destroy_dfu_info();
482
483         return ret;
484 }
485
486 int dfu_init(struct tfm_context *ctx, const char *dfu_info_file)
487 {
488         int ret;
489
490         ret = init_dfu_info(dfu_info_file);
491         if (ret < 0) {
492                 fprintf(stderr, "failed to get flash entries\n");
493                 return ret;
494         }
495
496         TAILQ_INIT(&ctx->dfu_ioq_head);
497
498         pthread_mutex_init(&ctx->dfu_mutex, NULL);
499         pthread_mutex_init(&ctx->dfu_sync_mutex, NULL);
500         pthread_cond_init(&ctx->dfu_data_arrive, NULL);
501         pthread_cond_init(&ctx->dfu_write_done, NULL);
502         pthread_cond_init(&ctx->dfu_sync_done, NULL);
503
504         ret = pthread_create(&ctx->dfu_thread, NULL, dfu_thread_main, ctx);
505         if (ret < 0) {
506                 fprintf(stderr, "failed to create thread for dfu\n");
507                 return ret;
508         }
509
510         return 0;
511 }
512
513 void dfu_exit(struct tfm_context *ctx)
514 {
515         pthread_cancel(ctx->dfu_thread);
516         pthread_join(ctx->dfu_thread, NULL);
517         destroy_dfu_info();
518         if (ctx->connect) {
519                 free(ctx->connect);
520                 ctx->connect = NULL;
521         }
522 }