/*
* Copyright (c) 2011 embedded brains GmbH. All rights reserved.
*
* embedded brains GmbH
* Obere Lagerstr. 30
* 82178 Puchheim
* Germany
* <info@embedded-brains.de>
*
* The license and distribution terms for this file may be
* found in the file LICENSE in this distribution or at
* http://www.rtems.org/license/LICENSE.
*/
#include <machine/rtems-bsd-kernel-space.h>
#include <assert.h>
#include <errno.h>
#include <inttypes.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <rtems.h>
#include <rtems/rtems_bsdnet.h>
#include <rtems/rtems_mii_ioctl.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/mbuf.h>
#include <net/if.h>
#include <net/if_arp.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <libcpu/powerpc-utility.h>
#include <bsp.h>
#include <bsp/intercom.h>
typedef struct {
struct arpcom arpcom;
int destination_core;
intercom_packet *packet;
unsigned transmitted_frames;
unsigned received_frames;
} if_intercom_control;
static if_intercom_control if_intercom;
static struct mbuf *new_mbuf(struct ifnet *ifp, bool wait)
{
struct mbuf *m = NULL;
int mw = wait ? M_WAIT : M_DONTWAIT;
MGETHDR(m, mw, MT_DATA);
if (m != NULL) {
MCLGET(m, mw);
if ((m->m_flags & M_EXT) != 0) {
m->m_pkthdr.rcvif = ifp;
return m;
} else {
m_free(m);
}
}
return NULL;
}
static void if_intercom_service(intercom_packet *packet, void *arg)
{
rtems_bsdnet_semaphore_obtain();
if_intercom_control *self = arg;
struct ifnet *ifp = &self->arpcom.ac_if;
struct mbuf *m = new_mbuf(ifp, true);
memcpy(mtod(m, void *), packet->data, packet->size);
/* Ethernet header */
struct ether_header *eh = mtod(m, struct ether_header *);
/* Discard Ethernet header and CRC */
int sz = (int) packet->size - ETHER_HDR_LEN;
/* Update mbuf */
m->m_len = sz;
m->m_pkthdr.len = sz;
m->m_data = mtod(m, char *) + ETHER_HDR_LEN;
/* Hand over */
ether_input(ifp, eh, m);
++self->received_frames;
qoriq_intercom_free_packet(packet);
rtems_bsdnet_semaphore_release();
}
static intercom_packet *allocate_packet(void)
{
intercom_packet *packet = qoriq_intercom_allocate_packet(
INTERCOM_TYPE_NETWORK,
INTERCOM_SIZE_2K
);
packet->size = 0;
return packet;
}
static struct mbuf *get_next_fragment(struct ifnet *ifp, struct mbuf *m)
{
struct mbuf *n = NULL;
int size = 0;
while (true) {
if (m == NULL) {
/* Dequeue first fragment of the next frame */
IF_DEQUEUE(&ifp->if_snd, m);
/* Empty queue? */
if (m == NULL) {
return m;
}
}
/* Get fragment size */
size = m->m_len;
if (size > 0) {
/* Now we have a not empty fragment */
break;
} else {
/* Discard empty fragments */
m = m_free(m);
}
}
/* Discard empty successive fragments */
n = m->m_next;
while (n != NULL && n->m_len <= 0) {
n = m_free(n);
}
m->m_next = n;
return m;
}
static void if_intercom_start(struct ifnet *ifp)
{
if_intercom_control *self = ifp->if_softc;
int destination_core = self->destination_core;
intercom_packet *packet = self->packet;
size_t size = packet->size;
char *data = packet->data + size;
struct mbuf *m = NULL;
while ((m = get_next_fragment(ifp, m)) != NULL) {
size_t fragment_size = (size_t) m->m_len;
size_t new_size = size + fragment_size;
assert(new_size <= 2048);
memcpy(data, mtod(m, void *), fragment_size);
data += fragment_size;
size = new_size;
m = m_free(m);
/* Last fragment of frame ? */
if (m == NULL) {
packet->size = size;
qoriq_intercom_send_packet(destination_core, packet);
++self->transmitted_frames;
packet = allocate_packet();
data = packet->data;
size = 0;
}
}
packet->size = size;
self->packet = packet;
ifp->if_flags &= ~IFF_OACTIVE;
}
static void if_intercom_init(void *arg)
{
if_intercom_control *self = arg;
uint32_t self_core = ppc_processor_id();
self->destination_core = self_core == 0 ? 1 : 0;
self->packet = allocate_packet();
qoriq_intercom_service_install(
INTERCOM_TYPE_NETWORK,
if_intercom_service,
self
);
}
static void show_stats(if_intercom_control *self)
{
printf("transmitted frames: %u\n", self->transmitted_frames);
printf("received frames: %u\n", self->received_frames);
}
static int if_intercom_ioctl(
struct ifnet *ifp,
ioctl_command_t command,
caddr_t data
)
{
if_intercom_control *self = ifp->if_softc;
int rv = 0;
switch (command) {
case SIOCGIFADDR:
case SIOCSIFADDR:
ether_ioctl(ifp, command, data);
break;
case SIOCSIFFLAGS:
if (ifp->if_flags & IFF_RUNNING) {
/* TODO: off */
}
if (ifp->if_flags & IFF_UP) {
ifp->if_flags |= IFF_RUNNING;
/* TODO: init */
}
break;
case SIO_RTEMS_SHOW_STATS:
show_stats(self);
break;
default:
rv = EINVAL;
break;
}
return rv;
}
static void if_intercom_watchdog(struct ifnet *ifp)
{
ifp->if_timer = 0;
}
static int if_intercom_attach(struct rtems_bsdnet_ifconfig *config)
{
if_intercom_control *self = &if_intercom;
struct ifnet *ifp = &self->arpcom.ac_if;
char *unit_name = NULL;
int unit_index = rtems_bsdnet_parse_driver_name(config, &unit_name);
assert(unit_index == 1);
assert(strcmp(unit_name, "intercom") == 0);
assert(config->hardware_address != NULL);
memcpy(self->arpcom.ac_enaddr, config->hardware_address, ETHER_ADDR_LEN);
/* Set interface data */
ifp->if_softc = self;
ifp->if_unit = (short) unit_index;
ifp->if_name = unit_name;
ifp->if_mtu = (config->mtu > 0) ? (u_long) config->mtu : ETHERMTU;
ifp->if_init = if_intercom_init;
ifp->if_ioctl = if_intercom_ioctl;
ifp->if_start = if_intercom_start;
ifp->if_output = ether_output;
ifp->if_watchdog = if_intercom_watchdog;
ifp->if_flags = config->ignore_broadcast ? 0 : IFF_BROADCAST;
ifp->if_snd.ifq_maxlen = ifqmaxlen;
ifp->if_timer = 0;
/* Attach the interface */
if_attach(ifp);
ether_ifattach(ifp);
return 1;
}
int qoriq_if_intercom_attach_detach(
struct rtems_bsdnet_ifconfig *config,
int attaching
)
{
if (attaching) {
return if_intercom_attach(config);
} else {
assert(0);
}
}