/*
* $Id$
*/
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <errno.h>
#include <rtems.h>
#include <rtems/libio.h>
#include <rtems/error.h>
#include <rtems/rtems_bsdnet.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/domain.h>
#include <sys/mbuf.h>
#include <sys/socketvar.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/callout.h>
#include <sys/proc.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <net/route.h>
#include <netinet/in.h>
#include <vm/vm.h>
#include <arpa/inet.h>
#include <net/netisr.h>
#include <net/route.h>
/*
* Memory allocation
*/
static int nmbuf = (64 * 1024) / MSIZE;
int nmbclusters = (128 * 1024) / MCLBYTES;
/*
* Socket buffering parameters
*/
unsigned long sb_efficiency = 8;
/*
* Network task synchronization
*/
static rtems_id networkSemaphore;
static rtems_id networkDaemonTid;
static rtems_unsigned32 networkDaemonPriority;
static void networkDaemon (void *task_argument);
/*
* Network timing
*/
int rtems_bsdnet_ticks_per_second;
int rtems_bsdnet_microseconds_per_tick;
/*
* Callout processing
*/
static rtems_interval ticksWhenCalloutsLastChecked;
static struct callout *callfree, calltodo;
/*
* FreeBSD variables
*/
int nfs_diskless_valid;
/*
* BOOTP values
*/
struct in_addr rtems_bsdnet_log_host_address;
struct in_addr rtems_bsdnet_bootp_server_address;
char *rtems_bsdnet_bootp_boot_file_name;
char *rtems_bsdnet_bootp_server_name;
char *rtems_bsdnet_domain_name;
struct in_addr rtems_bsdnet_nameserver[sizeof rtems_bsdnet_config.name_server /
sizeof rtems_bsdnet_config.name_server[0]];
int rtems_bsdnet_nameserver_count;
extern rtems_libio_handler_t rtems_bsdnet_io_handler;
/*
* Perform FreeBSD memory allocation.
* FIXME: This should be modified to keep memory allocation statistics.
*/
#undef malloc
#undef free
extern void *malloc (size_t);
extern void free (void *);
void *
rtems_bsdnet_malloc (unsigned long size, int type, int flags)
{
void *p;
for (;;) {
p = malloc (size);
if (p)
return p;
if (flags & M_NOWAIT)
return p;
/*
* FIXME: This should be redone as:
* static volatile int rtems_bsdnet_need_memory;
*
* rtems_bsdnet_need_memory = 1;
* message_queue_receive
*
* Then in rtems_bsdnet_freee:
* free (....);
* if (rtems_bsdnet_need_memory)
* rtems_bsdnet_need_memory = 0;
* message_queue_broadcast
*/
rtems_task_wake_after (rtems_bsdnet_ticks_per_second);
}
}
/*
* Free FreeBSD memory
* FIXME: This should be modified to keep memory allocation statistics.
*/
void
rtems_bsdnet_free (void *addr, int type)
{
free (addr);
}
/*
* Do the initializations required by the BSD code
* FIXME: Maybe we should use a different memory allocation scheme that
* would let us share space between mbufs and mbuf clusters.
* For now, we'll just take the easy way out!
*/
static void
bsd_init ()
{
/*
* Set up mbuf data strutures
* Cluster allocation *must* come first -- see comment on kmem_malloc().
*/
m_clalloc (nmbclusters, M_DONTWAIT);
mclrefcnt = malloc (nmbclusters);
if (mclrefcnt == NULL)
rtems_panic ("No memory for mbuf cluster reference counts.");
memset (mclrefcnt, '\0', nmbclusters);
m_mballoc (nmbuf, M_DONTWAIT);
mbstat.m_mtypes[MT_FREE] = nmbuf;
/*
* Set up domains
*/
{
extern struct domain routedomain;
extern struct domain inetdomain;
routedomain.dom_next = domains;
domains = &routedomain;
inetdomain.dom_next = domains;
domains = &inetdomain;
domaininit (NULL);
}
/*
* Set up interfaces
*/
ifinit (NULL);
}
/*
* Initialize and start network operations
*/
static void
rtems_bsdnet_initialize (void)
{
rtems_status_code sc;
/*
* Set the priority of all network tasks
*/
if (rtems_bsdnet_config.network_task_priority == 0)
networkDaemonPriority = 100;
else
networkDaemonPriority = rtems_bsdnet_config.network_task_priority;
/*
* Set the memory allocation limits
*/
if (rtems_bsdnet_config.mbuf_bytecount)
nmbuf = rtems_bsdnet_config.mbuf_bytecount / MSIZE;
if (rtems_bsdnet_config.mbuf_cluster_bytecount)
nmbclusters = rtems_bsdnet_config.mbuf_cluster_bytecount / MCLBYTES;
/*
* Create the task-synchronization semaphore
*/
sc = rtems_semaphore_create (rtems_build_name('B', 'S', 'D', 'n'),
0,
RTEMS_FIFO |
RTEMS_BINARY_SEMAPHORE |
RTEMS_NO_INHERIT_PRIORITY |
RTEMS_NO_PRIORITY_CEILING |
RTEMS_LOCAL,
0,
&networkSemaphore);
if (sc != RTEMS_SUCCESSFUL)
rtems_panic ("Can't create network seamphore: `%s'\n", rtems_status_text (sc));
/*
* Compute clock tick conversion factors
*/
rtems_clock_get (RTEMS_CLOCK_GET_TICKS_PER_SECOND, &rtems_bsdnet_ticks_per_second);
if (rtems_bsdnet_ticks_per_second <= 0)
rtems_bsdnet_ticks_per_second = 1;
rtems_bsdnet_microseconds_per_tick = 1000000 / rtems_bsdnet_ticks_per_second;
/*
* Ensure that `seconds' is greater than 0
*/
rtems_task_wake_after (rtems_bsdnet_ticks_per_second);
/*
* Set up BSD-style sockets
*/
bsd_init ();
/*
* Start network daemon
*/
networkDaemonTid = rtems_bsdnet_newproc ("ntwk", 4096, networkDaemon, NULL);
/*
* Register as an external I/O handler
*/
rtems_register_libio_handler (LIBIO_FLAGS_HANDLER_SOCK,
&rtems_bsdnet_io_handler);
/*
* Let other network tasks begin
*/
rtems_bsdnet_semaphore_release ();
}
rtems_id TaskWithSemaphore;
/*
* Obtain network mutex
*/
void
rtems_bsdnet_semaphore_obtain (void)
{
rtems_status_code sc;
sc = rtems_semaphore_obtain (networkSemaphore, RTEMS_WAIT, RTEMS_NO_TIMEOUT);
rtems_task_ident (RTEMS_SELF, 0, &TaskWithSemaphore);
if (sc != RTEMS_SUCCESSFUL)
rtems_panic ("Can't obtain network semaphore: `%s'\n", rtems_status_text (sc));
}
/*
* Release network mutex
*/
void
rtems_bsdnet_semaphore_release (void)
{
rtems_status_code sc;
TaskWithSemaphore = 0;
sc = rtems_semaphore_release (networkSemaphore);
if (sc != RTEMS_SUCCESSFUL)
rtems_panic ("Can't release network semaphore: `%s'\n", rtems_status_text (sc));
}
/*
* Wait for something to happen to a socket buffer
*/
int
sbwait(sb)
struct sockbuf *sb;
{
rtems_event_set events;
rtems_id tid;
rtems_status_code sc;
/*
* Soak up any pending events.
* The sleep/wakeup synchronization in the FreeBSD
* kernel has no memory.
*/
rtems_event_receive (SBWAIT_EVENT, RTEMS_EVENT_ANY | RTEMS_NO_WAIT, RTEMS_NO_TIMEOUT, &events);
/*
* Set this task as the target of the wakeup operation.
*/
rtems_task_ident (RTEMS_SELF, 0, &tid);
sb->sb_sel.si_pid = tid;
/*
* Release the network semaphore.
*/
rtems_bsdnet_semaphore_release ();
/*
* Wait for the wakeup event.
*/
sc = rtems_event_receive (SBWAIT_EVENT, RTEMS_EVENT_ANY | RTEMS_WAIT, sb->sb_timeo, &events);
/*
* Reobtain the network semaphore.
*/
rtems_bsdnet_semaphore_obtain ();
/*
* Return the status of the wait.
*/
switch (sc) {
case RTEMS_SUCCESSFUL: return 0;
case RTEMS_TIMEOUT: return EWOULDBLOCK;
default: return ENXIO;
}
}
/*
* Wake up the task waiting on a socket buffer.
*/
void
sowakeup(so, sb)
register struct socket *so;
register struct sockbuf *sb;
{
rtems_id tid;
if ((tid = sb->sb_sel.si_pid) != 0) {
sb->sb_sel.si_pid = 0;
rtems_event_send (tid, SBWAIT_EVENT);
}
}
/*
* For now, a socket can be used by only one task at a time.
*/
int
sb_lock(sb)
register struct sockbuf *sb;
{
rtems_panic ("Socket buffer is already in use.");
return 0;
}
void
wakeup (void *p)
{
rtems_panic ("Wakeup called");
}
/*
* Wait for a connection/disconnection event.
*/
int
soconnsleep (struct socket *so)
{
rtems_event_set events;
rtems_id tid;
rtems_status_code sc;
/*
* Soak up any pending events.
* The sleep/wakeup synchronization in the FreeBSD
* kernel has no memory.
*/
rtems_event_receive (SOSLEEP_EVENT, RTEMS_EVENT_ANY | RTEMS_NO_WAIT, RTEMS_NO_TIMEOUT, &events);
/*
* Set this task as the target of the wakeup operation.
*/
if (so->so_pgid)
rtems_panic ("Another task is already sleeping on that socket");
rtems_task_ident (RTEMS_SELF, 0, &tid);
so->so_pgid = tid;
/*
* Wait for the wakeup event.
*/
sc = rtems_bsdnet_event_receive (SOSLEEP_EVENT, RTEMS_EVENT_ANY | RTEMS_WAIT, so->so_rcv.sb_timeo, &events);
/*
* Relinquish ownership of the socket.
*/
so->so_pgid = 0;
switch (sc) {
case RTEMS_SUCCESSFUL: return 0;
case RTEMS_TIMEOUT: return EWOULDBLOCK;
default: return ENXIO;
}
}
/*
* Wake up a task waiting for a connection/disconnection to complete.
*/
void
soconnwakeup (struct socket *so)
{
if (so->so_pgid)
rtems_event_send (so->so_pgid, SOSLEEP_EVENT);
}
/*
* Send an event to the network daemon.
* This corresponds to sending a software interrupt in the BSD kernel.
*/
void
rtems_bsdnet_schednetisr (int n)
{
rtems_event_send (networkDaemonTid, 1 << n);
}
/*
* The network daemon
* This provides a context to run BSD software interrupts
*/
static void
networkDaemon (void *task_argument)
{
rtems_event_set events;
rtems_interval now;
int ticksPassed;
unsigned32 timeout;
struct callout *c;
for (;;) {
c = calltodo.c_next;
if (c)
timeout = c->c_time;
else
timeout = RTEMS_NO_TIMEOUT;
rtems_bsdnet_event_receive (NETISR_EVENTS,
RTEMS_EVENT_ANY | RTEMS_WAIT,
timeout,
&events);
if (events & NETISR_IP_EVENT)
ipintr ();
if (events & NETISR_ARP_EVENT)
arpintr ();
rtems_clock_get (RTEMS_CLOCK_GET_TICKS_SINCE_BOOT, &now);
ticksPassed = now - ticksWhenCalloutsLastChecked;
if (ticksPassed != 0) {
ticksWhenCalloutsLastChecked = now;
c = calltodo.c_next;
if (c) {
c->c_time -= ticksPassed;
while ((c = calltodo.c_next) != NULL && c->c_time <= 0) {
void *arg;
void (*func) (void *);
func = c->c_func;
arg = c->c_arg;
calltodo.c_next = c->c_next;
c->c_next = callfree;
callfree = c;
(*func)(arg);
}
}
}
}
}
/*
* Structure passed to task-start stub
*/
struct newtask {
void (*entry)(void *);
void *arg;
};
/*
* Task-start stub
*/
static void
taskEntry (rtems_task_argument arg)
{
struct newtask t;
/*
* Pick up task information and free
* the memory allocated to pass the
* information to this task.
*/
t = *(struct newtask *)arg;
free ((struct newtask *)arg);
/*
* Enter the competition for the network semaphore
*/
rtems_bsdnet_semaphore_obtain ();
/*
* Enter the task
*/
(*t.entry)(t.arg);
rtems_panic ("Network task returned!\n");
}
/*
* Start a network task
*/
rtems_id
rtems_bsdnet_newproc (char *name, int stacksize, void(*entry)(void *), void *arg)
{
struct newtask *t;
char nm[4];
rtems_id tid;
rtems_status_code sc;
strncpy (nm, name, 4);
sc = rtems_task_create (rtems_build_name(nm[0], nm[1], nm[2], nm[3]),
networkDaemonPriority,
stacksize,
RTEMS_PREEMPT|RTEMS_NO_TIMESLICE|RTEMS_NO_ASR|RTEMS_INTERRUPT_LEVEL(0),
RTEMS_NO_FLOATING_POINT|RTEMS_LOCAL,
&tid);
if (sc != RTEMS_SUCCESSFUL)
rtems_panic ("Can't create network daemon `%s': `%s'\n", name, rtems_status_text (sc));
/*
* Set up task arguments
*/
t = malloc (sizeof *t);
t->entry = entry;
t->arg = arg;
/*
* Start the task
*/
sc = rtems_task_start (tid, taskEntry, (rtems_task_argument)t);
if (sc != RTEMS_SUCCESSFUL)
rtems_panic ("Can't start network daemon `%s': `%s'\n", name, rtems_status_text (sc));
/*
* Let our caller know the i.d. of the new task
*/
return tid;
}
rtems_status_code rtems_bsdnet_event_receive (
rtems_event_set event_in,
rtems_option option_set,
rtems_interval ticks,
rtems_event_set *event_out)
{
rtems_status_code sc;
rtems_bsdnet_semaphore_release ();
sc = rtems_event_receive (event_in, option_set, ticks, event_out);
rtems_bsdnet_semaphore_obtain ();
return sc;
}
/*
* Return time since startup
*/
void
microtime (struct timeval *t)
{
rtems_interval now;
rtems_clock_get (RTEMS_CLOCK_GET_TICKS_SINCE_BOOT, &now);
t->tv_sec = now / rtems_bsdnet_ticks_per_second;
t->tv_usec = (now % rtems_bsdnet_ticks_per_second) * rtems_bsdnet_microseconds_per_tick;
}
unsigned long
rtems_bsdnet_seconds_since_boot (void)
{
rtems_interval now;
rtems_clock_get (RTEMS_CLOCK_GET_TICKS_SINCE_BOOT, &now);
return now / rtems_bsdnet_ticks_per_second;
}
/*
* Fake random number generator
*/
unsigned long
rtems_bsdnet_random (void)
{
rtems_interval now;
rtems_clock_get (RTEMS_CLOCK_GET_TICKS_SINCE_BOOT, &now);
return (now * 99991);
}
/*
* Callout list processing
*/
void
timeout(void (*ftn)(void *), void *arg, int ticks)
{
register struct callout *new, *p, *t;
if (ticks <= 0)
ticks = 1;
/* Fill in the next free callout structure. */
if (callfree == NULL) {
callfree = malloc (sizeof *callfree);
if (callfree == NULL)
rtems_panic ("No memory for timeout table entry");
callfree->c_next = NULL;
}
new = callfree;
callfree = new->c_next;
new->c_arg = arg;
new->c_func = ftn;
/*
* The time for each event is stored as a difference from the time
* of the previous event on the queue. Walk the queue, correcting
* the ticks argument for queue entries passed. Correct the ticks
* value for the queue entry immediately after the insertion point
* as well. Watch out for negative c_time values; these represent
* overdue events.
*/
for (p = &calltodo;
(t = p->c_next) != NULL && ticks > t->c_time; p = t)
if (t->c_time > 0)
ticks -= t->c_time;
new->c_time = ticks;
if (t != NULL)
t->c_time -= ticks;
/* Insert the new entry into the queue. */
p->c_next = new;
new->c_next = t;
}
/*
* Ticks till specified time
* FIXME: This version worries only about seconds, but that's good
* enough for the way the network code uses this routine.
*/
int
hzto(struct timeval *tv)
{
long diff = tv->tv_sec - rtems_bsdnet_seconds_since_boot();
if (diff <= 0)
return 1;
return diff * rtems_bsdnet_ticks_per_second;
}
/*
* Kernel debugging
*/
int rtems_bsdnet_log_priority;
void
rtems_bsdnet_log (int priority, const char *fmt, ...)
{
va_list args;
if (priority & rtems_bsdnet_log_priority) {
va_start (args, fmt);
vprintf (fmt, args);
va_end (args);
}
}
/*
* Hack alert: kmem_malloc `knows' that its
* first invocation is to get mbuf clusters!
*/
int mb_map_full;
vm_map_t mb_map;
vm_offset_t
kmem_malloc (vm_map_t *map, vm_size_t size, boolean_t waitflag)
{
void *p;
/*
* Can't get memory if we're already running.
*/
if (networkDaemonTid) {
if (waitflag == M_WAITOK)
rtems_panic (
"Network mbuf space can not be enlarged after rtems_bsdnet_initialize() has\n"
"returned. Enlarge the initial mbuf/cluster size in rtems_bsdnet_config.");
return 0;
}
#define ROUNDSIZE 2048
p = malloc (size+ROUNDSIZE);
p = (void *)((unsigned long)p & ~(ROUNDSIZE-1));
if ((p == NULL) && (waitflag == M_WAITOK))
rtems_panic ("Can't get initial network memory!");
if (mbutl == NULL)
mbutl = p;
return (vm_offset_t)p;
}
/*
* IP header checksum routine for processors which don't have an inline version
*/
u_int
in_cksum_hdr (const void *ip)
{
rtems_unsigned32 sum;
const rtems_unsigned16 *sp;
int i;
sum = 0;
sp = (rtems_unsigned16 *)ip;
for (i = 0 ; i < 10 ; i++)
sum += *sp++;
while (sum > 0xFFFF)
sum = (sum & 0xffff) + (sum >> 16);
return ~sum & 0xFFFF;
}
/*
* Manipulate routing tables
*/
int rtems_bsdnet_rtrequest (
int req,
struct sockaddr *dst,
struct sockaddr *gateway,
struct sockaddr *netmask,
int flags,
struct rtentry **net_nrt)
{
int error;
rtems_bsdnet_semaphore_obtain ();
error = rtrequest (req, dst, gateway, netmask, flags, net_nrt);
rtems_bsdnet_semaphore_release ();
if (error) {
errno = error;
return -1;
}
return 0;
}
static void
rtems_bsdnet_setup (void)
{
struct rtems_bsdnet_ifconfig *ifp;
int s;
struct ifreq ifreq;
struct sockaddr_in address;
struct sockaddr_in netmask;
struct sockaddr_in broadcast;
struct sockaddr_in gateway;
int i;
/*
* Set local parameters
*/
if (rtems_bsdnet_config.hostname)
sethostname (rtems_bsdnet_config.hostname,
strlen (rtems_bsdnet_config.hostname));
if (rtems_bsdnet_config.domainname)
rtems_bsdnet_domain_name =
strdup (rtems_bsdnet_config.domainname);
if (rtems_bsdnet_config.log_host)
rtems_bsdnet_log_host_address.s_addr =
inet_addr (rtems_bsdnet_config.log_host);
for (i = 0 ; i < sizeof rtems_bsdnet_config.name_server /
sizeof rtems_bsdnet_config.name_server[0] ; i++) {
if (!rtems_bsdnet_config.name_server[i])
break;
rtems_bsdnet_nameserver[rtems_bsdnet_nameserver_count++].s_addr
= inet_addr (rtems_bsdnet_config.name_server[i]);
}
/*
* Configure interfaces
*/
s = socket (AF_INET, SOCK_DGRAM, 0);
if (s < 0)
rtems_panic ("Can't create initial socket: %s", strerror (errno));
for (ifp = rtems_bsdnet_config.ifconfig ; ifp ; ifp = ifp->next) {
if (ifp->ip_address == NULL)
continue;
/*
* Get the interface flags
*/
strcpy (ifreq.ifr_name, ifp->name);
if (ioctl (s, SIOCGIFFLAGS, &ifreq) < 0)
rtems_panic ("Can't get %s flags: %s", ifp->name, strerror (errno));
/*
* Bring interface up
*/
ifreq.ifr_flags |= IFF_UP;
if (ioctl (s, SIOCSIFFLAGS, &ifreq) < 0)
rtems_panic ("Can't bring %s up: %s", ifp->name, strerror (errno));
/*
* Set interface netmask
*/
memset (&netmask, '\0', sizeof netmask);
netmask.sin_len = sizeof netmask;
netmask.sin_family = AF_INET;
netmask.sin_addr.s_addr = inet_addr (ifp->ip_netmask);
memcpy (&ifreq.ifr_addr, &netmask, sizeof netmask);
if (ioctl (s, SIOCSIFNETMASK, &ifreq) < 0)
rtems_panic ("Can't set %s netmask: %s", ifp->name, strerror (errno));
/*
* Set interface address
*/
memset (&address, '\0', sizeof address);
address.sin_len = sizeof address;
address.sin_family = AF_INET;
address.sin_addr.s_addr = inet_addr (ifp->ip_address);
memcpy (&ifreq.ifr_addr, &address, sizeof address);
if (ioctl (s, SIOCSIFADDR, &ifreq) < 0)
rtems_panic ("Can't set %s address: %s", ifp->name, strerror (errno));
/*
* Set interface broadcast address
*/
memset (&broadcast, '\0', sizeof broadcast);
broadcast.sin_len = sizeof broadcast;
broadcast.sin_family = AF_INET;
broadcast.sin_addr.s_addr = address.sin_addr.s_addr | ~netmask.sin_addr.s_addr;
memcpy (&ifreq.ifr_broadaddr, &broadcast, sizeof broadcast);
if (ioctl (s, SIOCSIFBRDADDR, &ifreq) < 0)
rtems_panic ("Can't set %s broadcast address: %s", ifp->name, strerror (errno));
}
/*
* We're done with the dummy socket
*/
close (s);
/*
* Set default route
*/
if (rtems_bsdnet_config.gateway) {
address.sin_addr.s_addr = INADDR_ANY;
netmask.sin_addr.s_addr = INADDR_ANY;
memset (&gateway, '\0', sizeof gateway);
gateway.sin_len = sizeof gateway;
gateway.sin_family = AF_INET;
gateway.sin_addr.s_addr = inet_addr (rtems_bsdnet_config.gateway);
if (rtems_bsdnet_rtrequest (
RTM_ADD,
(struct sockaddr *)&address,
(struct sockaddr *)&gateway,
(struct sockaddr *)&netmask,
(RTF_UP | RTF_GATEWAY | RTF_STATIC), NULL) < 0)
rtems_panic ("Can't set default route: %s", strerror (errno));
}
}
/*
* Initialize the network
*/
int
rtems_bsdnet_initialize_network (void)
{
struct rtems_bsdnet_ifconfig *ifp;
/*
* Start network tasks.
* Initialize BSD network data structures.
*/
rtems_bsdnet_initialize ();
/*
* Attach interfaces
*/
for (ifp = rtems_bsdnet_config.ifconfig ; ifp ; ifp = ifp->next) {
rtems_bsdnet_semaphore_obtain ();
(ifp->attach)(ifp);
rtems_bsdnet_semaphore_release ();
}
/*
* Bring up the network
*/
rtems_bsdnet_setup ();
if (rtems_bsdnet_config.bootp)
(*rtems_bsdnet_config.bootp)();
return 0;
}