Support for the Ethernet-Audio device
[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 #include <eth_audio.h>
8 #include <string.h>
9 #include <devfs.h>
10 #include <page_alloc.h>
11 #include <pmap.h>
12 #include <arch/nic_common.h>
13
14 struct file_operations ethaud_in_f_op;
15 struct file_operations ethaud_out_f_op;
16 struct page_map_operations ethaud_pm_op;
17
18 /* There is only one packet we'll ever send out.  It's a full ethernet frame
19  * that we build and submit to send_frame() (which does another memcpy). */
20 struct ethaud_udp_packet eth_udp_out;
21
22 /* Builds the device nodes in /dev */
23 void eth_audio_init(void)
24 {
25         struct page *page;
26         ethaud_in = make_device("/dev/eth_audio_in", S_IRUSR, __S_IFBLK,
27                                 &ethaud_in_f_op);
28         ethaud_out = make_device("/dev/eth_audio_out", S_IRUSR | S_IWUSR, __S_IFBLK,
29                                  &ethaud_out_f_op);
30         /* make sure the inode tracks the right pm (not it's internal one) */
31         ethaud_in->f_dentry->d_inode->i_mapping->pm_op = &ethaud_pm_op;
32         ethaud_out->f_dentry->d_inode->i_mapping->pm_op = &ethaud_pm_op;
33         /* zalloc pages and associate them with the devices' inodes */
34         assert(!kpage_alloc(&page));
35         memset(page2kva(page), 0, PGSIZE);
36         ethaud_in->f_dentry->d_inode->i_fs_info = page;
37         assert(!kpage_alloc(&page));
38         memset(page2kva(page), 0, PGSIZE);
39         ethaud_out->f_dentry->d_inode->i_fs_info = page;
40 }
41
42 /* Lots of unnecessary copies.  For now, we need to build the ethernet frame,
43  * which means we prepend the ethernet header... */
44 static void eth_audio_sendpacket(void *buf)
45 {
46         int retval;
47         /* Fill the outgoing buffer (Copy #1.  2 is in send_frame, 3 is the DMA). */
48         memcpy(&eth_udp_out.payload, buf, ETH_AUDIO_PAYLOAD_SZ);
49         /* Make sure there is still a reasonable header. */
50         static_assert(sizeof(eth_udp_out) == ETH_AUDIO_FRAME_SZ);
51         /* Should compute the UDP checksum before sending the frame out.  The
52          * Eth-audio device shouldn't care (and Linux seems to be okay with packets
53          * that have no checksum (but not a wrong checksum)).  Technically, this
54          * hurts our performance a bit (and some NICs can offload this). */
55         eth_udp_out.udp_hdr.checksum = htons(udp_checksum(&eth_udp_out.ip_hdr,
56                                                           &eth_udp_out.udp_hdr));
57         /* Send it out */
58         retval = send_frame((const char*)&eth_udp_out, ETH_AUDIO_FRAME_SZ);
59         assert(retval >= 0);
60 }
61
62 /* This is how we know who to send the packet back to, since we have no real
63  * networking stack.  Lots of assumptions about how things stay in sync. */
64 static void eth_audio_prep_response(struct ethaud_udp_packet *incoming,
65                                     struct ethaud_udp_packet *outgoing)
66 {
67         /* If you're looking for optimizations, we can do this just once */
68         memcpy(&outgoing->eth_hdr.dst_mac, &incoming->eth_hdr.src_mac, 6); 
69         memcpy(&outgoing->eth_hdr.src_mac, device_mac, 6); 
70         outgoing->eth_hdr.eth_type = htons(IP_ETH_TYPE);
71         outgoing->ip_hdr.version = IPPROTO_IPV4;
72         outgoing->ip_hdr.hdr_len = ETH_AUDIO_IP_HDR_SZ >> 2;
73         outgoing->ip_hdr.tos = 0;
74         outgoing->ip_hdr.packet_len = htons(ETH_AUDIO_PAYLOAD_SZ + UDP_HDR_SZ +
75                                             ETH_AUDIO_IP_HDR_SZ);
76         outgoing->ip_hdr.id = htons(0);
77         outgoing->ip_hdr.flags_frags = htons(0);
78         outgoing->ip_hdr.ttl = DEFAULT_TTL;
79         outgoing->ip_hdr.protocol = IPPROTO_UDP;
80         /* Need a non-broadcast IP.  Picking one higher than the sender's */
81         outgoing->ip_hdr.src_addr = htonl(ntohl(incoming->ip_hdr.src_addr) + 1);
82         outgoing->ip_hdr.dst_addr = incoming->ip_hdr.src_addr;
83         /* Since the IP header is set already, we can compute the checksum. */
84         outgoing->ip_hdr.checksum = htons(ip_checksum(&outgoing->ip_hdr));
85         outgoing->udp_hdr.src_port = htons(ETH_AUDIO_SEND_PORT);
86         outgoing->udp_hdr.dst_port = incoming->udp_hdr.src_port;
87         outgoing->udp_hdr.length = htons(ETH_AUDIO_PAYLOAD_SZ + UDP_HDR_SZ);
88         outgoing->udp_hdr.checksum = htons(0);
89 }
90
91 /* This is called by net subsys when it detects an ethernet audio packet.  Make
92  * sure the in device has the contents, and send out the old frame. */
93 void eth_audio_newpacket(void *buf)
94 {
95         struct page *in_page, *out_page;
96         /* Put info from the packet into the outgoing packet */
97         eth_audio_prep_response((struct ethaud_udp_packet*)buf, &eth_udp_out);
98         in_page = (struct page*)ethaud_in->f_dentry->d_inode->i_fs_info;
99         out_page = (struct page*)ethaud_out->f_dentry->d_inode->i_fs_info;
100         /* third copy (1st being the NIC to RAM). */
101         memcpy(page2kva(in_page), buf + ETH_AUDIO_HEADER_OFF, ETH_AUDIO_PAYLOAD_SZ);
102         /* Send the current outbound packet (can consider doing this by fsync) */
103         eth_audio_sendpacket(page2kva(out_page));
104 }
105
106 /* mmap() calls this to do any FS specific mmap() work.  Since our files are
107  * defined to be only one page, we need to make sure they aren't mmapping too
108  * much, and that it isn't a PRIVATE mapping.
109  *
110  * Then we need to make sure the one page of the VMR is mapped in the page table
111  * (circumventing handle_page_fault and the page cache).  Avoiding the page
112  * cache means we won't need to worry about accidentally unmapping this under
113  * pressure and having to remap it (which will cause a readpage). */
114 int eth_audio_mmap(struct file *file, struct vm_region *vmr)
115 {
116         struct page *page = (struct page*)file->f_dentry->d_inode->i_fs_info;
117         /* Only allow mmaping from the start of the file */
118         if (vmr->vm_foff)
119                 return -1;
120         /* Only allow mmaping of a page */
121         if (vmr->vm_end - vmr->vm_base != PGSIZE)
122                 return -1;
123         /* No private mappings (would be ignored anyway) */
124         if (vmr->vm_flags & MAP_PRIVATE)
125                 return -1;
126         assert(page);
127         /* Get the PTE for the page this VMR represents */
128         pte_t *pte = pgdir_walk(vmr->vm_proc->env_pgdir, (void*)vmr->vm_base, 1);
129         if (!pte)
130                 return -ENOMEM;
131         /* If there was a page there, it should have be munmapp()d. */
132         assert(!PAGE_PRESENT(*pte));
133         /* update the page table */
134         int pte_prot = (vmr->vm_prot & PROT_WRITE) ? PTE_USER_RW :
135                        (vmr->vm_prot & (PROT_READ|PROT_EXEC)) ? PTE_USER_RO : 0;
136         /* Storing a reference to the page in the PTE */
137         page_incref(page);
138         *pte = PTE(page2ppn(page), PTE_P | pte_prot);
139         return 0;
140 }
141
142 /* This shouldn't be called.  It could be if we ever handled a PF on the device,
143  * and the device wasn't present in the page table.  This should not happen,
144  * since ea_mmap() should have sorted that out.  Even if there was an unmap()
145  * then a new mmap(), it still shouldn't happen. */
146 int eth_audio_readpage(struct page_map *pm, struct page *page)
147 {
148         warn("Eth audio readpage!  (Did you page fault?)");
149         return -1;
150 }
151
152 /* File operations */
153 struct file_operations ethaud_in_f_op = {
154         dev_c_llseek,   /* Errors out, can't llseek */
155         0,                              /* Can't read, only mmap */
156         0,                              /* Can't write, only mmap */
157         kfs_readdir,    /* this will fail gracefully */
158         eth_audio_mmap,
159         kfs_open,
160         kfs_flush,
161         kfs_release,
162         0,                              /* fsync - makes no sense */
163         kfs_poll,
164         0,      /* readv */
165         0,      /* writev */
166         kfs_sendpage,
167         kfs_check_flags,
168 };
169
170 struct file_operations ethaud_out_f_op = {
171         dev_c_llseek,   /* Errors out, can't llseek */
172         0,                              /* Can't read, only mmap */
173         0,                              /* Can't write, only mmap */
174         kfs_readdir,    /* this will fail gracefully */
175         eth_audio_mmap,
176         kfs_open,
177         kfs_flush,
178         kfs_release,
179         0,                              /* fsync - TODO: make this send the packet */
180         kfs_poll,
181         0,      /* readv */
182         0,      /* writev */
183         kfs_sendpage,
184         kfs_check_flags,
185 };
186
187 /* Eth audio page map ops: */
188 struct page_map_operations ethaud_pm_op = {
189         eth_audio_readpage,
190 };
191