Eth audio device can notify processes
authorBarret Rhoden <brho@cs.berkeley.edu>
Tue, 14 Dec 2010 19:41:22 +0000 (11:41 -0800)
committerKevin Klues <klueska@cs.berkeley.edu>
Thu, 3 Nov 2011 00:35:57 +0000 (17:35 -0700)
When you run eth_audio, call it from busybox.  This will keep core 0 in
a loop of BB yielding, which will allow the NIC interrupts to be
received.  When ROS polls in the monitor, it does not receive interrupts
until it reads a complete line.

Note that the kernel cannot handle a process that does not munmap the
VMR before exiting.  I'm not willing to overhaul things for a ghetto
interface/device yet, so just be careful.

kern/include/eth_audio.h
kern/src/devfs.c
kern/src/eth_audio.c
tests/eth_audio.c

index ce2c4a7..282aa7b 100644 (file)
 #include <devfs.h>
 #include <net.h>
 
-#define ETH_AUDIO_RCV_PORT 1792
-#define ETH_AUDIO_SEND_PORT 1792
-/* 10 channels * 4 bytes/channel * 32 samples/packet = 1280.  + 2 bytes for a
- * sequence ID */
-#define ETH_AUDIO_PAYLOAD_SZ 1282
+#define ETH_AUDIO_RCV_PORT 1792                                /* where we listen */
+#define ETH_AUDIO_SRC_PORT 1792                                /* where we send from */
+#define ETH_AUDIO_DST_PORT 1793                                /* where we send to */
+/* 10 channels * 4 bytes/channel * 32 samples/packet = 1280.  On inbound
+ * packets, we have + 2 bytes for a * sequence ID.  On outbound packets, we have
+ * 4 bytes for control messages.  For now, we'll use the greater of the two,
+ * though this does mean we copy 2 extra bytes (that should be 0) into the
+ * mmap'd region.  Just don't read them. */
+#define ETH_AUDIO_PAYLOAD_SZ 1284
 #define ETH_AUDIO_IP_HDR_SZ 20
 #define ETH_AUDIO_HEADER_OFF (ETH_HDR_SZ + ETH_AUDIO_IP_HDR_SZ + UDP_HDR_SZ)
 #define ETH_AUDIO_FRAME_SZ (ETH_AUDIO_PAYLOAD_SZ + ETH_AUDIO_HEADER_OFF)
@@ -30,10 +34,6 @@ struct ethaud_udp_packet {
        char                                            payload[ETH_AUDIO_PAYLOAD_SZ];
 } __attribute__((packed));
 
-/* These two files are always open, like other device nodes.  Processes can open
- * and mmap them.  Don't do shit like unlinking them. */
-struct file *ethaud_in, *ethaud_out;
-
 void eth_audio_init(void);
 /* This is called by net subsys when it detects an ethernet audio packet */
 void eth_audio_newpacket(void *buf);
index 5f0f2f7..11f6670 100644 (file)
@@ -42,6 +42,7 @@ void devfs_init(void)
 }
 
 /* Creates a device node at a given location in the FS-tree */
+/* TODO: consider making this only deal with the inode */
 struct file *make_device(char *path, int mode, int type,
                          struct file_operations *fop)
 {
index f39a8f6..794461f 100644 (file)
@@ -10,6 +10,8 @@
 #include <page_alloc.h>
 #include <pmap.h>
 #include <arch/nic_common.h>
+#include <process.h>
+#include <smp.h>
 
 struct file_operations ethaud_in_f_op;
 struct file_operations ethaud_out_f_op;
@@ -19,24 +21,39 @@ struct page_map_operations ethaud_pm_op;
  * that we build and submit to send_frame() (which does another memcpy). */
 struct ethaud_udp_packet eth_udp_out;
 
+/* Consider a lock to protect this */
+struct proc *active_proc = 0;
+
+/* Nodes/inodes representing the device */
+struct inode *ethaud_in, *ethaud_out;
+
 /* Builds the device nodes in /dev */
 void eth_audio_init(void)
 {
        struct page *page;
-       ethaud_in = make_device("/dev/eth_audio_in", S_IRUSR, __S_IFBLK,
-                               &ethaud_in_f_op);
-       ethaud_out = make_device("/dev/eth_audio_out", S_IRUSR | S_IWUSR, __S_IFBLK,
-                                &ethaud_out_f_op);
-       /* make sure the inode tracks the right pm (not it's internal one) */
-       ethaud_in->f_dentry->d_inode->i_mapping->pm_op = &ethaud_pm_op;
-       ethaud_out->f_dentry->d_inode->i_mapping->pm_op = &ethaud_pm_op;
+       struct file *in_f, *out_f;
+       in_f = make_device("/dev/eth_audio_in", S_IRUSR, __S_IFBLK,
+                          &ethaud_in_f_op);
+       out_f = make_device("/dev/eth_audio_out", S_IRUSR | S_IWUSR, __S_IFBLK,
+                           &ethaud_out_f_op);
+       /* Grab and save the inodes (might change if we change make_device) */
+       ethaud_in = in_f->f_dentry->d_inode;
+       kref_get(&ethaud_in->i_kref, 1);
+       ethaud_out = out_f->f_dentry->d_inode;
+       kref_get(&ethaud_out->i_kref, 1);
+       /* Let go of the files (mostly used to get the dentry in the right place) */
+       kref_put(&in_f->f_kref);
+       kref_put(&out_f->f_kref);
+       /* make sure the inode tracks the right pm op */
+       ethaud_in->i_mapping->pm_op = &ethaud_pm_op;
+       ethaud_out->i_mapping->pm_op = &ethaud_pm_op;
        /* zalloc pages and associate them with the devices' inodes */
        assert(!kpage_alloc(&page));
        memset(page2kva(page), 0, PGSIZE);
-       ethaud_in->f_dentry->d_inode->i_fs_info = page;
+       ethaud_in->i_fs_info = page;
        assert(!kpage_alloc(&page));
        memset(page2kva(page), 0, PGSIZE);
-       ethaud_out->f_dentry->d_inode->i_fs_info = page;
+       ethaud_out->i_fs_info = page;
 }
 
 /* Lots of unnecessary copies.  For now, we need to build the ethernet frame,
@@ -82,10 +99,24 @@ static void eth_audio_prep_response(struct ethaud_udp_packet *incoming,
        outgoing->ip_hdr.dst_addr = incoming->ip_hdr.src_addr;
        /* Since the IP header is set already, we can compute the checksum. */
        outgoing->ip_hdr.checksum = htons(ip_checksum(&outgoing->ip_hdr));
-       outgoing->udp_hdr.src_port = htons(ETH_AUDIO_SEND_PORT);
-       outgoing->udp_hdr.dst_port = incoming->udp_hdr.src_port;
+       outgoing->udp_hdr.src_port = htons(ETH_AUDIO_SRC_PORT);
+       outgoing->udp_hdr.dst_port = htons(ETH_AUDIO_DST_PORT);
        outgoing->udp_hdr.length = htons(ETH_AUDIO_PAYLOAD_SZ + UDP_HDR_SZ);
        outgoing->udp_hdr.checksum = htons(0);
+
+       /* Debugging */
+       static int once = 0;
+       if (!once++)
+               printd("I will send %d bytes from %08p:%d to %08p:%d, iplen %04p, "
+                      "udplen %04p\n",
+                      ntohs(outgoing->udp_hdr.length),
+                      ntohl(outgoing->ip_hdr.src_addr),
+                      ntohs(outgoing->udp_hdr.src_port), 
+                      ntohl(outgoing->ip_hdr.dst_addr),
+                      ntohs(outgoing->udp_hdr.dst_port),
+                      ntohs(outgoing->ip_hdr.packet_len),
+                      ntohs(outgoing->udp_hdr.length)
+                          ); 
 }
 
 /* This is called by net subsys when it detects an ethernet audio packet.  Make
@@ -93,14 +124,19 @@ static void eth_audio_prep_response(struct ethaud_udp_packet *incoming,
 void eth_audio_newpacket(void *buf)
 {
        struct page *in_page, *out_page;
+       /* Bail out if these two haven't been initialized yet */
+       if (!ethaud_in || !ethaud_out)
+               return;
        /* Put info from the packet into the outgoing packet */
        eth_audio_prep_response((struct ethaud_udp_packet*)buf, &eth_udp_out);
-       in_page = (struct page*)ethaud_in->f_dentry->d_inode->i_fs_info;
-       out_page = (struct page*)ethaud_out->f_dentry->d_inode->i_fs_info;
+       in_page = (struct page*)ethaud_in->i_fs_info;
+       out_page = (struct page*)ethaud_out->i_fs_info;
        /* third copy (1st being the NIC to RAM). */
        memcpy(page2kva(in_page), buf + ETH_AUDIO_HEADER_OFF, ETH_AUDIO_PAYLOAD_SZ);
        /* Send the current outbound packet (can consider doing this by fsync) */
        eth_audio_sendpacket(page2kva(out_page));
+       if (active_proc)
+               proc_notify(active_proc, NE_FREE_APPLE_PIE, 0);
 }
 
 /* mmap() calls this to do any FS specific mmap() work.  Since our files are
@@ -123,6 +159,15 @@ int eth_audio_mmap(struct file *file, struct vm_region *vmr)
        /* No private mappings (would be ignored anyway) */
        if (vmr->vm_flags & MAP_PRIVATE)
                return -1;
+       /* Only one proc can use it at a time (we need to know who to notify, since
+        * we don't have any sockets or other abstractions to work with).  Note we
+        * are storing a reference. */
+       if (active_proc && active_proc != vmr->vm_proc)
+               return -1;
+       if (!active_proc) {
+               kref_get(&vmr->vm_proc->kref, 1);
+               active_proc = vmr->vm_proc;
+       }
        assert(page);
        /* Get the PTE for the page this VMR represents */
        pte_t *pte = pgdir_walk(vmr->vm_proc->env_pgdir, (void*)vmr->vm_base, 1);
@@ -149,6 +194,24 @@ int eth_audio_readpage(struct page_map *pm, struct page *page)
        return -1;
 }
 
+/* Called when the file is about to be closed (file obj freed).  This is the
+ * least intrusive way to find out when the device is unmapped and closed, so we
+ * can let go of the proc.  TODO: Note that currently, the kernel will not be
+ * able to unmap the file by itself, due to the circular dependence of the kref,
+ * proc_free, and destroy_vmr.  Programs have to unmap it themselves.  Since
+ * this device is a bit of a hack anyway, I'm okay with not making big changes
+ * to the kernel to support this yet. */
+int eth_audio_release(struct inode *inode, struct file *file)
+{
+       /* Disconnect the proc from the device, decref. */
+       if (active_proc && current) {
+               assert(active_proc == current);
+               kref_put(&active_proc->kref);
+               active_proc = 0;
+       }
+       return 0;
+}
+
 /* File operations */
 struct file_operations ethaud_in_f_op = {
        dev_c_llseek,   /* Errors out, can't llseek */
@@ -158,7 +221,7 @@ struct file_operations ethaud_in_f_op = {
        eth_audio_mmap,
        kfs_open,
        kfs_flush,
-       kfs_release,
+       eth_audio_release,
        0,                              /* fsync - makes no sense */
        kfs_poll,
        0,      /* readv */
@@ -175,7 +238,7 @@ struct file_operations ethaud_out_f_op = {
        eth_audio_mmap,
        kfs_open,
        kfs_flush,
-       kfs_release,
+       eth_audio_release,
        0,                              /* fsync - TODO: make this send the packet */
        kfs_poll,
        0,      /* readv */
index e417645..0c5fae3 100644 (file)
@@ -1,3 +1,10 @@
+#include <ros/resource.h>
+#include <ros/procdata.h>
+#include <ros/notification.h>
+#include <ros/bcq.h>
+#include <parlib.h>
+#include <vcore.h>
+
 #include <stdio.h> 
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <timing.h>
 #include <assert.h>
 
-/* Simple test program for the audio device.  Just mmaps the stuff and reads. */
+void *core0_tls = 0;
+void *in_buf, *out_buf;
+
+/* Test program for the audio device.  mmap()s the stuff, sets up a notif
+ * handler, and switches to multi_mode.
+ *
+ * Note: this has a lot of mhello-like MCP infrastructure.  When Lithe is
+ * working, you won't need any of this.  Just the mmap stuff and the notif
+ * handler.  Stuff specific to the ethernet audio device is marked ETH_AUD. */
 int main() 
 { 
+       int retval;
        int in_fd, out_fd;
-       void *in_buf, *out_buf;
+       /* ETHAUD mmap the input and output buffers */
        in_fd = open("/dev/eth_audio_in", O_RDONLY);
        out_fd = open("/dev/eth_audio_out", O_RDWR);
        assert(in_fd != -1);
@@ -30,13 +46,110 @@ int main()
                int err = errno;
                perror("Can't mmap the output buf:");
        }
-       strncpy(out_buf, "Nanwan loves you!\n", 19);
+       //strncpy(out_buf, "Nanwan loves you!\n", 19);
 
-       for (int i = 0; i < 20; i++) {
-               udelay(5000000);
-               printf("Contents: %s", in_buf);
+/* begin: stuff userspace needs to do before switching to multi-mode */
+       if (vcore_init())
+               printf("vcore_init() failed, we're fucked!\n");
+
+       /* tell the kernel where and how we want to receive notifications */
+       struct notif_method *nm;
+       /* ETHAUD Turn off all notifs */
+       for (int i = 0; i < MAX_NR_NOTIF; i++) {
+               nm = &__procdata.notif_methods[i];
+               nm->flags = 0;
        }
+       /* ETHAUD Turn on Free apple pie (which is the network packet) */
+       nm = &__procdata.notif_methods[NE_FREE_APPLE_PIE];
+       nm->flags |= NOTIF_WANTED | NOTIF_MSG | NOTIF_IPI;
+       nm->vcoreid = 0;        /* get it on vcore 0 */
+
+       /* Need to save this somewhere that you can find it again when restarting
+        * core0 */
+       core0_tls = get_tls_desc(0);
+       /* Need to save our floating point state somewhere (like in the
+        * user_thread_tcb so it can be restarted too */
 
+       /* don't forget to enable notifs on vcore0 at some point */
+       struct preempt_data *vcpd;
+       vcpd = &__procdata.vcore_preempt_data[0];
+       vcpd->notif_enabled = TRUE;
+       
+/* end: stuff userspace needs to do before switching to multi-mode */
+       /* ETHAUD */
+       /* Switch into _M mode */
+       retval = vcore_request(1);
+
+       /* Stay alive for a minute (will take interrupts) */
+       udelay(60000000);
+
+       printf("Eth aud, finishing up!\n");
+       /* Need to do unmap it, due to some limitations in the kernel */
+       munmap(in_buf, PGSIZE);
+       munmap(out_buf, PGSIZE);
        close(in_fd);
        close(out_fd);
 }
+
+/* ETHAUD, with some debugging commands... */
+void process_packet(void)
+{
+       //printf("Received a packet!\n");
+       //memset(out_buf, 0, PGSIZE);
+       memcpy(out_buf, in_buf, 1280);  /* size of the payload. */
+       memset(out_buf + 1280, 0, 4);   /* 4 bytes of control info. */
+       //printf("contents of out_buf %s\n", out_buf);
+}
+
+void vcore_entry(void)
+{
+       uint32_t vcoreid = vcore_id();
+       static bool first_time = TRUE;
+
+/* begin: stuff userspace needs to do to handle notifications */
+
+       struct vcore *vc = &__procinfo.vcoremap[vcoreid];
+       struct preempt_data *vcpd;
+       vcpd = &__procdata.vcore_preempt_data[vcoreid];
+       
+       /* here is how you receive a notif_event */
+       struct notif_event ne = {0};
+       bcq_dequeue(&vcpd->notif_evts, &ne, NR_PERCORE_EVENTS);
+       /* it might be in bitmask form too: */
+       //printf("and the bitmask looks like: ");
+       //PRINT_BITMASK(__procdata.vcore_preempt_data[vcoreid].notif_bmask, MAX_NR_NOTIF);
+       /* can see how many messages had to be sent as bits */
+       //printf("Number of event overflows: %d\n", vcpd->event_overflows);
+
+       /* ETHAUD app: process the packet if we got a notif */
+       if (ne.ne_type == NE_FREE_APPLE_PIE)
+               process_packet();
+
+       if (vc->preempt_pending) {
+               printf("Oh crap, vcore %d is being preempted!  Yielding\n", vcoreid);
+               sys_yield(TRUE);
+               printf("After yield on vcore %d. I wasn't being preempted.\n", vcoreid);
+       }
+               
+       /* Lets try to restart vcore0's context.  Note this doesn't do anything to
+        * set the appropriate TLS.  On x86, this will involve changing the LDT
+        * entry for this vcore to point to the TCB of the new user-thread. */
+       if (vcoreid == 0) {
+               vcpd->notif_pending = 0;
+               /* Do one last check for notifs after clearing pending */
+               set_tls_desc(core0_tls, 0);
+               /* Load silly state (Floating point) too */
+               pop_ros_tf(&vcpd->notif_tf, vcoreid);
+               printf("should never see me!");
+       }       
+       /* unmask notifications once you can let go of the notif_tf and it is okay
+        * to clobber the transition stack.
+        * Check Documentation/processes.txt: 4.2.4.  In real code, you should be
+        * popping the tf of whatever user process you want (get off the x-stack) */
+       vcpd->notif_enabled = TRUE;
+       
+/* end: stuff userspace needs to do to handle notifications */
+       /* The other vcores will hit here. */
+       while (1)
+               cpu_relax();
+}