/* -----------------------------------------------------------------------------
* Copyright (c) 2011 Ozmo Inc
* Released under the GNU General Public License Version 2 (GPLv2).
* -----------------------------------------------------------------------------
*/
#include <linux/module.h>
#include <linux/timer.h>
#include <linux/sched.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/errno.h>
#include "ozdbg.h"
#include "ozprotocol.h"
#include "ozeltbuf.h"
#include "ozpd.h"
#include "ozproto.h"
#include "ozcdev.h"
#include "ozusbsvc.h"
#include <asm/unaligned.h>
#include <linux/uaccess.h>
#include <net/psnap.h>
static struct oz_tx_frame *oz_tx_frame_alloc(struct oz_pd *pd);
static void oz_tx_frame_free(struct oz_pd *pd, struct oz_tx_frame *f);
static void oz_tx_isoc_free(struct oz_pd *pd, struct oz_tx_frame *f);
static struct sk_buff *oz_build_frame(struct oz_pd *pd, struct oz_tx_frame *f);
static int oz_send_isoc_frame(struct oz_pd *pd);
static void oz_retire_frame(struct oz_pd *pd, struct oz_tx_frame *f);
static void oz_isoc_stream_free(struct oz_isoc_stream *st);
static int oz_send_next_queued_frame(struct oz_pd *pd, int more_data);
static void oz_isoc_destructor(struct sk_buff *skb);
/*
* Counts the uncompleted isoc frames submitted to netcard.
*/
static atomic_t g_submitted_isoc = ATOMIC_INIT(0);
/* Application handler functions.
*/
static const struct oz_app_if g_app_if[OZ_NB_APPS] = {
[OZ_APPID_USB] = {
.init = oz_usb_init,
.term = oz_usb_term,
.start = oz_usb_start,
.stop = oz_usb_stop,
.rx = oz_usb_rx,
.heartbeat = oz_usb_heartbeat,
.farewell = oz_usb_farewell,
},
[OZ_APPID_SERIAL] = {
.init = oz_cdev_init,
.term = oz_cdev_term,
.start = oz_cdev_start,
.stop = oz_cdev_stop,
.rx = oz_cdev_rx,
},
};
/*
* Context: softirq or process
*/
void oz_pd_set_state(struct oz_pd *pd, unsigned state)
{
pd->state = state;
switch (state) {
case OZ_PD_S_IDLE:
oz_pd_dbg(pd, ON, "PD State: OZ_PD_S_IDLE\n");
break;
case OZ_PD_S_CONNECTED:
oz_pd_dbg(pd, ON, "PD State: OZ_PD_S_CONNECTED\n");
break;
case OZ_PD_S_STOPPED:
oz_pd_dbg(pd, ON, "PD State: OZ_PD_S_STOPPED\n");
break;
case OZ_PD_S_SLEEP:
oz_pd_dbg(pd, ON, "PD State: OZ_PD_S_SLEEP\n");
break;
}
}
/*
* Context: softirq or process
*/
void oz_pd_get(struct oz_pd *pd)
{
atomic_inc(&pd->ref_count);
}
/*
* Context: softirq or process
*/
void oz_pd_put(struct oz_pd *pd)
{
if (atomic_dec_and_test(&pd->ref_count))
oz_pd_destroy(pd);
}
/*
* Context: softirq-serialized
*/
struct oz_pd *oz_pd_alloc(const u8 *mac_addr)
{
struct oz_pd *pd = kzalloc(sizeof(struct oz_pd), GFP_ATOMIC);
if (pd) {
int i;
atomic_set(&pd->ref_count, 2);
for (i = 0; i < OZ_NB_APPS; i++)
spin_lock_init(&pd->app_lock[i]);
pd->last_rx_pkt_num = 0xffffffff;
oz_pd_set_state(pd, OZ_PD_S_IDLE);
pd->max_tx_size = OZ_MAX_TX_SIZE;
ether_addr_copy(pd->mac_addr, mac_addr);
oz_elt_buf_init(&pd->elt_buff);
spin_lock_init(&pd->tx_frame_lock);
INIT_LIST_HEAD(&pd->tx_queue);
INIT_LIST_HEAD(&pd->farewell_list);
pd->last_sent_frame = &pd->tx_queue;
spin_lock_init(&pd->stream_lock);
INIT_LIST_HEAD(&pd->stream_list);
tasklet_init(&pd->heartbeat_tasklet, oz_pd_heartbeat_handler,
(unsigned long)pd);
tasklet_init(&pd->timeout_tasklet, oz_pd_timeout_handler,
(unsigned long)pd);
hrtimer_init(&pd->heartbeat, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
hrtimer_init(&pd->timeout, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
pd->heartbeat.function = oz_pd_heartbeat_event;
pd->timeout.function = oz_pd_timeout_event;
}
return pd;
}
/*
* Context: softirq or process
*/
static void oz_pd_free(struct work_struct *work)
{
struct list_head *e, *n;
struct oz_pd *pd;
oz_pd_dbg(pd, ON, "Destroying PD\n");
pd = container_of(work, struct oz_pd, workitem);
/*Disable timer tasklets*/
tasklet_kill(&pd->heartbeat_tasklet);
tasklet_kill(&pd->timeout_tasklet);
/* Free streams, queued tx frames and farewells. */
list_for_each_safe(e, n, &pd->stream_list)
oz_isoc_stream_free(list_entry(e, struct oz_isoc_stream, link));
list_for_each_safe(e, n, &pd->tx_queue) {
struct oz_tx_frame *f = list_entry(e, struct