Don't change h->ridx when moving the handle.
[Rockbox-MoB.git] / testplugin.c
blob13b8d3efb05825e1a12ecfa178d4af25b84b00f3
1 /***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
10 * Copyright (C) 2007 Nicolas Pennequin
12 * All files in this archive are subject to the GNU General Public License.
13 * See the file COPYING in the source tree root for full license agreement.
15 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
16 * KIND, either express or implied.
18 ****************************************************************************/
21 #include "plugin.h"
23 PLUGIN_HEADER
25 struct plugin_api* rb;
27 #define GUARD_SIZE (32*1024)
29 /* amount of data to read in one read() call */
30 #define AUDIO_DEFAULT_FILECHUNK (1024*32)
32 /* Ring buffer helper macros */
33 /* Buffer pointer (p) plus value (v), wrapped if necessary */
34 #define RINGBUF_ADD(p,v) ((p+v)<buffer_len ? p+v : p+v-buffer_len)
35 /* Buffer pointer (p) minus value (v), wrapped if necessary */
36 #define RINGBUF_SUB(p,v) ((p>=v) ? p-v : p+buffer_len-v)
37 /* How far value (v) plus buffer pointer (p1) will cross buffer pointer (p2) */
38 #define RINGBUF_ADD_CROSS(p1,v,p2) \
39 ((p1<p2) ? (int)(p1+v)-(int)p2 : (int)(p1+v-p2)-(int)buffer_len)
40 /* Bytes available in the buffer */
41 #define BUF_USED RINGBUF_SUB(buf_widx, buf_ridx)
43 #ifdef ROCKBOX_HAS_LOGF
44 #define DEBUGF rb->logf
45 #endif
47 int num_files = 5;
48 char *files[] = {
49 "/a.mp3",
50 "/b.mp3",
51 "/c.mp3",
52 "/d.mp3",
53 "/e.mp3" };
55 struct memory_handle {
56 int id; /* A unique ID for the handle */
57 //enum data_type type;
58 //enum data_state state;
59 char path[MAX_PATH];
60 int fd;
61 size_t data; /* Start index of the handle's data buffer */
62 size_t data_len; /* Length of the data buffer for the handle */
63 size_t ridx; /* Current read pointer, relative to the main buffer */
64 size_t widx; /* Current write pointer */
65 size_t filesize; /* File total length */
66 size_t filerem; /* Remaining bytes of file NOT in buffer */
67 size_t available; /* Available bytes to read from buffer */
68 size_t offset; /* Offset at which we started reading the file */
69 struct memory_handle *next;
73 static char *buffer;
74 static char *guard_buffer;
76 static size_t buffer_len;
77 static size_t buf_widx;
78 static size_t buf_ridx;
80 static size_t conf_filechunk;
82 /* current memory handle in the linked list. NULL when the list is empty. */
83 static struct memory_handle *cur_handle;
84 /* first memory handle in the linked list. NULL when the list is empty. */
85 static struct memory_handle *first_handle;
86 static int num_handles;
88 static void graph_view(int width);
91 /* add a new handle to the linked list and return it. It will have become the
92 new current handle. The handle will reserve "data_size" bytes or if that's
93 not possible, decrease "data_size" to allow adding the handle. */
94 static struct memory_handle *add_handle(size_t *data_size)
96 /* this will give each handle a unique id */
97 static int cur_handle_id = 1;
99 /* make sure buf_widx is 32-bit aligned so that the handle struct is,
100 but before that we check we can actually align. */
101 if (RINGBUF_ADD_CROSS(buf_widx, 3, buf_ridx) >= 0) {
102 return NULL;
104 buf_widx = (RINGBUF_ADD(buf_widx, 3)) & ~3;
106 size_t len = (data_size ? *data_size : 0)
107 + sizeof(struct memory_handle);
109 /* check that we actually can add the handle and its data */
110 int overlap = RINGBUF_ADD_CROSS(buf_widx, len, buf_ridx);
111 if (overlap >= 0) {
112 *data_size -= overlap;
113 len -= overlap;
115 if (len < sizeof(struct memory_handle)) {
116 return NULL;
119 struct memory_handle *new_handle = (struct memory_handle *)(&buffer[buf_widx]);
121 /* only advance the buffer write index of the size of the struct */
122 buf_widx = RINGBUF_ADD(buf_widx, sizeof(struct memory_handle));
124 if (!first_handle) {
125 /* the new handle is the first one */
126 first_handle = new_handle;
129 if (cur_handle) {
130 cur_handle->next = new_handle;
133 cur_handle = new_handle;
134 cur_handle->id = cur_handle_id++;
135 cur_handle->next = NULL;
136 num_handles++;
137 return cur_handle;
140 /* delete a given memory handle from the linked list
141 and return true for success. Nothing is actually erased from memory. */
142 static bool rm_handle(struct memory_handle *h)
144 if (h == first_handle) {
145 first_handle = h->next;
146 if (h == cur_handle) {
147 DEBUGF("removing the first and last handle\n");
148 /* h was the first and last handle */
149 cur_handle = NULL;
150 buf_ridx = buf_widx;
151 } else {
152 buf_ridx = (void *)first_handle - (void *)buffer;
154 } else {
155 struct memory_handle *m = first_handle;
156 while (m && m->next != h) {
157 m = m->next;
159 if (h && m && m->next == h) {
160 m->next = h->next;
161 if (h == cur_handle) {
162 cur_handle = m;
164 } else {
165 return false;
169 num_handles--;
170 return true;
173 /* these are unfortunalty needed to be global
174 so move_handle can invalidate them */
175 static int cached_handle_id = -1;
176 static struct memory_handle *cached_handle = NULL;
178 /* Return a pointer to the memory handle of given ID.
179 NULL if the handle wasn't found */
180 static struct memory_handle *find_handle(int handle_id)
182 /* simple caching because most of the time the requested handle
183 will either be the same as the last, or the one after the last */
184 if (cached_handle)
186 if (cached_handle_id == handle_id && cached_handle_id == cached_handle->id)
187 return cached_handle;
188 else if (cached_handle->next && (cached_handle->next->id == handle_id))
190 /* JD's quick testing showd this block was only entered
191 2/1971 calls to find_handle.
192 8/1971 calls to find_handle resulted in a cache miss */
193 cached_handle = cached_handle->next;
194 cached_handle_id = handle_id;
195 return cached_handle;
199 struct memory_handle *m = first_handle;
200 while (m && m->id != handle_id) {
201 m = m->next;
203 cached_handle_id = handle_id;
204 cached_handle = m;
205 return (m && m->id == handle_id) ? m : NULL;
208 /* Move a memory handle to newpos.
209 Return a pointer to the new location of the handle */
210 static struct memory_handle *move_handle(size_t *delta, struct memory_handle *h)
212 if (*delta < 4) {
213 /* aligning backwards would yield a negative result,
214 and moving the handle of such a small amount is a waste
215 of time anyway. */
216 return NULL;
218 /* make sure delta is 32-bit aligned so that the handle struct is. */
219 *delta = (*delta - 3) & ~3;
221 size_t newpos = RINGBUF_ADD((void *)h - (void *)buffer, *delta);
223 struct memory_handle *dest = (struct memory_handle *)(&buffer[newpos]);
225 /* Invalidate the cache to prevent it from keeping the old location of h */
226 if (h == cached_handle)
227 cached_handle = NULL;
229 /* the cur_handle pointer might need updating */
230 if (h == cur_handle) {
231 cur_handle = dest;
234 if (h == first_handle) {
235 first_handle = dest;
236 buf_ridx = newpos;
237 } else {
238 struct memory_handle *m = first_handle;
239 while (m && m->next != h) {
240 m = m->next;
242 if (h && m && m->next == h) {
243 m->next = dest;
244 } else {
245 return NULL;
249 rb->memmove(dest, h, sizeof(struct memory_handle));
251 return dest;
254 /* Buffer data for the given handle. Return the amount of data buffered
255 or -1 if the handle wasn't found */
256 static ssize_t buffer_handle(int handle_id)
258 DEBUGF("buffer_handle(%d)\n", handle_id);
259 struct memory_handle *h = find_handle(handle_id);
260 if (!h)
261 return -1;
263 if (h->filerem == 0) {
264 /* nothing left to buffer */
265 return 0;
268 if (h->fd < 0) /* file closed, reopen */
270 if (*h->path)
271 h->fd = rb->open(h->path, O_RDONLY);
272 else
273 return -1;
275 if (h->fd < 0)
276 return -1;
278 if (h->offset)
279 rb->lseek(h->fd, h->offset, SEEK_SET);
282 ssize_t ret = 0;
283 while (h->filerem > 0)
285 //DEBUGF("h: %d\n", (void *)h - (void *)buffer);
286 //DEBUGF("buf_widx: %ld\n", (long)buf_widx);
287 /* max amount to copy */
288 size_t copy_n = MIN(conf_filechunk, buffer_len - buf_widx);
290 /* stop copying if it would overwrite the reading position */
291 if (RINGBUF_ADD_CROSS(buf_widx, copy_n, buf_ridx) >= 0)
292 break;
294 /* rc is the actual amount read */
295 int rc = rb->read(h->fd, &buffer[buf_widx], copy_n);
297 if (rc < 0)
299 //DEBUGF("failed on fd %d at buf_widx = %ld for handle %d\n", h->fd, (long)buf_widx, h->id);
300 DEBUGF("File ended %ld bytes early\n", (long)h->filerem);
301 h->filesize -= h->filerem;
302 h->filerem = 0;
303 break;
306 /* Advance buffer */
307 h->widx = RINGBUF_ADD(h->widx, rc);
308 if (h == cur_handle)
309 buf_widx = h->widx;
310 h->available += rc;
311 ret += rc;
312 h->filerem -= rc;
315 if (h->filerem == 0) {
316 /* finished buffering the file */
317 rb->close(h->fd);
320 DEBUGF("buffered %ld bytes (%ld of %ld available, remaining: %ld)\n",
321 ret, h->available, h->filesize, h->filerem);
323 graph_view(100);
325 return ret;
328 /* Free buffer space by moving the handle struct right before the useful
329 part of its data buffer */
330 static void free_buffer(int handle_id)
332 DEBUGF("free_buffer(%d)\n", handle_id);
333 struct memory_handle *h = find_handle(handle_id);
334 if (!h)
335 return;
337 size_t delta = RINGBUF_SUB(h->ridx, h->data);
338 h = move_handle(&delta, h);
339 if (!h) return;
340 /* The value of delta might change for alignment reasons */
341 h->data = RINGBUF_ADD(h->data, delta);
342 h->available -= delta;
344 graph_view(100);
347 static void fill_buffer(void)
349 DEBUGF("fill buffer()\n");
350 struct memory_handle *m = first_handle;
351 while (m) {
352 if (m->filerem > 0) {
353 buffer_handle(m->id);
354 return;
356 m = m->next;
360 static size_t data_rem(void)
362 size_t ret = 0;
364 struct memory_handle *m = first_handle;
365 while (m) {
366 ret += m->filerem;
367 m = m->next;
370 return ret;
373 static size_t wasted_space(void)
375 size_t ret = 0;
377 struct memory_handle *m = first_handle;
378 while (m) {
379 ret += RINGBUF_SUB(m->ridx, m->data);
380 m = m->next;
383 return ret;
386 /* Request a file be buffered
387 filename: name of the file t open
388 offset: starting offset to buffer from the file
389 RETURNS: <0 if the file cannot be opened, or one file already
390 queued to be opened, otherwise the handle for the file in the buffer
392 int bufopen(char *file, size_t offset)
394 if (cur_handle && cur_handle->filerem > 0) {
395 DEBUGF("bufopen: current handle still has data to buffer, aborted.\n");
396 return -1;
399 DEBUGF("bufopen: %s (offset: %ld)\n", file, offset);
401 int fd = rb->open(file, O_RDONLY);
402 if (fd < 0)
403 return -1;
405 size_t size = rb->filesize(fd) - offset;
406 struct memory_handle *h = add_handle(&size);
407 if (!h)
409 DEBUGF("failed to add handle\n");
410 rb->close(fd);
411 return -1;
414 if (offset) rb->lseek(fd, offset, SEEK_SET);
415 rb->strncpy(h->path, file, MAX_PATH);
416 h->fd = fd;
417 h->filesize = rb->filesize(fd);
418 h->filerem = h->filesize - offset;
419 h->offset = offset;
420 h->ridx = buf_widx;
421 h->widx = buf_widx;
422 h->data = buf_widx;
423 h->data_len = size;
424 h->available = 0;
426 DEBUGF("allocated %ld bytes. ID: %d\n", size, h->id);
427 return h->id;
430 /* Close the handle. Return 0 for success and < 0 for failure */
431 int bufclose(int handle_id)
433 DEBUGF("bufclose(%d)\n", handle_id);
434 struct memory_handle *h = find_handle(handle_id);
435 if (!h)
436 return -1;
438 rm_handle(h);
439 return 0;
442 /* Set the reading index in a handle (relatively to the start of the handle data).
443 Return 0 for success and < 0 for failure */
444 int bufseek(int handle_id, size_t offset)
446 struct memory_handle *h = find_handle(handle_id);
447 if (!h)
448 return -1;
450 if (offset > h->available)
451 return -2;
453 h->ridx = RINGBUF_ADD(h->data, offset);
454 return 0;
457 /* Advance the reading index in a handle (relatively to its current position).
458 Return 0 for success and < 0 for failure */
459 int bufadvance(int handle_id, off_t offset)
461 struct memory_handle *h = find_handle(handle_id);
462 if (!h)
463 return -1;
465 if (offset >= 0)
467 /* check for access beyond what's available */
468 if ((size_t)offset > (h->available - RINGBUF_SUB(h->ridx, h->data)))
469 return -2;
471 h->ridx = RINGBUF_ADD(h->ridx, offset);
473 else
475 /* check for access before what's available */
476 if ((size_t)(-offset) > RINGBUF_SUB(h->ridx, h->data))
477 return -2;
479 h->ridx = RINGBUF_SUB(h->ridx, (size_t)(-offset));
482 return 0;
485 /* Copy data from the given handle to the dest buffer.
486 Return the number of bytes copied or < 0 for failure. */
487 ssize_t bufread(int handle_id, size_t size, char *dest)
489 struct memory_handle *h = find_handle(handle_id);
490 size_t buffered_data;
491 if (!h)
492 return -1;
494 if (h->available == 0 && h->filerem > 0)
495 return -2;
497 if (h->available == 0 && h->filerem == 0)
498 return 0;
500 buffered_data = MIN(size, h->available);
502 if (h->ridx + buffered_data > buffer_len)
504 size_t read = buffer_len - h->ridx;
505 rb->memcpy(dest, &buffer[h->ridx], read);
506 rb->memcpy(dest+read, buffer, buffered_data - read);
508 else rb->memcpy(dest, &buffer[h->ridx], buffered_data);
510 h->ridx = RINGBUF_ADD(h->ridx, buffered_data);
511 h->available -= buffered_data;
512 return buffered_data;
515 /* Update the "data" pointer to make the handle's data available to the caller.
516 Return the length of the available linear data or < 0 for failure. */
517 ssize_t bufgetdata(int handle_id, size_t size, unsigned char **data)
519 struct memory_handle *h = find_handle(handle_id);
520 if (!h)
521 return -1;
523 if (h->available == 0 && h->filerem > 0)
524 return -2;
526 if (h->available == 0 && h->filerem == 0)
527 return 0;
529 ssize_t ret;
531 if (h->ridx + size > buffer_len &&
532 h->available - RINGBUF_SUB(h->ridx, h->data) >= size)
534 /* use the guard buffer to provide what was requested. */
535 size_t copy_n = h->ridx + size - buffer_len;
536 rb->memcpy(guard_buffer, (unsigned char *)buffer, copy_n);
537 ret = size;
538 DEBUGF("used the guard buffer to complete\n");
540 else
542 ret = MIN(h->available - RINGBUF_SUB(h->ridx, h->data),
543 buffer_len - h->ridx);
546 *data = (unsigned char *)&buffer[h->ridx];
548 //DEBUGF("bufgetdata(%d): h->ridx=%ld, ret=%ld\n", handle_id, (long)h->ridx, ret);
549 return ret;
552 bool test_ll(void)
554 struct memory_handle *m1, *m2, *m3, *m4;
556 if (cur_handle != NULL || first_handle != NULL)
557 return false;
559 m1 = add_handle(NULL);
561 if (cur_handle != m1 || first_handle != m1 || m1->next != NULL)
562 return false;
564 m2 = add_handle(NULL);
566 if (cur_handle != m2 || first_handle != m1 || m1->next != m2 || m2->next != NULL)
567 return false;
569 m3 = add_handle(NULL);
571 if (cur_handle != m3 || first_handle != m1 || m2->next != m3 || m3->next != NULL)
572 return false;
574 rm_handle(m2);
576 if (cur_handle != m3 || first_handle != m1 || m1->next != m3)
577 return false;
579 rm_handle(m3);
581 if (cur_handle != m1 || first_handle != m1 || m1->next != NULL)
582 return false;
584 m4 = add_handle(NULL);
586 if (cur_handle != m4 || first_handle != m1 || m1->next != m4 || m4->next != NULL)
587 return false;
589 rm_handle(m1);
591 if (cur_handle != m4 || first_handle != m4)
592 return false;
594 rm_handle(m4);
596 if (cur_handle != NULL || first_handle != NULL)
597 return false;
599 m1 = add_handle(NULL);
600 m2 = add_handle(NULL);
601 m3 = add_handle(NULL);
602 m4 = add_handle(NULL);
604 if (cur_handle != m4 || first_handle != m1)
605 return false;
607 #if 0
608 m2 = move_handle(1024*100, m2);
610 if (cur_handle != m4 || first_handle != m1 || m1->next != m2 || m2->next != m3)
611 return false;
613 m1 = move_handle(1024*100*3, m1);
615 if (cur_handle != m4 || first_handle != m1 || m1->next != m2)
616 return false;
617 #endif
619 rm_handle(m1);
620 rm_handle(m2);
621 rm_handle(m3);
622 rm_handle(m4);
624 if (cur_handle != NULL || first_handle != NULL)
625 return false;
627 return true;
630 /* display a nice graphical view of the ringbuffer. */
631 static void graph_view(int width)
633 #ifndef ROCKBOX_HAS_LOGF
634 int i, r_pos, w_pos;
635 r_pos = buf_ridx * width / buffer_len;
636 w_pos = buf_widx * width / buffer_len;
638 DEBUGF("|");
639 for (i=0; i <= width; i++)
641 if (i != r_pos && i != w_pos)
643 if (buf_ridx <= buf_widx)
645 if (i > r_pos && i < w_pos) {
646 DEBUGF(">");
647 } else {
648 DEBUGF("-");
651 else
653 if (i > r_pos || i < w_pos) {
654 DEBUGF(">");
655 } else {
656 DEBUGF("-");
660 else
662 if (i == r_pos && i == w_pos)
664 if (buf_ridx <= buf_widx) {
665 DEBUGF("RW");
666 } else {
667 DEBUGF("WR");
669 } else if (i == r_pos) {
670 DEBUGF("R");
671 } else if (i == w_pos) {
672 DEBUGF("W");
676 DEBUGF("|");
677 DEBUGF("\n");
678 #else
679 (void)width;
680 #endif
683 void print_progress(size_t amount, int file, int numfiles)
685 char buf[32];
686 rb->lcd_clear_display();
687 rb->snprintf(buf, sizeof(buf), "file %d of %d", file, numfiles);
688 rb->lcd_puts(0, 0, buf);
689 rb->snprintf(buf, sizeof(buf), "read: %ld", amount);
690 rb->lcd_puts(0, 1, buf);
691 rb->lcd_update();
694 bool buffer_init(void)
696 buffer = rb->plugin_get_audio_buffer(&buffer_len);
697 if (!buffer)
699 DEBUGF("couldn't allocate buffer\n");
700 return false;
702 buffer_len -= GUARD_SIZE;
703 guard_buffer = buffer + buffer_len;
705 buf_widx = 0;
706 buf_ridx = 0;
708 first_handle = NULL;
709 num_handles = 0;
711 conf_filechunk = AUDIO_DEFAULT_FILECHUNK;
713 return true;
716 bool disk_is_spinning(void)
718 return true;
723 static long codec_stack[4*DEFAULT_STACK_SIZE/sizeof(long)];
724 static struct thread_entry* codecthread_id;
726 static long bufopen_stack[DEFAULT_STACK_SIZE/sizeof(long)];
727 static struct thread_entry* bufopenthread_id;
729 bool done_playing = false;
731 #define MAX_HANDLES 16
733 int handles[MAX_HANDLES];
735 void codec_thread(void)
737 int idx = 0;
738 int fd = -1; /* used to write the files out as they are read */
739 unsigned char *data;
740 char outfile[MAX_PATH];
741 long read, total = 0;
743 while (1)
745 if (!done_playing)
747 if (handles[idx] > 0) {
749 if (fd < 0) {
750 rb->snprintf(outfile, MAX_PATH, "/file%d.mp3", idx);
751 fd = rb->open(outfile, O_CREAT|O_TRUNC|O_WRONLY);
752 if (fd < 0) {
753 DEBUGF("couldn't create file\n");
754 rb->splash(HZ, "couldn't create file");
758 do {
759 read = bufgetdata(handles[idx], GUARD_SIZE, &data);
760 if (read >= 0) {
761 read = MIN(read, GUARD_SIZE);
762 rb->write(fd, data, read);
763 total += read;
764 bufadvance(handles[idx], read);
765 print_progress(total, idx+1, num_files);
767 rb->sleep(HZ/20);
768 } while (read > 0);
770 if (read >= 0 && total >= 0) {
771 DEBUGF("read %ld bytes from handle %d\n", total, handles[idx]);
774 if (read == -2) {
775 DEBUGF("data for handle %d isn't ready\n", handles[idx]);
776 } else if (read == -1) {
777 DEBUGF("couldn't find handle %d\n", handles[idx]);
778 } else if (read == 0) {
779 DEBUGF("finished reading handle %d\n", handles[idx]);
780 bufclose(handles[idx]);
781 rb->close(fd);
782 fd = -1;
783 total = 0;
784 idx++;
786 if (idx >= num_files) {
787 done_playing = true;
788 break;
794 rb->sleep(HZ/4);
797 DEBUGF("removing the codec thread\n");
798 rb->remove_thread(NULL);
801 void bufopen_thread(void)
803 int idx = 0, ret;
804 while (idx < num_files)
806 ret = bufopen(files[idx], 0);
807 if (ret > 0) {
808 handles[idx++] = ret;
810 rb->sleep(HZ*8);
813 DEBUGF("bufopen thread finished\n");
814 rb->remove_thread(NULL);
818 /* this is the plugin entry point */
819 enum plugin_status plugin_start(struct plugin_api* api, void* parameter)
821 (void)parameter;
822 rb = api;
824 buffer_init();
826 if (!test_ll())
828 DEBUGF("linked list test failed\n");
829 rb->splash(HZ, "linked list test failed");
830 return PLUGIN_ERROR;
833 codecthread_id = rb->create_thread(codec_thread,
834 codec_stack,
835 sizeof(codec_stack),
836 "codec"
837 IF_PRIO(,PRIORITY_PLAYBACK)
838 IF_COP(, CPU, false));
840 bufopenthread_id = rb->create_thread(bufopen_thread,
841 bufopen_stack,
842 sizeof(bufopen_stack),
843 "bufopen"
844 IF_PRIO(,PRIORITY_BACKGROUND)
845 IF_COP(, CPU, false));
847 if (!codecthread_id)
849 rb->splash(HZ, "failed to create codec thread");
850 return PLUGIN_ERROR;
852 else if (!bufopenthread_id)
854 rb->splash(HZ, "failed to create bufopen thread");
855 return PLUGIN_ERROR;
857 else
860 while (!done_playing)
862 if (wasted_space() > buffer_len/4) {
863 DEBUGF("there is %ld bytes of wasted space\n", wasted_space());
864 struct memory_handle *m = first_handle;
865 while (m) {
866 free_buffer(m->id);
867 m = m->next;
871 if (data_rem() > 0 && BUF_USED < buffer_len/4 && disk_is_spinning()) {
872 DEBUGF("%ld bytes left to buffer and the buffer is running low\n", data_rem());
873 fill_buffer();
874 } else {
875 rb->sleep(HZ/2);
878 DEBUGF("done playing\n");
879 rb->yield();
882 rb->backlight_on();
883 DEBUGF("end of plugin\n");
884 return PLUGIN_OK;