1 /*
2  * Copyright (c) 2015 Travis Geiselbrecht
3  *
4  * Use of this source code is governed by a MIT-style
5  * license that can be found in the LICENSE file or at
6  * https://opensource.org/licenses/MIT
7  */
8 #include "lkboot.h"
9 
10 #include <stdio.h>
11 #include <lk/debug.h>
12 #include <string.h>
13 #include <lk/compiler.h>
14 #include <lk/err.h>
15 #include <assert.h>
16 #include <lk/trace.h>
17 #include <stdlib.h>
18 #include <lib/cbuf.h>
19 #include <app/lkboot.h>
20 #include <arch/arm/dcc.h>
21 #include <kernel/vm.h>
22 #include <kernel/mutex.h>
23 
24 #include "pdcc.h"
25 
26 #define LOCAL_TRACE 0
27 
28 static struct pdcc_buffer_descriptor buffer_desc __ALIGNED(256);
29 static paddr_t buffer_desc_phys;
30 
31 #define DCC_BUFLEN 256
32 
33 static uint8_t htod_buffer[DCC_BUFLEN] __ALIGNED(CACHE_LINE);
34 static uint8_t dtoh_buffer[DCC_BUFLEN] __ALIGNED(CACHE_LINE);
35 
36 static uint htod_index;
37 static uint htod_pos;
38 static bool dtoh_filled;
39 
send_pdcc_command(uint32_t opcode,uint32_t data)40 static void send_pdcc_command(uint32_t opcode, uint32_t data) {
41     uint32_t word;
42 
43     word = PDCC_VALID |
44            ((opcode & 0x7f) << PDCC_OPCODE_SHIFT) |
45            (data & 0x00ffffff);
46 
47     // XXX may block forever
48     LTRACEF("sending 0x%x\n", word);
49     arm_dcc_write(&word, 1, INFINITE_TIME);
50 }
51 
send_buffer_header(void)52 static void send_buffer_header(void) {
53     send_pdcc_command(PDCC_OP_BUF_HEADER, buffer_desc_phys / 256);
54 }
55 
send_reset(void)56 static void send_reset(void) {
57     send_pdcc_command(PDCC_OP_RESET, 0);
58 }
59 
send_out_index_update(uint32_t index)60 static void send_out_index_update(uint32_t index) {
61     send_pdcc_command(PDCC_OP_UPDATE_OUT_INDEX, index);
62 }
63 
send_buffer_consumed(void)64 static void send_buffer_consumed(void) {
65     send_pdcc_command(PDCC_OP_CONSUMED_IN, 0);
66 }
67 
68 #define DCC_PROCESS_RESET 1
dcc_process_opcode(uint32_t word)69 static int dcc_process_opcode(uint32_t word) {
70     int ret = 0;
71 
72     if (word & PDCC_VALID) {
73         uint32_t opcode = PDCC_OPCODE(word);
74         uint32_t data = PDCC_DATA(word);
75         LTRACEF("word 0x%x, opcode 0x%x, data 0x%x\n", word, opcode, data);
76         switch (opcode) {
77             case PDCC_OP_RESET:
78                 htod_index = 0;
79                 htod_pos = 0;
80                 dtoh_filled = false;
81 
82                 // try to send the buffer header
83                 send_buffer_header();
84                 ret = DCC_PROCESS_RESET;
85                 break;
86             case PDCC_OP_BUF_HEADER:
87                 // we shouldn't get this
88                 break;
89 
90             case PDCC_OP_UPDATE_OUT_INDEX:
91                 if (data > DCC_BUFLEN) {
92                     // out of range
93                     send_reset();
94                 } else {
95                     htod_index = data;
96                     htod_pos = 0;
97                     arch_invalidate_cache_range((vaddr_t)htod_buffer, DCC_BUFLEN);
98                 }
99                 break;
100 
101             case PDCC_OP_CONSUMED_IN:
102                 arch_invalidate_cache_range((vaddr_t)dtoh_buffer, DCC_BUFLEN);
103                 dtoh_filled = false;
104                 break;
105             default:
106                 TRACEF("bad opcode from host 0x%x\n", opcode);
107                 send_reset();
108         }
109     }
110 
111     return ret;
112 }
113 
dcc_read(void * unused,void * _data,size_t len)114 static ssize_t dcc_read(void *unused, void *_data, size_t len) {
115     unsigned char *data = _data;
116     size_t pos = 0;
117     uint32_t dcc;
118 
119     LTRACEF("buf %p, len %zu, htod_pos %u, htod_index %u\n", _data, len, htod_pos, htod_index);
120 
121     lk_time_t timeout = 0; // first dcc command should be with no timeout
122     while (pos < len) {
123         // process a dcc command
124         ssize_t err = arm_dcc_read(&dcc, 1, timeout);
125         if (err > 0) {
126             err = dcc_process_opcode(dcc);
127             if (err == DCC_PROCESS_RESET) {
128                 return ERR_IO;
129             }
130         }
131 
132         // see if there is any data in the incoming buffer
133         if (htod_index > 0) {
134             size_t tocopy = MIN(htod_index - htod_pos, len - pos);
135 
136             memcpy(&data[pos], &htod_buffer[htod_pos], tocopy);
137             pos += tocopy;
138             htod_pos += tocopy;
139 
140             // if we consumed everything, tell the host we're done with the buffer
141             if (htod_pos == htod_index) {
142                 send_buffer_consumed();
143                 htod_index = 0;
144                 htod_pos = 0;
145                 arch_invalidate_cache_range((vaddr_t)htod_buffer, DCC_BUFLEN);
146             }
147         }
148 
149         timeout = 1000;
150     }
151 
152     return 0;
153 }
154 
dcc_write(void * unused,const void * _data,size_t len)155 static ssize_t dcc_write(void *unused, const void *_data, size_t len) {
156     const unsigned char *data = _data;
157     size_t pos = 0;
158 
159     LTRACEF("buf %p, len %zu\n", _data, len);
160 
161     while (pos < len) {
162         LTRACEF("pos %zu, len %zu, dtoh_filled %d\n", pos, len, dtoh_filled);
163         if (!dtoh_filled) {
164             // put as much data as we can in the outgoing buffer
165             size_t tocopy = MIN(len, DCC_BUFLEN);
166 
167             LTRACEF("tocopy %zu\n", tocopy);
168             memcpy(dtoh_buffer, data, tocopy);
169             arch_clean_cache_range((vaddr_t)dtoh_buffer, DCC_BUFLEN);
170             send_out_index_update(tocopy);
171             dtoh_filled = true;
172 
173             pos += tocopy;
174         }
175 
176         // process a dcc command
177         uint32_t dcc;
178         ssize_t err = arm_dcc_read(&dcc, 1, 1000);
179         if (err > 0) {
180             err = dcc_process_opcode(dcc);
181             if (err == DCC_PROCESS_RESET) {
182                 return ERR_IO;
183             }
184         }
185     }
186 
187     return pos;
188 }
189 
lkboot_check_dcc_open(void)190 lkb_t *lkboot_check_dcc_open(void) {
191     lkb_t *lkb = NULL;
192 
193     // read a dcc op and process it
194     {
195         uint32_t dcc;
196         ssize_t err = arm_dcc_read(&dcc, 1, 0);
197         if (err > 0) {
198             err = dcc_process_opcode(dcc);
199         }
200     }
201 
202     if (htod_index > 0) {
203         // we have data, construct a lkb and return it
204         LTRACEF("we have data on dcc, starting command handler\n");
205         lkb = lkboot_create_lkb(NULL, dcc_read, dcc_write);
206     }
207 
208     return lkb;
209 }
210 
lkboot_dcc_init(void)211 void lkboot_dcc_init(void) {
212     paddr_t pa;
213     __UNUSED status_t err;
214 
215     buffer_desc.version = PDCC_VERSION;
216 
217     pa = vaddr_to_paddr(htod_buffer);
218     DEBUG_ASSERT(pa);
219 
220     buffer_desc.htod_buffer_phys = pa;
221     buffer_desc.htod_buffer_len = DCC_BUFLEN;
222 
223     pa = vaddr_to_paddr(dtoh_buffer);
224     DEBUG_ASSERT(pa);
225 
226     buffer_desc.dtoh_buffer_phys = pa;
227     buffer_desc.dtoh_buffer_len = DCC_BUFLEN;
228 
229     buffer_desc_phys = vaddr_to_paddr(&buffer_desc);
230     DEBUG_ASSERT(buffer_desc_phys);
231 
232     arch_clean_cache_range((vaddr_t)&buffer_desc, sizeof(buffer_desc));
233 }
234 
235