summaryrefslogtreecommitdiff
path: root/apps/plugins/lamp.c
blob: 4d4205a093c9f3a388aafe3e25273992ad0b1995 (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
/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _// __ \_/ ___\|  |/ /| __ \ / __ \  \/  /
 *   Jukebox    |    |   ( (__) )  \___|    ( | \_\ ( (__) )    (
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * Copyright (C) 2007 Vuong Minh Hiep (vmh)
 * Copyright (C) 2008 Thomas Martitz (kugel.)
 * Copyright (C) 2008 Alexander Papst
 * Copyright (C) 2008 Peter D'Hoye
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/

#include "plugin.h"
#include "lib/helper.h"
#include "lib/pluginlib_actions.h"

/* this set the context to use with PLA */
static const struct button_mapping *plugin_contexts[] = { pla_main_ctx };

/* variable button definitions.
 - only targets with a colour display
    LAMP_LEFT / LAMP_RIGHT: change the color
    LAMP_NEXT / LAMP_PREV:  (optional) change the color
 - only targets which can set brightness
    LAMP_UP / LAMP_DOWN:    change the brightness
*/

/* we use PLA */
#ifdef HAVE_SCROLLWHEEL
#   define LAMP_LEFT              PLA_LEFT
#   define LAMP_RIGHT             PLA_RIGHT
#   define LAMP_UP                PLA_SCROLL_FWD
#   define LAMP_DOWN              PLA_SCROLL_BACK
#   define LAMP_UP_REPEAT         PLA_SCROLL_FWD_REPEAT
#   define LAMP_DOWN_REPEAT       PLA_SCROLL_BACK_REPEAT
#else
#   define LAMP_LEFT              PLA_LEFT
#   define LAMP_RIGHT             PLA_RIGHT
#   define LAMP_UP                PLA_UP
#   define LAMP_DOWN              PLA_DOWN
#   define LAMP_UP_REPEAT         PLA_UP_REPEAT
#   define LAMP_DOWN_REPEAT       PLA_DOWN_REPEAT
#endif/* HAVE_SCROLLWHEEL */


#define LAMP_EXIT        PLA_EXIT
#define LAMP_EXIT2       PLA_CANCEL


#ifdef HAVE_LCD_COLOR
/* RGB color sets */
#define NUM_COLORSETS   2
static unsigned colorset[NUM_COLORSETS] = {
    LCD_RGBPACK(255, 255, 255),    /* white */
    LCD_RGBPACK(255,   0,   0),    /* red */
};
#endif /* HAVE_LCD_COLOR */

/* this is the plugin entry point */
enum plugin_status plugin_start(const void* parameter)
{
    enum plugin_status status = PLUGIN_OK;
    long button;
    bool quit = false;
    (void)parameter;

#ifdef HAVE_LCD_COLOR
    int cs = 0;
    bool update = false;
#endif /* HAVE_LCD_COLOR */

#if LCD_DEPTH > 1
    unsigned bg_color = rb->lcd_get_background();
    rb->lcd_set_backdrop(NULL);
    rb->lcd_set_background(LCD_WHITE);
#endif

#ifdef HAVE_BACKLIGHT_BRIGHTNESS
    int current_brightness = MAX_BRIGHTNESS_SETTING;
    backlight_brightness_set(MAX_BRIGHTNESS_SETTING);
#endif /* HAVE_BACKLIGHT_BRIGHTNESS */
#ifdef HAVE_BUTTONLIGHT_BRIGHTNESS
    buttonlight_brightness_set(MAX_BRIGHTNESS_SETTING);
#endif /* HAVE_BUTTONLIGHT_BRIGHTNESS */

#ifdef HAVE_LCD_INVERT
#ifdef HAVE_NEGATIVE_LCD
    rb->lcd_set_invert_display(true);
#else
    rb->lcd_set_invert_display(false);
#endif /* HAVE_NEGATIVE_LCD */
#endif /* HAVE_LCD_INVERT */

    backlight_force_on();
#ifdef HAVE_BUTTON_LIGHT
    buttonlight_force_on();
#endif /* HAVE_BUTTON_LIGHT */

    rb->lcd_clear_display();
    rb->lcd_update();

    do
    {
#ifdef HAVE_LCD_COLOR
        if(update)
        {
            if(cs < 0)
                cs = NUM_COLORSETS-1;
            if(cs >= NUM_COLORSETS)
                cs = 0;
            rb->lcd_set_background(colorset[cs]);
            rb->lcd_clear_display();
            rb->lcd_update();
            update = false;
        }
#endif /* HAVE_LCD_COLOR */
        button = pluginlib_getaction(HZ*30, plugin_contexts,
                               ARRAYLEN(plugin_contexts));

        switch(button)
        {
#ifdef HAVE_LCD_COLOR
            case LAMP_RIGHT:
#ifdef LAMP_NEXT
            case LAMP_NEXT:
#endif /* LAMP_NEXT */
                cs++;
                update = true;
                break;

            case LAMP_LEFT:
#ifdef LAMP_PREV
            case LAMP_PREV:
#endif /* LAMP_PREV */
                cs--;
                update = true;
                break;
#endif /* HAVE_LCD_COLOR */

#ifdef HAVE_BACKLIGHT_BRIGHTNESS
            case LAMP_UP:
            case (LAMP_UP_REPEAT):
                if (current_brightness < MAX_BRIGHTNESS_SETTING)
                    backlight_brightness_set(++current_brightness);
                break;

            case LAMP_DOWN:
            case (LAMP_DOWN_REPEAT):
                if (current_brightness > MIN_BRIGHTNESS_SETTING)
                    backlight_brightness_set(--current_brightness);
                break;
#endif /* HAVE_BACKLIGHT_BRIGHTNESS */
            case LAMP_EXIT:
            case LAMP_EXIT2:
                quit = true;
                break;
            case BUTTON_NONE:
                /* time out */
                break;
            default:
                if(rb->default_event_handler(button) == SYS_USB_CONNECTED)
                {
                    status = PLUGIN_USB_CONNECTED;
                    quit = true;
                }
        }
        rb->reset_poweroff_timer();
    } while (!quit);

    /* restore */
    backlight_use_settings();
#ifdef HAVE_BUTTON_LIGHT
    buttonlight_use_settings();
#endif /* HAVE_BUTTON_LIGHT */

#ifdef HAVE_LCD_INVERT
    rb->lcd_set_invert_display(rb->global_settings->invert);
#endif /* HAVE_LCD_INVERT */

#ifdef HAVE_BACKLIGHT_BRIGHTNESS
    backlight_brightness_use_setting();
#endif /* HAVE_BACKLIGHT_BRIGHTNESS */
#ifdef HAVE_BUTTONLIGHT_BRIGHTNESS
    buttonlight_brightness_use_setting();
#endif /* HAVE_BUTTONLIGHT_BRIGHTNESS */

#if LCD_DEPTH > 1
    rb->lcd_set_background(bg_color);
#endif
    return status;
}
705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989
/***************************************************************************
 *             __________               __   ___.
 *   Open      \______   \ ____   ____ |  | _\_ |__   _______  ___
 *   Source     |       _//  _ \_/ ___\|  |/ /| __ \ /  _ \  \/  /
 *   Jukebox    |    |   (  <_> )  \___|    < | \_\ (  <_> > <  <
 *   Firmware   |____|_  /\____/ \___  >__|_ \|___  /\____/__/\_ \
 *                     \/            \/     \/    \/            \/
 * $Id$
 *
 * mpegplayer buffering routines
 *
 * Copyright (c) 2007 Michael Sevakis
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
 * KIND, either express or implied.
 *
 ****************************************************************************/
#include "plugin.h"
#include "mpegplayer.h"
#include <system.h>

static struct mutex disk_buf_mtx SHAREDBSS_ATTR;
static struct event_queue disk_buf_queue SHAREDBSS_ATTR;
static struct queue_sender_list disk_buf_queue_send SHAREDBSS_ATTR;
static uint32_t disk_buf_stack[DEFAULT_STACK_SIZE*2/sizeof(uint32_t)];

struct disk_buf disk_buf SHAREDBSS_ATTR;
static void *nf_list[MPEGPLAYER_MAX_STREAMS+1];

static inline void disk_buf_lock(void)
{
    rb->mutex_lock(&disk_buf_mtx);
}

static inline void disk_buf_unlock(void)
{
    rb->mutex_unlock(&disk_buf_mtx);
}

static inline void disk_buf_on_clear_data_notify(struct stream_hdr *sh)
{
    DEBUGF("DISK_BUF_CLEAR_DATA_NOTIFY: 0x%02X (cleared)\n",
           STR_FROM_HDR(sh)->id);
    list_remove_item(nf_list, sh);
}

inline bool disk_buf_is_data_ready(struct stream_hdr *sh,
                                   ssize_t margin)
{
    /* Data window available? */
    off_t right = sh->win_right;

    /* Margins past end-of-file can still return true */
    if (right > disk_buf.filesize - margin)
       right = disk_buf.filesize - margin;

    return sh->win_left >= disk_buf.win_left &&
           right + margin <= disk_buf.win_right;
}

void dbuf_l2_init(struct dbuf_l2_cache *l2_p)
{
    l2_p->addr = OFF_T_MAX; /* Mark as invalid */
}

static int disk_buf_on_data_notify(struct stream_hdr *sh)
{
    DEBUGF("DISK_BUF_DATA_NOTIFY: 0x%02X ", STR_FROM_HDR(sh)->id);

    if (sh->win_left <= sh->win_right)
    {
        /* Check if the data is already ready already */
        if (disk_buf_is_data_ready(sh, 0))
        {
            /* It was - don't register */
            DEBUGF("(was ready)\n"
                   "  swl:%lu swr:%lu\n"
                   "  dwl:%lu dwr:%lu\n",
                   sh->win_left, sh->win_right,
                   disk_buf.win_left, disk_buf.win_right);
            /* Be sure it's not listed though if multiple requests were made */
            list_remove_item(nf_list, sh);
            return DISK_BUF_NOTIFY_OK;
        }

        switch (disk_buf.state)
        {
        case TSTATE_DATA:
        case TSTATE_BUFFERING:
        case TSTATE_INIT:
            disk_buf.state = TSTATE_BUFFERING;
            list_add_item(nf_list, sh);
            DEBUGF("(registered)\n"
                   "  swl:%lu swr:%lu\n"
                   "  dwl:%lu dwr:%lu\n",
                   sh->win_left, sh->win_right,
                   disk_buf.win_left, disk_buf.win_right);
            return DISK_BUF_NOTIFY_REGISTERED;
        }
    }

    DEBUGF("(error)\n");
    return DISK_BUF_NOTIFY_ERROR;
}

static bool check_data_notifies_callback(struct stream_hdr *sh, intptr_t data)
{
    if (disk_buf_is_data_ready(sh, 0))
    {
        /* Remove from list then post notification - post because send
         * could result in a wait for each thread to finish resulting
         * in deadlock */
        list_remove_item(nf_list, sh);
        str_post_msg(STR_FROM_HDR(sh), DISK_BUF_DATA_NOTIFY, 0);
        DEBUGF("DISK_BUF_DATA_NOTIFY: 0x%02X (notified)\n",
               STR_FROM_HDR(sh)->id);
    }

    return true;
    (void)data;
}

/* Check registered streams and notify them if their data is available */
static inline void check_data_notifies(void)
{
    list_enum_items(nf_list,
                    (list_enum_callback_t)check_data_notifies_callback,
                    0);
}

/* Clear all registered notifications - do not post them */
static inline void clear_data_notifies(void)
{
    list_clear_all(nf_list);
}

/* Background buffering when streaming */
static inline void disk_buf_buffer(void)
{
    struct stream_window sw;

    switch (disk_buf.state)
    {
    default:
    {
        size_t wm;
        uint32_t time;

        /* Get remaining minimum data based upon the stream closest to the
         * right edge of the window */
        if (!stream_get_window(&sw))
            break;

        time = stream_get_ticks(NULL);
        wm = muldiv_uint32(5*CLOCK_RATE, sw.right - disk_buf.pos_last,
                           time - disk_buf.time_last);
        wm = MIN(wm, (size_t)disk_buf.size);
        wm = MAX(wm, DISK_BUF_LOW_WATERMARK);

        disk_buf.time_last = time;
        disk_buf.pos_last = sw.right;

        /* Fast attack, slow decay */
        disk_buf.low_wm = (wm > (size_t)disk_buf.low_wm) ?
            wm : AVERAGE(disk_buf.low_wm, wm, 16);

#if 0
        rb->splashf(0, "*%10ld %10ld", disk_buf.low_wm,
                   disk_buf.win_right - sw.right);
#endif

        if (disk_buf.win_right - sw.right > disk_buf.low_wm)
            break;

        disk_buf.state = TSTATE_BUFFERING;
        } /* default: */

        /* Fall-through */
    case TSTATE_BUFFERING:
    {
        ssize_t len, n;
        uint32_t tag, *tag_p;

        /* Limit buffering up to the stream with the least progress */
        if (!stream_get_window(&sw))
        {
            disk_buf.state = TSTATE_DATA;
            rb->storage_sleep();
            break;
        }

        /* Wrap pointer */
        if (disk_buf.tail >= disk_buf.end)
            disk_buf.tail = disk_buf.start;

        len = disk_buf.size - disk_buf.win_right + sw.left;

        if (len < DISK_BUF_PAGE_SIZE)
        {
            /* Free space is less than one page */
            disk_buf.state = TSTATE_DATA;
            disk_buf.low_wm = DISK_BUF_LOW_WATERMARK;
            rb->storage_sleep();
            break;
        }

        len = disk_buf.tail - disk_buf.start;
        tag = MAP_OFFSET_TO_TAG(disk_buf.win_right);
        tag_p = &disk_buf.cache[len >> DISK_BUF_PAGE_SHIFT];

        if (*tag_p != tag)
        {
            if (disk_buf.need_seek)
            {
                rb->lseek(disk_buf.in_file, disk_buf.win_right, SEEK_SET);
                disk_buf.need_seek = false;
            }

            n = rb->read(disk_buf.in_file, disk_buf.tail, DISK_BUF_PAGE_SIZE);

            if (n <= 0)
            {
                /* Error or end of stream */
                disk_buf.state = TSTATE_EOS;
                rb->storage_sleep();
                break;
            }

            if (len < DISK_GUARDBUF_SIZE)
            {
                /* Autoguard guard-o-rama - maintain guardbuffer coherency */
                rb->memcpy(disk_buf.end + len, disk_buf.tail,
                           MIN(DISK_GUARDBUF_SIZE - len, n));
            }

            /* Update the cache entry for this page */
            *tag_p = tag;
        }
        else
        {
            /* Skipping a read */
            n = MIN(DISK_BUF_PAGE_SIZE,
                    disk_buf.filesize - disk_buf.win_right);
            disk_buf.need_seek = true;
        }

        disk_buf.tail += DISK_BUF_PAGE_SIZE;

        /* Keep left edge moving forward */

        /* Advance right edge in temp variable first, then move
         * left edge if overflow would occur. This avoids a stream
         * thinking its data might be available when it actually
         * may not end up that way after a slide of the window. */
        len = disk_buf.win_right + n;

        if (len - disk_buf.win_left > disk_buf.size)
            disk_buf.win_left += n;

        disk_buf.win_right = len;

        /* Continue buffering until filled or file end */
        rb->yield();
        } /* TSTATE_BUFFERING: */

    case TSTATE_EOS:
        break;
    } /* end switch */
}

static void disk_buf_on_reset(ssize_t pos)
{
    int page;
    uint32_t tag;
    off_t anchor;

    disk_buf.state = TSTATE_INIT;
    disk_buf.status = STREAM_STOPPED;
    clear_data_notifies();

    if (pos >= disk_buf.filesize)
    {
        /* Anchor on page immediately following the one containing final
         * data */
        anchor = disk_buf.file_pages * DISK_BUF_PAGE_SIZE;
        disk_buf.win_left = disk_buf.filesize;
    }
    else
    {
        anchor = pos & ~DISK_BUF_PAGE_MASK;
        disk_buf.win_left = anchor;
    }

    /* Collect all valid data already buffered that is contiguous with the
     * current position - probe to left, then to right */
    if (anchor > 0)
    {
        page = MAP_OFFSET_TO_PAGE(anchor);
        tag = MAP_OFFSET_TO_TAG(anchor);

        do
        {
            if (--tag, --page < 0)
                page = disk_buf.pgcount - 1;
    
            if (disk_buf.cache[page] != tag)
                break;

            disk_buf.win_left = tag << DISK_BUF_PAGE_SHIFT;
        }
        while (tag > 0);
    }

    if (anchor < disk_buf.filesize)
    {
        page = MAP_OFFSET_TO_PAGE(anchor);
        tag = MAP_OFFSET_TO_TAG(anchor);

        do
        {
            if (disk_buf.cache[page] != tag)
                break;

            if (++tag, ++page >= disk_buf.pgcount)
                page = 0;

            anchor += DISK_BUF_PAGE_SIZE;
        }
        while (anchor < disk_buf.filesize);
    }

    if (anchor >= disk_buf.filesize)
    {
        disk_buf.win_right = disk_buf.filesize;
        disk_buf.state = TSTATE_EOS;
    }
    else
    {
        disk_buf.win_right = anchor;
    }

    disk_buf.tail = disk_buf.start + MAP_OFFSET_TO_BUFFER(anchor);

    DEBUGF("disk buf reset\n"
           "  dwl:%ld dwr:%ld\n",
           disk_buf.win_left, disk_buf.win_right);

    /* Next read position is at right edge */
    rb->lseek(disk_buf.in_file, disk_buf.win_right, SEEK_SET);
    disk_buf.need_seek = false;

    disk_buf_reply_msg(disk_buf.win_right - disk_buf.win_left);
}

static void disk_buf_on_stop(void)
{
    bool was_buffering = disk_buf.state == TSTATE_BUFFERING;

    disk_buf.state = TSTATE_EOS;
    disk_buf.status = STREAM_STOPPED;
    clear_data_notifies();

    disk_buf_reply_msg(was_buffering);
}

static void disk_buf_on_play_pause(bool play, bool forcefill)
{
    struct stream_window sw;

    if (disk_buf.state != TSTATE_EOS)
    {
        if (forcefill)
        {
            /* Force buffer filling to top */
            disk_buf.state = TSTATE_BUFFERING;
        }
        else if (disk_buf.state != TSTATE_BUFFERING)
        {
            /* If not filling already, simply monitor */
            disk_buf.state = TSTATE_DATA;
        }
    }
    /* else end of stream - no buffering to do */

    disk_buf.pos_last = stream_get_window(&sw) ? sw.right : 0;
    disk_buf.time_last = stream_get_ticks(NULL);

    disk_buf.status = play ? STREAM_PLAYING : STREAM_PAUSED;
}

static int disk_buf_on_load_range(struct dbuf_range *rng)
{
    uint32_t tag = rng->tag_start;
    uint32_t tag_end = rng->tag_end;
    int page = rng->pg_start;

    /* Check if a seek is required */
    bool need_seek = rb->lseek(disk_buf.in_file, 0, SEEK_CUR)
                        != (off_t)(tag << DISK_BUF_PAGE_SHIFT);

    do
    {
        uint32_t *tag_p = &disk_buf.cache[page];

        if (*tag_p != tag)
        {
            /* Page not cached - load it */
            ssize_t o, n;

            if (need_seek)
            {
                rb->lseek(disk_buf.in_file, tag << DISK_BUF_PAGE_SHIFT,
                          SEEK_SET);
                need_seek = false;
            }

            o = page << DISK_BUF_PAGE_SHIFT;
            n = rb->read(disk_buf.in_file, disk_buf.start + o,
                         DISK_BUF_PAGE_SIZE);

            if (n < 0)
            {
                /* Read error */
                return DISK_BUF_NOTIFY_ERROR;
            }

            if (n == 0)
            {
                /* End of file */
                break;
            }

            if (o < DISK_GUARDBUF_SIZE)
            {
                /* Autoguard guard-o-rama - maintain guardbuffer coherency */
                rb->memcpy(disk_buf.end + o, disk_buf.start + o,
                           MIN(DISK_GUARDBUF_SIZE - o, n));
            }

            /* Update the cache entry */
            *tag_p = tag;
        }
        else
        {
            /* Skipping a disk read - must seek on next one */
            need_seek = true;
        }

        if (++page >= disk_buf.pgcount)
            page = 0;
    }
    while (++tag <= tag_end);

    return DISK_BUF_NOTIFY_OK;
}

static void disk_buf_thread(void)
{
    struct queue_event ev;

    disk_buf.state = TSTATE_EOS;
    disk_buf.status = STREAM_STOPPED;

    while (1)    
    {
        if (disk_buf.state != TSTATE_EOS)
        {
            /* Poll buffer status and messages */
            rb->queue_wait_w_tmo(disk_buf.q, &ev,
                                 disk_buf.state == TSTATE_BUFFERING ?
                                    0 : HZ/5);
        }
        else
        {
            /* Sit idle and wait for commands */
            rb->queue_wait(disk_buf.q, &ev);
        }

        switch (ev.id)
        {
        case SYS_TIMEOUT:
            if (disk_buf.state == TSTATE_EOS)
                break;

            disk_buf_buffer();

            /* Check for any due notifications if any are pending */
            if (*nf_list != NULL)
                check_data_notifies();

            /* Still more data left? */
            if (disk_buf.state != TSTATE_EOS)
                continue;

            /* Nope - end of stream */
            break;

        case DISK_BUF_CACHE_RANGE:
            disk_buf_reply_msg(disk_buf_on_load_range(
                                (struct dbuf_range *)ev.data));
            break;

        case STREAM_RESET:
            disk_buf_on_reset(ev.data);
            break;

        case STREAM_STOP:
            disk_buf_on_stop();
            break;

        case STREAM_PAUSE:
        case STREAM_PLAY:
            disk_buf_on_play_pause(ev.id == STREAM_PLAY, ev.data != 0);
            disk_buf_reply_msg(1);
            break;

        case STREAM_QUIT:
            disk_buf.state = TSTATE_EOS;
            return;

        case DISK_BUF_DATA_NOTIFY:
            disk_buf_reply_msg(disk_buf_on_data_notify(
                                (struct stream_hdr *)ev.data));
            break;

        case DISK_BUF_CLEAR_DATA_NOTIFY:
            disk_buf_on_clear_data_notify((struct stream_hdr *)ev.data);
            disk_buf_reply_msg(1);
            break;
        }
    }
}

/* Caches some data from the current file */
static ssize_t disk_buf_probe(off_t start, size_t length, void **p)
{
    off_t end;
    uint32_t tag, tag_end;
    int page;

    /* Can't read past end of file */
    if (length > (size_t)(disk_buf.filesize - start))
    {
        length = disk_buf.filesize - start;
    }

    /* Can't cache more than the whole buffer size */
    if (length > (size_t)disk_buf.size)
    {
        length = disk_buf.size;
    }
    /* Zero-length probes permitted */

    end = start + length;

    /* Prepare the range probe */
    tag = MAP_OFFSET_TO_TAG(start);
    tag_end = MAP_OFFSET_TO_TAG(end);
    page = MAP_OFFSET_TO_PAGE(start);

    /* If the end is on a page boundary, check one less or an extra
     * one will be probed */
    if (tag_end > tag && (end & DISK_BUF_PAGE_MASK) == 0)
    {
        tag_end--;
    }

    if (p != NULL)
    {
        *p = disk_buf.start + (page << DISK_BUF_PAGE_SHIFT)
                + (start & DISK_BUF_PAGE_MASK);
    }

    /* Obtain initial load point. If all data was cached, no message is sent
     * otherwise begin on the first page that is not cached. Since we have to
     * send the message anyway, the buffering thread will determine what else
     * requires loading on its end in order to cache the specified range. */
    do
    {
        if (disk_buf.cache[page] != tag)
        {
            static struct dbuf_range rng IBSS_ATTR;
            intptr_t result;

            DEBUGF("disk_buf: cache miss\n");
            rng.tag_start = tag;
            rng.tag_end = tag_end;
            rng.pg_start = page;
            
            result = rb->queue_send(disk_buf.q, DISK_BUF_CACHE_RANGE,
                                    (intptr_t)&rng);

            return result == DISK_BUF_NOTIFY_OK ? (ssize_t)length : -1;
        }

        if (++page >= disk_buf.pgcount)
            page = 0;
    }
    while (++tag <= tag_end);

    return length;
}

/* Attempt to get a pointer to size bytes on the buffer. Returns real amount of
 * data available as well as the size of non-wrapped data after *p. */
ssize_t _disk_buf_getbuffer(size_t size, void **pp, void **pwrap,
                            size_t *sizewrap)
{
    disk_buf_lock();

    size = disk_buf_probe(disk_buf.offset, size, pp);

    if (size != (size_t)-1 && pwrap && sizewrap)
    {
        uint8_t *p = (uint8_t *)*pp;

        if (p + size > disk_buf.end + DISK_GUARDBUF_SIZE)
        {
            /* Return pointer to wraparound and the size of same */
            size_t nowrap = (disk_buf.end + DISK_GUARDBUF_SIZE) - p;
            *pwrap = disk_buf.start + DISK_GUARDBUF_SIZE;
            *sizewrap = size - nowrap;
        }
        else
        {
            *pwrap = NULL;
            *sizewrap = 0;
        }
    }

    disk_buf_unlock();

    return size;
}

ssize_t _disk_buf_getbuffer_l2(struct dbuf_l2_cache *l2,
                               size_t size, void **pp)
{
    off_t offs;
    off_t l2_addr;
    size_t l2_size;
    void *l2_p;

    if (l2 == NULL)
    {
        /* Shouldn't have to check this normally */
        DEBUGF("disk_buf_getbuffer_l2: l2 = NULL!\n");
    }

    if (size > DISK_BUF_L2_CACHE_SIZE)
    {
        /* Asking for too much; just go through L1 */
        return disk_buf_getbuffer(size, pp, NULL, NULL);
    }

    offs = disk_buf.offset; /* Other calls keep this within bounds */
    l2_addr = l2->addr;

    if (offs >= l2_addr && offs < l2_addr + DISK_BUF_L2_CACHE_SIZE)
    {
        /* Data is in the local buffer */
        offs &= DISK_BUF_L2_CACHE_MASK;

        *pp = l2->data + offs;
        if (offs + size > l2->size)
            size = l2->size - offs; /* Keep size within file limits */

        return size;
    }

    /* Have to probe main buffer */
    l2_addr = offs & ~DISK_BUF_L2_CACHE_MASK;
    l2_size = DISK_BUF_L2_CACHE_SIZE*2; /* 2nd half is a guard buffer */

    disk_buf_lock();

    l2_size = disk_buf_probe(l2_addr, l2_size, &l2_p);

    if (l2_size != (size_t)-1)
    {
        rb->memcpy(l2->data, l2_p, l2_size);

        l2->addr = l2_addr;
        l2->size = l2_size;
        offs -= l2_addr;

        *pp = l2->data + offs;
        if (offs + size > l2->size)
            size = l2->size - offs; /* Keep size within file limits */
    }
    else
    {
        size = -1;
    }

    disk_buf_unlock();

    return size;
}


/* Read size bytes of data into a buffer - advances the buffer pointer
 * and returns the real size read. */
ssize_t disk_buf_read(void *buffer, size_t size)
{
    uint8_t *p;

    disk_buf_lock();

    size = disk_buf_probe(disk_buf.offset, size, PUN_PTR(void **, &p));

    if (size != (size_t)-1)
    {
        if (p + size > disk_buf.end + DISK_GUARDBUF_SIZE)
        {
            /* Read wraps */
            size_t nowrap = (disk_buf.end + DISK_GUARDBUF_SIZE) - p;
            rb->memcpy(buffer, p, nowrap);
            rb->memcpy(buffer + nowrap, disk_buf.start + DISK_GUARDBUF_SIZE,
                       size - nowrap);
        }
        else
        {
            /* Read wasn't wrapped or guardbuffer holds it */
            rb->memcpy(buffer, p, size);
        }

        disk_buf.offset += size;
    }

    disk_buf_unlock();

    return size;
}

ssize_t disk_buf_lseek(off_t offset, int whence)
{
    disk_buf_lock();

    /* The offset returned is the result of the current thread's action and
     * may be invalidated so a local result is returned and not the value
     * of disk_buf.offset directly */
    switch (whence)
    {
    case SEEK_SET:
        /* offset is just the offset */
        break;
    case SEEK_CUR:
        offset += disk_buf.offset;
        break;
    case SEEK_END:
        offset = disk_buf.filesize + offset;
        break;
    default:
        disk_buf_unlock();
        return -2; /* Invalid request */
    }

    if (offset < 0 || offset > disk_buf.filesize)
    {
        offset = -3;
    }
    else
    {
        disk_buf.offset = offset;
    }

    disk_buf_unlock();

    return offset;
}

/* Prepare the buffer to enter the streaming state. Evaluates the available
 * streaming window. */
ssize_t disk_buf_prepare_streaming(off_t pos, size_t len)
{
    disk_buf_lock();

    if (pos < 0)
        pos = 0;
    else if (pos > disk_buf.filesize)
        pos = disk_buf.filesize;

    DEBUGF("prepare streaming:\n  pos:%ld len:%lu\n", pos, (unsigned long)len);

    pos = disk_buf_lseek(pos, SEEK_SET);
    len = disk_buf_probe(pos, len, NULL);

    DEBUGF("  probe done: pos:%ld len:%lu\n", pos, (unsigned long)len);

    len = disk_buf_send_msg(STREAM_RESET, pos);

    disk_buf_unlock();

    return len;
}

/* Set the streaming window to an arbitrary position within the file. Makes no
 * probes to validate data. Use after calling another function to cause data
 * to be cached and correct values are known. */
ssize_t disk_buf_set_streaming_window(off_t left, off_t right)
{
    ssize_t len;

    disk_buf_lock();

    if (left < 0)
        left = 0;
    else if (left > disk_buf.filesize)
        left = disk_buf.filesize;

    if (left > right)
        right = left;

    if (right > disk_buf.filesize)
        right = disk_buf.filesize;

    disk_buf.win_left = left;
    disk_buf.win_right = right;
    disk_buf.tail = disk_buf.start + ((right + DISK_BUF_PAGE_SIZE-1) &
                                       ~DISK_BUF_PAGE_MASK) % disk_buf.size;

    len = disk_buf.win_right - disk_buf.win_left;

    disk_buf_unlock();

    return len;
}

void * disk_buf_offset2ptr(off_t offset)
{
    if (offset < 0)
        offset = 0;
    else if (offset > disk_buf.filesize)
        offset = disk_buf.filesize;

    return disk_buf.start + (offset % disk_buf.size);
}

void disk_buf_close(void)
{
    disk_buf_lock();

    if (disk_buf.in_file >= 0)
    {
        rb->close(disk_buf.in_file);
        disk_buf.in_file = -1;

        /* Invalidate entire cache */
        rb->memset(disk_buf.cache, 0xff,
                   disk_buf.pgcount*sizeof (*disk_buf.cache));
        disk_buf.file_pages = 0;
        disk_buf.filesize = 0;
        disk_buf.offset = 0;
    }

    disk_buf_unlock();
}

int disk_buf_open(const char *filename)
{
    int fd;

    disk_buf_lock();

    disk_buf_close();

    fd = rb->open(filename, O_RDONLY);

    if (fd >= 0)
    {
        ssize_t filesize = rb->filesize(fd);

        if (filesize <= 0)
        {
            rb->close(disk_buf.in_file);
        }
        else
        {
            disk_buf.filesize = filesize;
            /* Number of file pages rounded up toward +inf */
            disk_buf.file_pages = ((size_t)filesize + DISK_BUF_PAGE_SIZE-1)
                                    / DISK_BUF_PAGE_SIZE;
            disk_buf.in_file = fd;
        }
    }

    disk_buf_unlock();

    return fd;
}

intptr_t disk_buf_send_msg(long id, intptr_t data)
{
    return rb->queue_send(disk_buf.q, id, data);
}

void disk_buf_post_msg(long id, intptr_t data)
{
    rb->queue_post(disk_buf.q, id, data);
}

void disk_buf_reply_msg(intptr_t retval)
{
    rb->queue_reply(disk_buf.q, retval);
}

bool disk_buf_init(void)
{
    disk_buf.thread = 0;

    rb->mutex_init(&disk_buf_mtx);

    disk_buf.q = &disk_buf_queue;
    rb->queue_init(disk_buf.q, false);

    disk_buf.state  = TSTATE_EOS;
    disk_buf.status = STREAM_STOPPED;

    disk_buf.in_file = -1;
    disk_buf.filesize = 0;
    disk_buf.win_left = 0;
    disk_buf.win_right = 0;
    disk_buf.time_last = 0;
    disk_buf.pos_last = 0;
    disk_buf.low_wm = DISK_BUF_LOW_WATERMARK;

    disk_buf.start = mpeg_malloc_all((size_t*)&disk_buf.size, MPEG_ALLOC_DISKBUF);
    if (disk_buf.start == NULL)
        return false;

#if NUM_CORES > 1
    CACHEALIGN_BUFFER(disk_buf.start, disk_buf.size);
    disk_buf.start = UNCACHED_ADDR(disk_buf.start);
#endif
    disk_buf.size -= DISK_GUARDBUF_SIZE;
    disk_buf.pgcount = disk_buf.size / DISK_BUF_PAGE_SIZE;

    /* Fit it as tightly as possible */
    while (disk_buf.pgcount*(sizeof (*disk_buf.cache) + DISK_BUF_PAGE_SIZE)
            > (size_t)disk_buf.size)
    {
        disk_buf.pgcount--;
    }

    disk_buf.cache = (typeof (disk_buf.cache))disk_buf.start;
    disk_buf.start += sizeof (*disk_buf.cache)*disk_buf.pgcount;
    disk_buf.size = disk_buf.pgcount*DISK_BUF_PAGE_SIZE;
    disk_buf.end = disk_buf.start + disk_buf.size;
    disk_buf.tail = disk_buf.start;

    DEBUGF("disk_buf info:\n"
           "  page count: %d\n"
           "  size:       %ld\n",
           disk_buf.pgcount, (long)disk_buf.size);

    rb->memset(disk_buf.cache, 0xff,
               disk_buf.pgcount*sizeof (*disk_buf.cache));

    disk_buf.thread = rb->create_thread(
        disk_buf_thread, disk_buf_stack, sizeof(disk_buf_stack), 0,
        "mpgbuffer" IF_PRIO(, PRIORITY_BUFFERING) IF_COP(, CPU));

    rb->queue_enable_queue_send(disk_buf.q, &disk_buf_queue_send,
                                disk_buf.thread);

    if (disk_buf.thread == 0)
        return false;

    /* Wait for thread to initialize */
    disk_buf_send_msg(STREAM_NULL, 0);

    return true;
}

void disk_buf_exit(void)
{
    if (disk_buf.thread != 0)
    {
        rb->queue_post(disk_buf.q, STREAM_QUIT, 0);
        rb->thread_wait(disk_buf.thread);
        disk_buf.thread = 0;
    }
}