Merge origin/netpush (networking code) (XCC)
[akaros.git] / kern / src / eth_audio.c
1 /* Copyright (c) 2010 The Regents of the University of California
2  * Barret Rhoden <brho@cs.berkeley.edu>
3  * See LICENSE for details.
4  *
5  * Rimas's Ethernet-Audio device */
6
7 #ifdef __CONFIG_ETH_AUDIO__
8
9 #include <eth_audio.h>
10 #include <string.h>
11 #include <devfs.h>
12 #include <page_alloc.h>
13 #include <pmap.h>
14 #include <arch/nic_common.h>
15 #include <process.h>
16 #include <smp.h>
17
18 struct file_operations ethaud_in_f_op;
19 struct file_operations ethaud_out_f_op;
20 struct page_map_operations ethaud_pm_op;
21
22 /* There is only one packet we'll ever send out.  It's a full ethernet frame
23  * that we build and submit to send_frame() (which does another memcpy). */
24 struct ethaud_udp_packet eth_udp_out;
25
26 /* Consider a lock to protect this */
27 struct proc *active_proc = 0;
28
29 /* Nodes/inodes representing the device */
30 struct inode *ethaud_in, *ethaud_out;
31
32 /* Builds the device nodes in /dev */
33 void eth_audio_init(void)
34 {
35         struct page *page;
36         struct file *in_f, *out_f;
37         in_f = make_device("/dev/eth_audio_in", S_IRUSR, __S_IFBLK,
38                            &ethaud_in_f_op);
39         out_f = make_device("/dev/eth_audio_out", S_IRUSR | S_IWUSR, __S_IFBLK,
40                             &ethaud_out_f_op);
41         /* Grab and save the inodes (might change if we change make_device) */
42         ethaud_in = in_f->f_dentry->d_inode;
43         kref_get(&ethaud_in->i_kref, 1);
44         ethaud_out = out_f->f_dentry->d_inode;
45         kref_get(&ethaud_out->i_kref, 1);
46         /* Let go of the files (mostly used to get the dentry in the right place) */
47         kref_put(&in_f->f_kref);
48         kref_put(&out_f->f_kref);
49         /* make sure the inode tracks the right pm op */
50         ethaud_in->i_mapping->pm_op = &ethaud_pm_op;
51         ethaud_out->i_mapping->pm_op = &ethaud_pm_op;
52         /* zalloc pages and associate them with the devices' inodes */
53         assert(!kpage_alloc(&page));
54         memset(page2kva(page), 0, PGSIZE);
55         ethaud_in->i_fs_info = page;
56         assert(!kpage_alloc(&page));
57         memset(page2kva(page), 0, PGSIZE);
58         ethaud_out->i_fs_info = page;
59 }
60
61 /* Lots of unnecessary copies.  For now, we need to build the ethernet frame,
62  * which means we prepend the ethernet header... */
63 static void eth_audio_sendpacket(void *buf)
64 {
65         int retval;
66         /* Fill the outgoing buffer (Copy #1.  2 is in send_frame, 3 is the DMA). */
67         memcpy(&eth_udp_out.payload, buf, ETH_AUDIO_PAYLOAD_SZ);
68         /* Make sure there is still a reasonable header. */
69         static_assert(sizeof(eth_udp_out) == ETH_AUDIO_FRAME_SZ);
70         /* Should compute the UDP checksum before sending the frame out.  The
71          * Eth-audio device shouldn't care (and Linux seems to be okay with packets
72          * that have no checksum (but not a wrong checksum)).  Technically, this
73          * hurts our performance a bit (and some NICs can offload this). */
74         eth_udp_out.udp_hdr.checksum = 0;
75         eth_udp_out.udp_hdr.checksum = udp_checksum(&eth_udp_out.ip_hdr,
76                                                           &eth_udp_out.udp_hdr);
77         /* Send it out */
78         retval = send_frame((const char*)&eth_udp_out, ETH_AUDIO_FRAME_SZ);
79         assert(retval >= 0);
80 }
81
82 /* This is how we know who to send the packet back to, since we have no real
83  * networking stack.  Lots of assumptions about how things stay in sync. */
84 static void eth_audio_prep_response(struct ethaud_udp_packet *incoming,
85                                     struct ethaud_udp_packet *outgoing)
86 {
87         /* If you're looking for optimizations, we can do this just once */
88         memcpy(&outgoing->eth_hdr.dst_mac, &incoming->eth_hdr.src_mac, 6); 
89         memcpy(&outgoing->eth_hdr.src_mac, device_mac, 6); 
90         outgoing->eth_hdr.eth_type = htons(IP_ETH_TYPE);
91         outgoing->ip_hdr.version = IPPROTO_IPV4;
92         outgoing->ip_hdr.hdr_len = ETH_AUDIO_IP_HDR_SZ >> 2;
93         outgoing->ip_hdr.tos = 0;
94         outgoing->ip_hdr.packet_len = htons(ETH_AUDIO_PAYLOAD_SZ + UDP_HDR_SZ +
95                                             ETH_AUDIO_IP_HDR_SZ);
96         outgoing->ip_hdr.id = htons(0);
97         outgoing->ip_hdr.flags_frags = htons(0);
98         outgoing->ip_hdr.ttl = DEFAULT_TTL;
99         outgoing->ip_hdr.protocol = IPPROTO_UDP;
100         /* Need a non-broadcast IP.  Picking one higher than the sender's */
101         outgoing->ip_hdr.src_addr = htonl(ntohl(incoming->ip_hdr.src_addr) + 1);
102         outgoing->ip_hdr.dst_addr = incoming->ip_hdr.src_addr;
103         /* Since the IP header is set already, we can compute the checksum. */
104         outgoing->ip_hdr.checksum = 0;
105         outgoing->ip_hdr.checksum = ip_checksum(&outgoing->ip_hdr);
106         outgoing->udp_hdr.src_port = htons(ETH_AUDIO_SRC_PORT);
107         outgoing->udp_hdr.dst_port = htons(ETH_AUDIO_DST_PORT);
108         outgoing->udp_hdr.length = htons(ETH_AUDIO_PAYLOAD_SZ + UDP_HDR_SZ);
109         outgoing->udp_hdr.checksum = htons(0);
110
111         /* Debugging */
112         static int once = 0;
113         if (!once++)
114                 printd("I will send %d bytes from %08p:%d to %08p:%d, iplen %04p, "
115                        "udplen %04p\n",
116                        ntohs(outgoing->udp_hdr.length),
117                        ntohl(outgoing->ip_hdr.src_addr),
118                        ntohs(outgoing->udp_hdr.src_port), 
119                        ntohl(outgoing->ip_hdr.dst_addr),
120                        ntohs(outgoing->udp_hdr.dst_port),
121                        ntohs(outgoing->ip_hdr.packet_len),
122                        ntohs(outgoing->udp_hdr.length)
123                            ); 
124 }
125
126 /* This is called by net subsys when it detects an ethernet audio packet.  Make
127  * sure the in device has the contents, and send out the old frame. */
128 void eth_audio_newpacket(void *buf)
129 {
130         struct page *in_page, *out_page;
131         /* Bail out if these two haven't been initialized yet */
132         if (!ethaud_in || !ethaud_out)
133                 return;
134         /* Put info from the packet into the outgoing packet */
135         eth_audio_prep_response((struct ethaud_udp_packet*)buf, &eth_udp_out);
136         in_page = (struct page*)ethaud_in->i_fs_info;
137         out_page = (struct page*)ethaud_out->i_fs_info;
138         /* third copy (1st being the NIC to RAM). */
139         memcpy(page2kva(in_page), buf + ETH_AUDIO_HEADER_OFF, ETH_AUDIO_PAYLOAD_SZ);
140         /* Send the current outbound packet (can consider doing this by fsync) */
141         eth_audio_sendpacket(page2kva(out_page));
142         if (active_proc)
143                 proc_notify(active_proc, 0);
144 }
145
146 /* mmap() calls this to do any FS specific mmap() work.  Since our files are
147  * defined to be only one page, we need to make sure they aren't mmapping too
148  * much, and that it isn't a PRIVATE mapping.
149  *
150  * Then we need to make sure the one page of the VMR is mapped in the page table
151  * (circumventing handle_page_fault and the page cache).  Avoiding the page
152  * cache means we won't need to worry about accidentally unmapping this under
153  * pressure and having to remap it (which will cause a readpage). */
154 int eth_audio_mmap(struct file *file, struct vm_region *vmr)
155 {
156         struct page *page = (struct page*)file->f_dentry->d_inode->i_fs_info;
157         /* Only allow mmaping from the start of the file */
158         if (vmr->vm_foff)
159                 return -1;
160         /* Only allow mmaping of a page */
161         if (vmr->vm_end - vmr->vm_base != PGSIZE)
162                 return -1;
163         /* No private mappings (would be ignored anyway) */
164         if (vmr->vm_flags & MAP_PRIVATE)
165                 return -1;
166         /* Only one proc can use it at a time (we need to know who to notify, since
167          * we don't have any sockets or other abstractions to work with).  Note we
168          * are storing a reference. */
169         if (active_proc && active_proc != vmr->vm_proc)
170                 return -1;
171         if (!active_proc) {
172                 kref_get(&vmr->vm_proc->p_kref, 1);
173                 active_proc = vmr->vm_proc;
174         }
175         assert(page);
176         /* Get the PTE for the page this VMR represents */
177         pte_t *pte = pgdir_walk(vmr->vm_proc->env_pgdir, (void*)vmr->vm_base, 1);
178         if (!pte)
179                 return -ENOMEM;
180         /* If there was a page there, it should have be munmapp()d. */
181         assert(!PAGE_PRESENT(*pte));
182         /* update the page table */
183         int pte_prot = (vmr->vm_prot & PROT_WRITE) ? PTE_USER_RW :
184                        (vmr->vm_prot & (PROT_READ|PROT_EXEC)) ? PTE_USER_RO : 0;
185         /* Storing a reference to the page in the PTE */
186         page_incref(page);
187         *pte = PTE(page2ppn(page), PTE_P | pte_prot);
188         return 0;
189 }
190
191 /* This shouldn't be called.  It could be if we ever handled a PF on the device,
192  * and the device wasn't present in the page table.  This should not happen,
193  * since ea_mmap() should have sorted that out.  Even if there was an unmap()
194  * then a new mmap(), it still shouldn't happen. */
195 int eth_audio_readpage(struct page_map *pm, struct page *page)
196 {
197         warn("Eth audio readpage!  (Did you page fault?)");
198         return -1;
199 }
200
201 /* Called when the file is about to be closed (file obj freed).  This is the
202  * least intrusive way to find out when the device is unmapped and closed, so we
203  * can let go of the proc.  TODO: Note that currently, the kernel will not be
204  * able to unmap the file by itself, due to the circular dependence of the kref,
205  * proc_free, and destroy_vmr.  Programs have to unmap it themselves.  Since
206  * this device is a bit of a hack anyway, I'm okay with not making big changes
207  * to the kernel to support this yet. */
208 int eth_audio_release(struct inode *inode, struct file *file)
209 {
210         /* Disconnect the proc from the device, decref. */
211         if (active_proc && current) {
212                 assert(active_proc == current);
213                 kref_put(&active_proc->p_kref);
214                 active_proc = 0;
215         }
216         return 0;
217 }
218
219 /* File operations */
220 struct file_operations ethaud_in_f_op = {
221         dev_c_llseek,   /* Errors out, can't llseek */
222         0,                              /* Can't read, only mmap */
223         0,                              /* Can't write, only mmap */
224         kfs_readdir,    /* this will fail gracefully */
225         eth_audio_mmap,
226         kfs_open,
227         kfs_flush,
228         eth_audio_release,
229         0,                              /* fsync - makes no sense */
230         kfs_poll,
231         0,      /* readv */
232         0,      /* writev */
233         kfs_sendpage,
234         kfs_check_flags,
235 };
236
237 struct file_operations ethaud_out_f_op = {
238         dev_c_llseek,   /* Errors out, can't llseek */
239         0,                              /* Can't read, only mmap */
240         0,                              /* Can't write, only mmap */
241         kfs_readdir,    /* this will fail gracefully */
242         eth_audio_mmap,
243         kfs_open,
244         kfs_flush,
245         eth_audio_release,
246         0,                              /* fsync - TODO: make this send the packet */
247         kfs_poll,
248         0,      /* readv */
249         0,      /* writev */
250         kfs_sendpage,
251         kfs_check_flags,
252 };
253
254 /* Eth audio page map ops: */
255 struct page_map_operations ethaud_pm_op = {
256         eth_audio_readpage,
257 };
258
259 #endif /* __CONFIG_ETH_AUDIO__ */