rockbox/apps/iap/iap-core.c

1423 lines
36 KiB
C
Raw Normal View History

/***************************************************************************
* __________ __ ___.
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
* \/ \/ \/ \/ \/
* $Id$
*
* Copyright (C) 2002 by Alan Korr & Nick Robinson
*
* All files in this archive are subject to the GNU General Public License.
* See the file COPYING in the source tree root for full license agreement.
*
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
* KIND, either express or implied.
*
****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include "panic.h"
#include "iap-core.h"
#include "iap-lingo.h"
#include "button.h"
#include "config.h"
#include "cpu.h"
#include "system.h"
#include "kernel.h"
#include "thread.h"
#include "serial.h"
#include "appevents.h"
#include "core_alloc.h"
#include "playlist.h"
#include "playback.h"
#include "audio.h"
#include "settings.h"
#include "metadata.h"
#include "sound.h"
#include "action.h"
#include "powermgmt.h"
#include "usb.h"
#include "tuner.h"
#if CONFIG_TUNER
#include "ipod_remote_tuner.h"
#endif
/* MS_TO_TICKS converts a milisecond time period into the
* corresponding amount of ticks. If the time period cannot
* be accurately measured in ticks it will round up.
*/
#if (HZ>1000)
#error "HZ is >1000, please fix MS_TO_TICKS"
#endif
#define MS_PER_HZ (1000/HZ)
#define MS_TO_TICKS(x) (((x)+MS_PER_HZ-1)/MS_PER_HZ)
/* IAP specifies a timeout of 25ms for traffic from a device to the iPod.
* Depending on HZ this cannot be accurately measured. Find out the next
* best thing.
*/
#define IAP_PKT_TIMEOUT (MS_TO_TICKS(25))
/* Events in the iap_queue */
#define IAP_EV_TICK (1) /* The regular task timeout */
#define IAP_EV_MSG_RCVD (2) /* A complete message has been received from the device */
#define IAP_EV_MALLOC (3) /* Allocate memory for the RX/TX buffers */
static bool iap_started = false;
static bool iap_setupflag = false, iap_running = false;
/* This is set to true if a SYS_POWEROFF message is received,
* signalling impending power off
*/
static bool iap_shutdown = false;
static struct timeout iap_task_tmo;
unsigned long iap_remotebtn = 0;
/* Used to make sure a button press is delivered to the processing
* backend. While this is !0, no new incoming messasges are processed.
* Counted down by remote_control_rx()
*/
int iap_repeatbtn = 0;
/* Used to time out button down events in case we miss the button up event
* from the device somehow.
* If a device sends a button down event it's required to repeat that event
* every 30 to 100ms as long as the button is pressed, and send an explicit
* button up event if the button is released.
* In case the button up event is lost any down events will time out after
* ~200ms.
* iap_periodic() will count down this variable and reset all buttons if
* it reaches 0
*/
unsigned int iap_timeoutbtn = 0;
bool iap_btnrepeat = false, iap_btnshuffle = false;
static long thread_stack[(DEFAULT_STACK_SIZE*6)/sizeof(long)];
static struct event_queue iap_queue;
/* These are pointer used to manage a dynamically allocated buffer which
* will hold both the RX and TX side of things.
*
* iap_buffer_handle is the handle returned from core_alloc()
* iap_buffers points to the start of the complete buffer
*
* The buffer is partitioned as follows:
* - TX_BUFLEN+6 bytes for the TX buffer
* The 6 extra bytes are for the sync byte, the SOP byte, the length indicators
* (3 bytes) and the checksum byte.
* iap_txstart points to the beginning of the TX buffer
* iap_txpayload points to the beginning of the payload portion of the TX buffer
* iap_txnext points to the position where the next byte will be placed
*
* - RX_BUFLEN+2 bytes for the RX buffer
* The RX buffer can hold multiple packets at once, up to it's
* maximum capacity. Every packet consists of a two byte length
* indicator followed by the actual payload. The length indicator
* is two bytes for every length, even for packets with a length <256
* bytes.
*
* Once a packet has been processed from the RX buffer the rest
* of the buffer (and the pointers below) are shifted to the front
* so that the next packet again starts at the beginning of the
* buffer. This happens with interrupts disabled, to prevent
* writing into the buffer during the move.
*
* iap_rxstart points to the beginning of the RX buffer
* iap_rxpayload starts to the beginning of the currently recieved
* packet
* iap_rxnext points to the position where the next incoming byte
* will be placed
* iap_rxlen is not a pointer, but an indicator of the free
* space left in the RX buffer.
*
* The RX buffer is placed behind the TX buffer so that an eventual TX
* buffer overflow has some place to spill into where it will not cause
* immediate damage. See the comments for IAP_TX_* and iap_send_tx()
*/
#define IAP_MALLOC_SIZE (TX_BUFLEN+6+RX_BUFLEN+2)
#ifdef IAP_MALLOC_DYNAMIC
static int iap_buffer_handle;
#endif
static unsigned char* iap_buffers;
static unsigned char* iap_rxstart;
static unsigned char* iap_rxpayload;
static unsigned char* iap_rxnext;
static uint32_t iap_rxlen;
static unsigned char* iap_txstart;
unsigned char* iap_txpayload;
unsigned char* iap_txnext;
/* The versions of the various Lingoes we support. A major version
* of 0 means unsupported
*/
unsigned char lingo_versions[32][2] = {
{1, 9}, /* General lingo, 0x00 */
#ifdef HAVE_LINE_REC
{1, 1}, /* Microphone lingo, 0x01 */
#else
{0, 0}, /* Microphone lingo, 0x01, disabled */
#endif
{1, 2}, /* Simple remote lingo, 0x02 */
{1, 5}, /* Display remote lingo, 0x03 */
{1, 12}, /* Extended Interface lingo, 0x04 */
{1, 1}, /* RF/BT Transmitter lingo, 0x05 */
{0, 0}, /* USB Host lingo, 0x06, disabled */
#if CONFIG_TUNER
{1, 0}, /* RF Receiver lingo, 0x07 */
#else
{0, 0}, /* RF Receiver lingo, 0x07 disabled */
#endif
{} /* every other lingo, disabled */
};
/* states of the iap de-framing state machine */
enum fsm_state {
ST_SYNC, /* wait for 0xFF sync byte */
ST_SOF, /* wait for 0x55 start-of-frame byte */
ST_LEN, /* receive length byte (small packet) */
ST_LENH, /* receive length high byte (large packet) */
ST_LENL, /* receive length low byte (large packet) */
ST_DATA, /* receive data */
ST_CHECK /* verify checksum */
};
static struct state_t {
enum fsm_state state; /* current fsm state */
unsigned int len; /* payload data length */
unsigned int check; /* running checksum over [len,payload,check] */
unsigned int count; /* playload bytes counter */
} frame_state = {
.state = ST_SYNC
};
enum interface_state interface_state = IST_STANDARD;
struct device_t device;
#ifdef IAP_MALLOC_DYNAMIC
static int iap_move_callback(int handle, void* current, void* new);
static struct buflib_callbacks iap_buflib_callbacks = {
iap_move_callback,
NULL
};
#endif
static void iap_malloc(void);
void put_u16(unsigned char *buf, const uint16_t data)
{
buf[0] = (data >> 8) & 0xFF;
buf[1] = (data >> 0) & 0xFF;
}
void put_u32(unsigned char *buf, const uint32_t data)
{
buf[0] = (data >> 24) & 0xFF;
buf[1] = (data >> 16) & 0xFF;
buf[2] = (data >> 8) & 0xFF;
buf[3] = (data >> 0) & 0xFF;
}
uint32_t get_u32(const unsigned char *buf)
{
return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
}
uint16_t get_u16(const unsigned char *buf)
{
return (buf[0] << 8) | buf[1];
}
#if defined(LOGF_ENABLE) && defined(ROCKBOX_HAS_LOGF)
/* Convert a buffer into a printable string, perl style
* buf contains the data to be converted, len is the length
* of the buffer.
*
* This will convert at most 1024 bytes from buf
*/
static char* hexstring(const unsigned char *buf, unsigned int len) {
static char hexbuf[4097];
unsigned int l;
const unsigned char* p;
unsigned char* out;
unsigned char h[] = {'0', '1', '2', '3', '4', '5', '6', '7',
'8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
if (len > 1024) {
l = 1024;
} else {
l = len;
}
p = buf;
out = hexbuf;
do {
*out++ = h[(*p)>>4];
*out++ = h[*p & 0x0F];
} while(--l && p++);
*out = 0x00;
return hexbuf;
}
#endif
void iap_tx_strlcpy(const unsigned char *str)
{
ptrdiff_t txfree;
int r;
txfree = TX_BUFLEN - (iap_txnext - iap_txstart);
r = strlcpy(iap_txnext, str, txfree);
if (r < txfree)
{
/* No truncation occured
* Account for the terminating \0
*/
iap_txnext += (r+1);
} else {
/* Truncation occured, the TX buffer is now full. */
iap_txnext = iap_txstart + TX_BUFLEN;
}
}
void iap_reset_auth(struct auth_t* auth)
{
auth->state = AUST_NONE;
auth->max_section = 0;
auth->next_section = 0;
}
void iap_reset_device(struct device_t* device)
{
iap_reset_auth(&(device->auth));
device->lingoes = 0;
device->notifications = 0;
device->changed_notifications = 0;
device->do_notify = false;
device->do_power_notify = false;
device->accinfo = ACCST_NONE;
device->capabilities = 0;
device->capabilities_queried = 0;
}
static int iap_task(struct timeout *tmo)
{
(void) tmo;
queue_post(&iap_queue, IAP_EV_TICK, 0);
return MS_TO_TICKS(100);
}
void iap_set_remote_volume(void)
{
IAP_TX_INIT(0x03, 0x0D);
IAP_TX_PUT(0x04);
IAP_TX_PUT(0x00);
IAP_TX_PUT(0xFF & (int)((global_settings.volume + 90) * 2.65625));
iap_send_tx();
}
/* This thread is waiting for events posted to iap_queue and calls
* the appropriate subroutines in response
*/
static void iap_thread(void)
{
struct queue_event ev;
while(1) {
queue_wait(&iap_queue, &ev);
switch (ev.id)
{
/* Handle the regular 100ms tick used for driving the
* authentication state machine and notifications
*/
case IAP_EV_TICK:
{
iap_periodic();
break;
}
/* Handle a newly received message from the device */
case IAP_EV_MSG_RCVD:
{
iap_handlepkt();
break;
}
/* Handle memory allocation. This is used only once, during
* startup
*/
case IAP_EV_MALLOC:
{
iap_malloc();
break;
}
/* Handle poweroff message */
case SYS_POWEROFF:
{
iap_shutdown = true;
break;
}
/* Ack USB thread */
case SYS_USB_CONNECTED:
{
usb_acknowledge(SYS_USB_CONNECTED_ACK);
break;
}
}
}
}
/* called by playback when the next track starts */
static void iap_track_changed(unsigned short id, void *ignored)
{
(void)id;
(void)ignored;
if ((interface_state == IST_EXTENDED) && device.do_notify) {
long playlist_pos = playlist_next(0);
playlist_pos -= playlist_get_first_index(NULL);
if(playlist_pos < 0)
playlist_pos += playlist_amount();
IAP_TX_INIT4(0x04, 0x0027);
IAP_TX_PUT(0x01);
IAP_TX_PUT_U32(playlist_pos);
iap_send_tx();
return;
}
}
/* Do general setup of the needed infrastructure.
*
* Please note that a lot of additional work is done by iap_start()
*/
void iap_setup(const int ratenum)
{
iap_bitrate_set(ratenum);
iap_remotebtn = BUTTON_NONE;
iap_setupflag = true;
iap_started = false;
iap_running = false;
}
/* Actually bring up the message queue, message handler thread and
* notification timer
*
* NOTE: This is running in interrupt context
*/
static void iap_start(void)
{
unsigned int tid;
if (iap_started)
return;
iap_reset_device(&device);
queue_init(&iap_queue, true);
tid = create_thread(iap_thread, thread_stack, sizeof(thread_stack),
0, "iap"
IF_PRIO(, PRIORITY_SYSTEM)
IF_COP(, CPU));
if (!tid)
panicf("Could not create iap thread");
timeout_register(&iap_task_tmo, iap_task, MS_TO_TICKS(100), (intptr_t)NULL);
add_event(PLAYBACK_EVENT_TRACK_CHANGE, iap_track_changed);
/* Since we cannot allocate memory while in interrupt context
* post a message to our own queue to get that done
*/
queue_post(&iap_queue, IAP_EV_MALLOC, 0);
iap_started = true;
}
static void iap_malloc(void)
{
#ifndef IAP_MALLOC_DYNAMIC
static unsigned char serbuf[IAP_MALLOC_SIZE];
#endif
if (iap_running)
return;
#ifdef IAP_MALLOC_DYNAMIC
iap_buffer_handle = core_alloc_ex("iap", IAP_MALLOC_SIZE, &iap_buflib_callbacks);
if (iap_buffer_handle < 0)
panicf("Could not allocate buffer memory");
iap_buffers = core_get_data(iap_buffer_handle);
#else
iap_buffers = serbuf;
#endif
iap_txstart = iap_buffers;
iap_txpayload = iap_txstart+5;
iap_txnext = iap_txpayload;
iap_rxstart = iap_buffers+(TX_BUFLEN+6);
iap_rxpayload = iap_rxstart;
iap_rxnext = iap_rxpayload;
iap_rxlen = RX_BUFLEN+2;
iap_running = true;
}
void iap_bitrate_set(const int ratenum)
{
switch(ratenum)
{
case 0:
serial_bitrate(0);
break;
case 1:
serial_bitrate(9600);
break;
case 2:
serial_bitrate(19200);
break;
case 3:
serial_bitrate(38400);
break;
case 4:
serial_bitrate(57600);
break;
}
}
/* Message format:
0xff
0x55
length
mode
command (2 bytes)
parameters (0-n bytes)
checksum (length+mode+parameters+checksum == 0)
*/
/* Send the current content of the TX buffer.
* This will check for TX buffer overflow and panic, but it might
* be too late by then (although one would have to overflow the complete
* RX buffer as well)
*/
void iap_send_tx(void)
{
int i, chksum;
ptrdiff_t txlen;
unsigned char* txstart;
txlen = iap_txnext - iap_txpayload;
if (txlen <= 0)
return;
if (txlen > TX_BUFLEN)
panicf("IAP: TX buffer overflow");
if (txlen < 256)
{
/* Short packet */
txstart = iap_txstart+2;
*(txstart+2) = txlen;
chksum = txlen;
} else {
/* Long packet */
txstart = iap_txstart;
*(txstart+2) = 0x00;
*(txstart+3) = (txlen >> 8) & 0xFF;
*(txstart+4) = (txlen) & 0xFF;
chksum = *(txstart+3) + *(txstart+4);
}
*(txstart) = 0xFF;
*(txstart+1) = 0x55;
for (i=0; i<txlen; i++)
{
chksum += iap_txpayload[i];
}
*(iap_txnext) = 0x100 - (chksum & 0xFF);
#if defined(LOGF_ENABLE) && defined(ROCKBOX_HAS_LOGF)
logf("T: %s", hexstring(txstart+3, (iap_txnext - txstart)-3));
#endif
for (i=0; i <= (iap_txnext - txstart); i++)
{
while(!tx_rdy()) ;
tx_writec(txstart[i]);
}
}
/* This is just a compatibility wrapper around the new TX buffer
* infrastructure
*/
void iap_send_pkt(const unsigned char * data, const int len)
{
if (!iap_running)
return;
iap_txnext = iap_txpayload;
IAP_TX_PUT_DATA(data, len);
iap_send_tx();
}
bool iap_getc(const unsigned char x)
{
struct state_t *s = &frame_state;
static long pkt_timeout;
if (!iap_setupflag)
return false;
/* Check the time since the last packet arrived. */
if ((s->state != ST_SYNC) && TIME_AFTER(current_tick, pkt_timeout)) {
/* Packet timeouts only make sense while not waiting for the
* sync byte */
s->state = ST_SYNC;
return iap_getc(x);
}
/* run state machine to detect and extract a valid frame */
switch (s->state) {
case ST_SYNC:
if (x == 0xFF) {
/* The IAP infrastructure is started by the first received sync
* byte. It takes a while to spin up, so do not advance the state
* machine until it has started.
*/
if (!iap_running)
{
iap_start();
break;
}
iap_rxnext = iap_rxpayload;
s->state = ST_SOF;
}
break;
case ST_SOF:
if (x == 0x55) {
/* received a valid sync/SOF pair */
s->state = ST_LEN;
} else {
s->state = ST_SYNC;
return iap_getc(x);
}
break;
case ST_LEN:
s->check = x;
s->count = 0;
if (x == 0) {
/* large packet */
s->state = ST_LENH;
} else {
/* small packet */
if (x > (iap_rxlen-2))
{
/* Packet too long for buffer */
s->state = ST_SYNC;
break;
}
s->len = x;
s->state = ST_DATA;
put_u16(iap_rxnext, s->len);
iap_rxnext += 2;
}
break;
case ST_LENH:
s->check += x;
s->len = x << 8;
s->state = ST_LENL;
break;
case ST_LENL:
s->check += x;
s->len += x;
if ((s->len == 0) || (s->len > (iap_rxlen-2))) {
/* invalid length */
s->state = ST_SYNC;
break;
} else {
s->state = ST_DATA;
put_u16(iap_rxnext, s->len);
iap_rxnext += 2;
}
break;
case ST_DATA:
s->check += x;
*(iap_rxnext++) = x;
s->count += 1;
if (s->count == s->len) {
s->state = ST_CHECK;
}
break;
case ST_CHECK:
s->check += x;
if ((s->check & 0xFF) == 0) {
/* done, received a valid frame */
iap_rxpayload = iap_rxnext;
queue_post(&iap_queue, IAP_EV_MSG_RCVD, 0);
} else {
/* Invalid frame */
}
s->state = ST_SYNC;
break;
default:
#ifdef LOGF_ENABLE
logf("Unhandled iap state %d", (int) s->state);
#else
panicf("Unhandled iap state %d", (int) s->state);
#endif
break;
}
pkt_timeout = current_tick + IAP_PKT_TIMEOUT;
/* return true while still hunting for the sync and start-of-frame byte */
return (s->state == ST_SYNC) || (s->state == ST_SOF);
}
void iap_get_trackinfo(const unsigned int track, struct mp3entry* id3)
{
int tracknum;
int fd;
struct playlist_track_info info;
tracknum = track;
tracknum += playlist_get_first_index(NULL);
if(tracknum >= playlist_amount())
tracknum -= playlist_amount();
/* If the tracknumber is not the current one,
read id3 from disk */
if(playlist_next(0) != tracknum)
{
playlist_get_track_info(NULL, tracknum, &info);
fd = open(info.filename, O_RDONLY);
memset(id3, 0, sizeof(*id3));
get_metadata(id3, fd, info.filename);
close(fd);
} else {
memcpy(id3, audio_current_track(), sizeof(*id3));
}
}
uint32_t iap_get_trackpos(void)
{
struct mp3entry *id3 = audio_current_track();
return id3->elapsed;
}
uint32_t iap_get_trackindex(void)
{
struct playlist_info* playlist = playlist_get_current();
return (playlist->index - playlist->first_index);
}
void iap_periodic(void)
{
static int count;
if(!iap_setupflag) return;
/* Handle pending authentication tasks */
switch (device.auth.state)
{
case AUST_INIT:
{
/* Send out GetDevAuthenticationInfo */
IAP_TX_INIT(0x00, 0x14);
iap_send_tx();
device.auth.state = AUST_CERTREQ;
break;
}
case AUST_CERTDONE:
{
/* Send out GetDevAuthenticationSignature, with
* 20 bytes of challenge and a retry counter of 1.
* Since we do not really care about the content of the
* challenge we just use the first 20 bytes of whatever
* is in the RX buffer right now.
*/
IAP_TX_INIT(0x00, 0x17);
IAP_TX_PUT_DATA(iap_rxstart,
(device.auth.version == 0x100) ? 16 : 20);
IAP_TX_PUT(0x01);
iap_send_tx();
device.auth.state = AUST_CHASENT;
break;
}
default:
{
break;
}
}
/* Time out button down events */
if (iap_timeoutbtn)
iap_timeoutbtn -= 1;
if (!iap_timeoutbtn)
{
iap_remotebtn = BUTTON_NONE;
iap_repeatbtn = 0;
iap_btnshuffle = false;
iap_btnrepeat = false;
}
/* Handle power down messages. */
if (iap_shutdown && device.do_power_notify)
{
/* NotifyiPodStateChange */
IAP_TX_INIT(0x00, 0x23);
IAP_TX_PUT(0x01);
iap_send_tx();
/* No further actions, we're going down */
iap_reset_device(&device);
return;
}
/* Handle GetAccessoryInfo messages */
if (device.accinfo == ACCST_INIT)
{
/* GetAccessoryInfo */
IAP_TX_INIT(0x00, 0x27);
IAP_TX_PUT(0x00);
iap_send_tx();
device.accinfo = ACCST_SENT;
}
/* Do not send requests for device information while
* an authentication is still running, this seems to
* confuse some devices
*/
if (!DEVICE_AUTH_RUNNING && (device.accinfo == ACCST_DATA))
{
int first_set;
/* Find the first bit set in the capabilities field,
* ignoring those we already asked for
*/
first_set = find_first_set_bit(device.capabilities & (~device.capabilities_queried));
if (first_set != 32)
{
/* Add bit to queried cababilities */
device.capabilities_queried |= BIT_N(first_set);
switch (first_set)
{
/* Name */
case 0x01:
/* Firmware version */
case 0x04:
/* Hardware version */
case 0x05:
/* Manufacturer */
case 0x06:
/* Model number */
case 0x07:
/* Serial number */
case 0x08:
/* Maximum payload size */
case 0x09:
{
IAP_TX_INIT(0x00, 0x27);
IAP_TX_PUT(first_set);
iap_send_tx();
break;
}
/* Minimum supported iPod firmware version */
case 0x02:
{
IAP_TX_INIT(0x00, 0x27);
IAP_TX_PUT(2);
IAP_TX_PUT_U32(IAP_IPOD_MODEL);
IAP_TX_PUT(IAP_IPOD_FIRMWARE_MAJOR);
IAP_TX_PUT(IAP_IPOD_FIRMWARE_MINOR);
IAP_TX_PUT(IAP_IPOD_FIRMWARE_REV);
iap_send_tx();
break;
}
/* Minimum supported lingo version. Queries Lingo 0 */
case 0x03:
{
IAP_TX_INIT(0x00, 0x27);
IAP_TX_PUT(3);
IAP_TX_PUT(0);
iap_send_tx();
break;
}
}
device.accinfo = ACCST_SENT;
}
}
if (!device.do_notify) return;
if ((device.notifications == 0) && (interface_state != IST_EXTENDED)) return;
/* Volume change notifications are sent every 100ms */
if (device.notifications & (BIT_N(4) | BIT_N(16))) {
/* Currently we do not track volume changes for BIT_N(16),
*
*/
IAP_TX_INIT(0x03, 0x09);
IAP_TX_PUT(0x04);
IAP_TX_PUT(0x00);
IAP_TX_PUT(0xFF &(int)((global_settings.volume + 90) * 2.65625));
device.changed_notifications |= BIT_N(4);
iap_send_tx();
}
/* All other events are sent every 500ms */
count += 1;
if (count < 5) return;
count = 0;
/* RemoteEventNotification */
/* Mode 04 PlayStatusChangeNotification */
/* Are we in Extended Mode */
if (interface_state == IST_EXTENDED) {
/* Return Track Position */
struct mp3entry *id3 = audio_current_track();
unsigned long time_elapsed = id3->elapsed;
IAP_TX_INIT4(0x04, 0x0027);
IAP_TX_PUT(0x04);
IAP_TX_PUT_U32(time_elapsed);
iap_send_tx();
}
/* Track position (ms) or Track position (s) */
if (device.notifications & (BIT_N(0) | BIT_N(15)))
{
uint32_t t;
uint16_t ts;
bool changed;
t = iap_get_trackpos();
ts = (t / 1000) & 0xFFFF;
if ((device.notifications & BIT_N(0)) && (device.trackpos_ms != t))
{
IAP_TX_INIT(0x03, 0x09);
IAP_TX_PUT(0x00);
IAP_TX_PUT_U32(t);
device.changed_notifications |= BIT_N(0);
changed = true;
iap_send_tx();
}
if ((device.notifications & BIT_N(15)) && (device.trackpos_s != ts)) {
IAP_TX_INIT(0x03, 0x09);
IAP_TX_PUT(0x0F);
IAP_TX_PUT_U16(ts);
device.changed_notifications |= BIT_N(15);
changed = true;
iap_send_tx();
}
if (changed)
{
device.trackpos_ms = t;
device.trackpos_s = ts;
}
}
/* Track index */
if (device.notifications & BIT_N(1))
{
uint32_t index;
index = iap_get_trackindex();
if (device.track_index != index) {
IAP_TX_INIT(0x03, 0x09);
IAP_TX_PUT(0x01);
IAP_TX_PUT_U32(index);
device.changed_notifications |= BIT_N(1);
iap_send_tx();
device.track_index = index;
}
}
/* Chapter index */
if (device.notifications & BIT_N(2))
{
uint32_t index;
index = iap_get_trackindex();
if (device.track_index != index)
{
IAP_TX_INIT(0x03, 0x09);
IAP_TX_PUT(0x02);
IAP_TX_PUT_U32(index);
IAP_TX_PUT_U16(0);
IAP_TX_PUT_U16(0xFFFF);
device.changed_notifications |= BIT_N(2);
iap_send_tx();
device.track_index = index;
}
}
/* Play status */
if (device.notifications & BIT_N(3))
{
unsigned char play_status;
play_status = audio_status();
if (device.play_status != play_status)
{
/* If play_status = PAUSE/STOP we should mute else
* we should unmute
* 0 = Stopped
* 1 = Playing
* 2 = Pause
* 3 = Play/Pause
*/
IAP_TX_INIT(0x03, 0x09);
IAP_TX_PUT(0x03);
if (play_status & AUDIO_STATUS_PLAY) {
/* Playing or paused */
if (play_status & AUDIO_STATUS_PAUSE) {
/* Paused */
IAP_TX_PUT(0x02);
} else {
/* Playing */
IAP_TX_PUT(0x01);
}
} else {
IAP_TX_PUT(0x00);
}
device.changed_notifications |= BIT_N(3);
iap_send_tx();
device.play_status = play_status;
if (play_status != 1) {
/* Not Playing */
audio_pause();
#if CONFIG_TUNER
if (radio_present==1) {
tuner_set(RADIO_MUTE,1);
}
#endif
} else {
/* Playing */
audio_resume();
#if CONFIG_TUNER
if (radio_present==1) {
tuner_set(RADIO_MUTE,0);
}
#endif
}
}
}
/* Power/Battery */
if (device.notifications & BIT_N(5))
{
unsigned char power_state;
unsigned char battery_l;
power_state = charger_input_state;
battery_l = battery_level();
if ((device.power_state != power_state) || (device.battery_level != battery_l))
{
IAP_TX_INIT(0x03, 0x09);
IAP_TX_PUT(0x05);
iap_fill_power_state();
device.changed_notifications |= BIT_N(5);
iap_send_tx();
device.power_state = power_state;
device.battery_level = battery_l;
}
}
/* Equalizer state
* This is not handled yet.
*
* TODO: Fix equalizer handling
*/
/* Shuffle */
if (device.notifications & BIT_N(7))
{
unsigned char shuffle;
shuffle = global_settings.playlist_shuffle;
if (device.shuffle != shuffle)
{
IAP_TX_INIT(0x03, 0x09);
IAP_TX_PUT(0x07);
IAP_TX_PUT(shuffle?0x01:0x00);
device.changed_notifications |= BIT_N(7);
iap_send_tx();
device.shuffle = shuffle;
}
}
/* Repeat */
if (device.notifications & BIT_N(8))
{
unsigned char repeat;
repeat = global_settings.repeat_mode;
if (device.repeat != repeat)
{
IAP_TX_INIT(0x03, 0x09);
IAP_TX_PUT(0x08);
switch (repeat)
{
case REPEAT_OFF:
{
IAP_TX_PUT(0x00);
break;
}
case REPEAT_ONE:
{
IAP_TX_PUT(0x01);
break;
}
case REPEAT_ALL:
{
IAP_TX_PUT(0x02);
break;
}
}
device.changed_notifications |= BIT_N(8);
iap_send_tx();
device.repeat = repeat;
}
}
/* Date/Time */
if (device.notifications & BIT_N(9))
{
struct tm* tm;
tm = get_time();
if (memcmp(tm, &(device.datetime), sizeof(struct tm)))
{
IAP_TX_INIT(0x03, 0x09);
IAP_TX_PUT(0x09);
IAP_TX_PUT_U16(tm->tm_year);
/* Month */
IAP_TX_PUT(tm->tm_mon+1);
/* Day */
IAP_TX_PUT(tm->tm_mday);
/* Hour */
IAP_TX_PUT(tm->tm_hour);
/* Minute */
IAP_TX_PUT(tm->tm_min);
device.changed_notifications |= BIT_N(9);
iap_send_tx();
memcpy(&(device.datetime), tm, sizeof(struct tm));
}
}
/* Alarm
* This is not supported yet.
*
* TODO: Fix alarm handling
*/
/* Backlight
* This is not supported yet.
*
* TODO: Fix backlight handling
*/
/* Hold switch */
if (device.notifications & BIT_N(0x0C))
{
unsigned char hold;
hold = button_hold();
if (device.hold != hold) {
IAP_TX_INIT(0x03, 0x09);
IAP_TX_PUT(0x0C);
IAP_TX_PUT(hold?0x01:0x00);
device.changed_notifications |= BIT_N(0x0C);
iap_send_tx();
device.hold = hold;
}
}
/* Sound check
* This is not supported yet.
*
* TODO: Fix sound check handling
*/
/* Audiobook check
* This is not supported yet.
*
* TODO: Fix audiobook handling
*/
}
/* Change the current interface state.
* On a change from IST_EXTENDED to IST_STANDARD, or from IST_STANDARD
* to IST_EXTENDED, pause playback, if playing
*/
void iap_interface_state_change(const enum interface_state new)
{
if (((interface_state == IST_EXTENDED) && (new == IST_STANDARD)) ||
((interface_state == IST_STANDARD) && (new == IST_EXTENDED))) {
if (audio_status() == AUDIO_STATUS_PLAY)
{
REMOTE_BUTTON(BUTTON_RC_PLAY);
}
}
interface_state = new;
}
static void iap_handlepkt_mode5(const unsigned int len, const unsigned char *buf)
{
(void) len;
unsigned int cmd = buf[1];
switch (cmd)
{
/* Sent from iPod Begin Transmission */
case 0x02:
{
/* RF Transmitter: Begin High Power transmission */
unsigned char data0[] = {0x05, 0x02};
iap_send_pkt(data0, sizeof(data0));
break;
}
/* Sent from iPod End High Power Transmission */
case 0x03:
{
/* RF Transmitter: End High Power transmission */
unsigned char data1[] = {0x05, 0x03};
iap_send_pkt(data1, sizeof(data1));
break;
}
/* Return Version Number ?*/
case 0x04:
{
/* do nothing */
break;
}
}
}
void iap_handlepkt(void)
{
int level;
int length;
if(!iap_setupflag) return;
/* if we are waiting for a remote button to go out,
delay the handling of the new packet */
if(iap_repeatbtn)
{
queue_post(&iap_queue, IAP_EV_MSG_RCVD, 0);
sleep(1);
return;
}
/* handle command by mode */
length = get_u16(iap_rxstart);
#if defined(LOGF_ENABLE) && defined(ROCKBOX_HAS_LOGF)
logf("R: %s", hexstring(iap_rxstart+2, (length)));
#endif
if (length != 0) {
unsigned char mode = *(iap_rxstart+2);
switch (mode) {
case 0: iap_handlepkt_mode0(length, iap_rxstart+2); break;
#ifdef HAVE_LINE_REC
case 1: iap_handlepkt_mode1(length, iap_rxstart+2); break;
#endif
case 2: iap_handlepkt_mode2(length, iap_rxstart+2); break;
case 3: iap_handlepkt_mode3(length, iap_rxstart+2); break;
case 4: iap_handlepkt_mode4(length, iap_rxstart+2); break;
case 5: iap_handlepkt_mode5(length, iap_rxstart+2); break;
#if CONFIG_TUNER
case 7: iap_handlepkt_mode7(length, iap_rxstart+2); break;
#endif
}
}
/* Remove the handled packet from the RX buffer
* This needs to be done with interrupts disabled, to make
* sure the buffer and the pointers into it are handled
* cleanly
*/
level = disable_irq_save();
memmove(iap_rxstart, iap_rxstart+(length+2), (RX_BUFLEN+2)-(length+2));
iap_rxnext -= (length+2);
iap_rxpayload -= (length+2);
iap_rxlen += (length+2);
restore_irq(level);
/* poke the poweroff timer */
reset_poweroff_timer();
}
int remote_control_rx(void)
{
int btn = iap_remotebtn;
if(iap_repeatbtn)
iap_repeatbtn--;
return btn;
}
const unsigned char *iap_get_serbuf(void)
{
return iap_rxstart;
}
#ifdef IAP_MALLOC_DYNAMIC
static int iap_move_callback(int handle, void* current, void* new)
{
(void) handle;
(void) current;
iap_txstart = new;
iap_txpayload = iap_txstart+5;
iap_txnext = iap_txpayload;
iap_rxstart = iap_buffers+(TX_BUFLEN+6);
return BUFLIB_CB_OK;
}
#endif
/* Change the shuffle state */
void iap_shuffle_state(const bool state)
{
/* Set shuffle to enabled */
if(state && !global_settings.playlist_shuffle)
{
global_settings.playlist_shuffle = 1;
settings_save();
if (audio_status() & AUDIO_STATUS_PLAY)
playlist_randomise(NULL, current_tick, true);
}
/* Set shuffle to disabled */
else if(!state && global_settings.playlist_shuffle)
{
global_settings.playlist_shuffle = 0;
settings_save();
if (audio_status() & AUDIO_STATUS_PLAY)
playlist_sort(NULL, true);
}
}
/* Change the repeat state */
void iap_repeat_state(const unsigned char state)
{
if (state != global_settings.repeat_mode)
{
global_settings.repeat_mode = state;
settings_save();
if (audio_status() & AUDIO_STATUS_PLAY)
audio_flush_and_reload_tracks();
}
}
void iap_repeat_next(void)
{
switch (global_settings.repeat_mode)
{
case REPEAT_OFF:
{
iap_repeat_state(REPEAT_ALL);
break;
}
case REPEAT_ALL:
{
iap_repeat_state(REPEAT_ONE);
break;
}
case REPEAT_ONE:
{
iap_repeat_state(REPEAT_OFF);
break;
}
}
}
/* This function puts the current power/battery state
* into the TX buffer. The buffer is assumed to be initialized
*/
void iap_fill_power_state(void)
{
unsigned char power_state;
unsigned char battery_l;
power_state = charger_input_state;
battery_l = battery_level();
if (power_state == NO_CHARGER) {
if (battery_l < 30) {
IAP_TX_PUT(0x00);
} else {
IAP_TX_PUT(0x01);
}
IAP_TX_PUT((char)((battery_l * 255)/100));
} else {
IAP_TX_PUT(0x04);
IAP_TX_PUT(0x00);
}
}