1 // Copyright 2017 The Fuchsia Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include <ddk/debug.h>
6 #include <fbl/auto_lock.h>
7 
8 #include "dwc3.h"
9 #include "dwc3-regs.h"
10 #include "dwc3-types.h"
11 
12 #include <stdio.h>
13 #include <string.h>
14 
15 #define EP0_LOCK(dwc)   (&(dwc)->eps[EP0_OUT].lock)
16 
dwc3_queue_setup_locked(dwc3_t * dwc)17 static void dwc3_queue_setup_locked(dwc3_t* dwc) {
18     io_buffer_cache_flush_invalidate(&dwc->ep0_buffer, 0, sizeof(usb_setup_t));
19     dwc3_ep_start_transfer(dwc, EP0_OUT, TRB_TRBCTL_SETUP, io_buffer_phys(&dwc->ep0_buffer),
20                            sizeof(usb_setup_t), false);
21     dwc->ep0_state = EP0_STATE_SETUP;
22 }
23 
dwc3_ep0_init(dwc3_t * dwc)24 zx_status_t dwc3_ep0_init(dwc3_t* dwc) {
25     // fifo only needed for physical endpoint 0
26     zx_status_t status = dwc3_ep_fifo_init(dwc, EP0_OUT);
27     if (status != ZX_OK) {
28         return status;
29     }
30 
31     for (unsigned i = EP0_OUT; i <= EP0_IN; i++) {
32         dwc3_endpoint_t* ep = &dwc->eps[i];
33         ep->enabled = false;
34         ep->max_packet_size = EP0_MAX_PACKET_SIZE;
35         ep->type = USB_ENDPOINT_CONTROL;
36         ep->interval = 0;
37     }
38 
39     return ZX_OK;
40 }
41 
dwc3_ep0_reset(dwc3_t * dwc)42 void dwc3_ep0_reset(dwc3_t* dwc) {
43     fbl::AutoLock lock(EP0_LOCK(dwc));
44 
45     dwc3_cmd_ep_end_transfer(dwc, EP0_OUT);
46     dwc->ep0_state = EP0_STATE_NONE;
47 }
48 
dwc3_ep0_start(dwc3_t * dwc)49 void dwc3_ep0_start(dwc3_t* dwc) {
50     fbl::AutoLock lock(EP0_LOCK(dwc));
51 
52     dwc3_cmd_start_new_config(dwc, EP0_OUT, 0);
53     dwc3_ep_set_config(dwc, EP0_OUT, true);
54     dwc3_ep_set_config(dwc, EP0_IN, true);
55 
56     dwc3_queue_setup_locked(dwc);
57 }
58 
dwc3_handle_setup(dwc3_t * dwc,usb_setup_t * setup,void * buffer,size_t length,size_t * out_actual)59 static zx_status_t dwc3_handle_setup(dwc3_t* dwc, usb_setup_t* setup, void* buffer, size_t length,
60                                      size_t* out_actual) {
61     if (setup->bmRequestType == (USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE)) {
62         // handle some special setup requests in this driver
63         switch (setup->bRequest) {
64         case USB_REQ_SET_ADDRESS:
65             zxlogf(TRACE, "SET_ADDRESS %d\n", setup->wValue);
66             dwc3_set_address(dwc, setup->wValue);
67             *out_actual = 0;
68             return ZX_OK;
69         case USB_REQ_SET_CONFIGURATION: {
70             zxlogf(TRACE, "SET_CONFIGURATION %d\n", setup->wValue);
71             dwc3_reset_configuration(dwc);
72             dwc->configured = false;
73             zx_status_t status = usb_dci_interface_control(&dwc->dci_intf, setup, nullptr, 0,
74                                                            nullptr, 0, out_actual);
75             if (status == ZX_OK && setup->wValue) {
76                 dwc->configured = true;
77                 dwc3_start_eps(dwc);
78             }
79             return status;
80         }
81         default:
82             // fall through to usb_dci_interface_control()
83             break;
84         }
85     } else if (setup->bmRequestType == (USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_INTERFACE) &&
86                setup->bRequest == USB_REQ_SET_INTERFACE) {
87         zxlogf(TRACE, "SET_INTERFACE %d\n", setup->wValue);
88         dwc3_reset_configuration(dwc);
89         dwc->configured = false;
90         zx_status_t status = usb_dci_interface_control(&dwc->dci_intf, setup, nullptr, 0, nullptr,
91                                                        0, out_actual);
92         if (status == ZX_OK) {
93             dwc->configured = true;
94             dwc3_start_eps(dwc);
95         }
96         return status;
97     }
98 
99     if ((setup->bmRequestType & USB_DIR_MASK) == USB_DIR_IN) {
100         return usb_dci_interface_control(&dwc->dci_intf, setup, nullptr, 0, buffer, length,
101                                          out_actual);
102     } else {
103         return usb_dci_interface_control(&dwc->dci_intf, setup, buffer, length, nullptr, 0,
104                                          out_actual);
105     }
106 }
107 
dwc3_ep0_xfer_not_ready(dwc3_t * dwc,unsigned ep_num,unsigned stage)108 void dwc3_ep0_xfer_not_ready(dwc3_t* dwc, unsigned ep_num, unsigned stage) {
109     fbl::AutoLock lock(EP0_LOCK(dwc));
110 
111     switch (dwc->ep0_state) {
112     case EP0_STATE_SETUP:
113         if (stage == DEPEVT_XFER_NOT_READY_STAGE_DATA ||
114             stage == DEPEVT_XFER_NOT_READY_STAGE_STATUS) {
115             // Stall if we receive xfer not ready data/status while waiting for setup to complete
116            dwc3_cmd_ep_set_stall(dwc, EP0_OUT);
117            dwc3_queue_setup_locked(dwc);
118         }
119         break;
120     case EP0_STATE_DATA_OUT:
121         if (ep_num == EP0_IN && stage == DEPEVT_XFER_NOT_READY_STAGE_DATA) {
122             // end transfer and stall if we receive xfer not ready in the opposite direction
123             dwc3_cmd_ep_end_transfer(dwc, EP0_OUT);
124             dwc3_cmd_ep_set_stall(dwc, EP0_OUT);
125             dwc3_queue_setup_locked(dwc);
126         }
127         break;
128     case EP0_STATE_DATA_IN:
129         if (ep_num == EP0_OUT && stage == DEPEVT_XFER_NOT_READY_STAGE_DATA) {
130             // end transfer and stall if we receive xfer not ready in the opposite direction
131             dwc3_cmd_ep_end_transfer(dwc, EP0_IN);
132             dwc3_cmd_ep_set_stall(dwc, EP0_OUT);
133             dwc3_queue_setup_locked(dwc);
134         }
135         break;
136     case EP0_STATE_WAIT_NRDY_OUT:
137         if (ep_num == EP0_OUT) {
138             if (dwc->cur_setup.wLength > 0) {
139                 dwc3_ep_start_transfer(dwc, EP0_OUT, TRB_TRBCTL_STATUS_3, 0, 0, false);
140             } else {
141                 dwc3_ep_start_transfer(dwc, EP0_OUT, TRB_TRBCTL_STATUS_2, 0, 0, false);
142             }
143             dwc->ep0_state = EP0_STATE_STATUS;
144         }
145         break;
146     case EP0_STATE_WAIT_NRDY_IN:
147         if (ep_num == EP0_IN) {
148             if (dwc->cur_setup.wLength > 0) {
149                 dwc3_ep_start_transfer(dwc, EP0_IN, TRB_TRBCTL_STATUS_3, 0, 0, false);
150             } else {
151                 dwc3_ep_start_transfer(dwc, EP0_IN, TRB_TRBCTL_STATUS_2, 0, 0, false);
152             }
153             dwc->ep0_state = EP0_STATE_STATUS;
154         }
155         break;
156     default:
157         zxlogf(ERROR, "dwc3_ep0_xfer_not_ready unhandled state %u\n", dwc->ep0_state);
158         break;
159     }
160 }
161 
dwc3_ep0_xfer_complete(dwc3_t * dwc,unsigned ep_num)162 void dwc3_ep0_xfer_complete(dwc3_t* dwc, unsigned ep_num) {
163     fbl::AutoLock lock(EP0_LOCK(dwc));
164 
165     switch (dwc->ep0_state) {
166     case EP0_STATE_SETUP: {
167         usb_setup_t* setup = &dwc->cur_setup;
168 
169         void* vaddr = io_buffer_virt(&dwc->ep0_buffer);
170         zx_paddr_t paddr = io_buffer_phys(&dwc->ep0_buffer);
171         memcpy(setup, vaddr, sizeof(*setup));
172 
173         zxlogf(TRACE, "got setup: type: 0x%02X req: %d value: %d index: %d length: %d\n",
174                 setup->bmRequestType, setup->bRequest, setup->wValue, setup->wIndex,
175                 setup->wLength);
176 
177         bool is_out = ((setup->bmRequestType & USB_DIR_MASK) == USB_DIR_OUT);
178         if (setup->wLength > 0 && is_out) {
179             // queue a read for the data phase
180            io_buffer_cache_flush_invalidate(&dwc->ep0_buffer, 0, dwc->ep0_buffer.size);
181             dwc3_ep_start_transfer(dwc, EP0_OUT, TRB_TRBCTL_CONTROL_DATA, paddr,
182                                    dwc->ep0_buffer.size, false);
183             dwc->ep0_state = EP0_STATE_DATA_OUT;
184         } else {
185             size_t actual;
186             zx_status_t status = dwc3_handle_setup(dwc, setup, vaddr, dwc->ep0_buffer.size,
187                                                    &actual);
188             zxlogf(TRACE, "dwc3_handle_setup returned %d actual %zu\n", status, actual);
189             if (status != ZX_OK) {
190                 dwc3_cmd_ep_set_stall(dwc, EP0_OUT);
191                 dwc3_queue_setup_locked(dwc);
192                 break;
193             }
194 
195             if (setup->wLength > 0) {
196                 // queue a write for the data phase
197                 io_buffer_cache_flush(&dwc->ep0_buffer, 0, actual);
198                 dwc3_ep_start_transfer(dwc, EP0_IN, TRB_TRBCTL_CONTROL_DATA, paddr, actual, false);
199                 dwc->ep0_state = EP0_STATE_DATA_IN;
200             } else {
201                 dwc->ep0_state = EP0_STATE_WAIT_NRDY_IN;
202             }
203        }
204        break;
205     }
206     case EP0_STATE_DATA_OUT: {
207         dwc3_endpoint_t* ep = &dwc->eps[ep_num];
208         dwc3_trb_t  trb;
209         dwc_ep_read_trb(ep, ep->fifo.current, &trb);
210         ep->fifo.current = nullptr;
211         zx_off_t received = dwc->ep0_buffer.size - TRB_BUFSIZ(trb.status);
212 
213         // "actual" is unused for OUT transfers.
214         size_t actual;
215         zx_status_t status = dwc3_handle_setup(dwc, &dwc->cur_setup,
216                                                io_buffer_virt(&dwc->ep0_buffer), received,
217                                                &actual);
218         if (status != ZX_OK) {
219             dwc3_cmd_ep_set_stall(dwc, EP0_OUT);
220             dwc3_queue_setup_locked(dwc);
221             break;
222         }
223         dwc->ep0_state = EP0_STATE_WAIT_NRDY_IN;
224         break;
225     }
226     case EP0_STATE_DATA_IN:
227         dwc->ep0_state = EP0_STATE_WAIT_NRDY_OUT;
228         break;
229     case EP0_STATE_STATUS:
230         dwc3_queue_setup_locked(dwc);
231         break;
232     default:
233         break;
234     }
235 }
236