Revision f8af1e88
b/hw/usb-uhci.c | ||
---|---|---|
95 | 95 |
#endif |
96 | 96 |
|
97 | 97 |
typedef struct UHCIState UHCIState; |
98 |
typedef struct UHCIAsync UHCIAsync; |
|
99 |
typedef struct UHCIQueue UHCIQueue; |
|
98 | 100 |
|
99 | 101 |
/* |
100 | 102 |
* Pending async transaction. |
101 | 103 |
* 'packet' must be the first field because completion |
102 | 104 |
* handler does "(UHCIAsync *) pkt" cast. |
103 | 105 |
*/ |
104 |
typedef struct UHCIAsync { |
|
106 |
|
|
107 |
struct UHCIAsync { |
|
105 | 108 |
USBPacket packet; |
106 | 109 |
QEMUSGList sgl; |
107 |
UHCIState *uhci;
|
|
110 |
UHCIQueue *queue;
|
|
108 | 111 |
QTAILQ_ENTRY(UHCIAsync) next; |
109 | 112 |
uint32_t td; |
110 |
uint32_t token; |
|
111 |
int8_t valid; |
|
112 | 113 |
uint8_t isoc; |
113 | 114 |
uint8_t done; |
114 |
} UHCIAsync; |
|
115 |
}; |
|
116 |
|
|
117 |
struct UHCIQueue { |
|
118 |
uint32_t token; |
|
119 |
UHCIState *uhci; |
|
120 |
QTAILQ_ENTRY(UHCIQueue) next; |
|
121 |
QTAILQ_HEAD(, UHCIAsync) asyncs; |
|
122 |
int8_t valid; |
|
123 |
}; |
|
115 | 124 |
|
116 | 125 |
typedef struct UHCIPort { |
117 | 126 |
USBPort port; |
... | ... | |
137 | 146 |
uint32_t pending_int_mask; |
138 | 147 |
|
139 | 148 |
/* Active packets */ |
140 |
QTAILQ_HEAD(,UHCIAsync) async_pending;
|
|
149 |
QTAILQ_HEAD(, UHCIQueue) queues;
|
|
141 | 150 |
uint8_t num_ports_vmstate; |
142 | 151 |
|
143 | 152 |
/* Properties */ |
... | ... | |
157 | 166 |
uint32_t el_link; |
158 | 167 |
} UHCI_QH; |
159 | 168 |
|
160 |
static UHCIAsync *uhci_async_alloc(UHCIState *s) |
|
169 |
static inline int32_t uhci_queue_token(UHCI_TD *td) |
|
170 |
{ |
|
171 |
/* covers ep, dev, pid -> identifies the endpoint */ |
|
172 |
return td->token & 0x7ffff; |
|
173 |
} |
|
174 |
|
|
175 |
static UHCIQueue *uhci_queue_get(UHCIState *s, UHCI_TD *td) |
|
176 |
{ |
|
177 |
uint32_t token = uhci_queue_token(td); |
|
178 |
UHCIQueue *queue; |
|
179 |
|
|
180 |
QTAILQ_FOREACH(queue, &s->queues, next) { |
|
181 |
if (queue->token == token) { |
|
182 |
return queue; |
|
183 |
} |
|
184 |
} |
|
185 |
|
|
186 |
queue = g_new0(UHCIQueue, 1); |
|
187 |
queue->uhci = s; |
|
188 |
queue->token = token; |
|
189 |
QTAILQ_INIT(&queue->asyncs); |
|
190 |
QTAILQ_INSERT_HEAD(&s->queues, queue, next); |
|
191 |
return queue; |
|
192 |
} |
|
193 |
|
|
194 |
static void uhci_queue_free(UHCIQueue *queue) |
|
195 |
{ |
|
196 |
UHCIState *s = queue->uhci; |
|
197 |
|
|
198 |
QTAILQ_REMOVE(&s->queues, queue, next); |
|
199 |
g_free(queue); |
|
200 |
} |
|
201 |
|
|
202 |
static UHCIAsync *uhci_async_alloc(UHCIQueue *queue) |
|
161 | 203 |
{ |
162 | 204 |
UHCIAsync *async = g_new0(UHCIAsync, 1); |
163 | 205 |
|
164 |
async->uhci = s;
|
|
206 |
async->queue = queue;
|
|
165 | 207 |
usb_packet_init(&async->packet); |
166 |
pci_dma_sglist_init(&async->sgl, &s->dev, 1);
|
|
208 |
pci_dma_sglist_init(&async->sgl, &queue->uhci->dev, 1);
|
|
167 | 209 |
|
168 | 210 |
return async; |
169 | 211 |
} |
170 | 212 |
|
171 |
static void uhci_async_free(UHCIState *s, UHCIAsync *async)
|
|
213 |
static void uhci_async_free(UHCIAsync *async) |
|
172 | 214 |
{ |
173 | 215 |
usb_packet_cleanup(&async->packet); |
174 | 216 |
qemu_sglist_destroy(&async->sgl); |
175 | 217 |
g_free(async); |
176 | 218 |
} |
177 | 219 |
|
178 |
static void uhci_async_link(UHCIState *s, UHCIAsync *async)
|
|
220 |
static void uhci_async_link(UHCIAsync *async) |
|
179 | 221 |
{ |
180 |
QTAILQ_INSERT_HEAD(&s->async_pending, async, next); |
|
222 |
UHCIQueue *queue = async->queue; |
|
223 |
QTAILQ_INSERT_TAIL(&queue->asyncs, async, next); |
|
181 | 224 |
} |
182 | 225 |
|
183 |
static void uhci_async_unlink(UHCIState *s, UHCIAsync *async)
|
|
226 |
static void uhci_async_unlink(UHCIAsync *async) |
|
184 | 227 |
{ |
185 |
QTAILQ_REMOVE(&s->async_pending, async, next); |
|
228 |
UHCIQueue *queue = async->queue; |
|
229 |
QTAILQ_REMOVE(&queue->asyncs, async, next); |
|
186 | 230 |
} |
187 | 231 |
|
188 |
static void uhci_async_cancel(UHCIState *s, UHCIAsync *async)
|
|
232 |
static void uhci_async_cancel(UHCIAsync *async) |
|
189 | 233 |
{ |
190 | 234 |
DPRINTF("uhci: cancel td 0x%x token 0x%x done %u\n", |
191 | 235 |
async->td, async->token, async->done); |
192 | 236 |
|
193 | 237 |
if (!async->done) |
194 | 238 |
usb_cancel_packet(&async->packet); |
195 |
uhci_async_free(s, async);
|
|
239 |
uhci_async_free(async); |
|
196 | 240 |
} |
197 | 241 |
|
198 | 242 |
/* |
199 | 243 |
* Mark all outstanding async packets as invalid. |
200 | 244 |
* This is used for canceling them when TDs are removed by the HCD. |
201 | 245 |
*/ |
202 |
static UHCIAsync *uhci_async_validate_begin(UHCIState *s)
|
|
246 |
static void uhci_async_validate_begin(UHCIState *s)
|
|
203 | 247 |
{ |
204 |
UHCIAsync *async;
|
|
248 |
UHCIQueue *queue;
|
|
205 | 249 |
|
206 |
QTAILQ_FOREACH(async, &s->async_pending, next) {
|
|
207 |
async->valid--;
|
|
250 |
QTAILQ_FOREACH(queue, &s->queues, next) {
|
|
251 |
queue->valid--;
|
|
208 | 252 |
} |
209 |
return NULL; |
|
210 | 253 |
} |
211 | 254 |
|
212 | 255 |
/* |
... | ... | |
214 | 257 |
*/ |
215 | 258 |
static void uhci_async_validate_end(UHCIState *s) |
216 | 259 |
{ |
217 |
UHCIAsync *curr, *n; |
|
260 |
UHCIQueue *queue, *n; |
|
261 |
UHCIAsync *async; |
|
218 | 262 |
|
219 |
QTAILQ_FOREACH_SAFE(curr, &s->async_pending, next, n) {
|
|
220 |
if (curr->valid > 0) {
|
|
263 |
QTAILQ_FOREACH_SAFE(queue, &s->queues, next, n) {
|
|
264 |
if (queue->valid > 0) {
|
|
221 | 265 |
continue; |
222 | 266 |
} |
223 |
uhci_async_unlink(s, curr); |
|
224 |
uhci_async_cancel(s, curr); |
|
267 |
while (!QTAILQ_EMPTY(&queue->asyncs)) { |
|
268 |
async = QTAILQ_FIRST(&queue->asyncs); |
|
269 |
uhci_async_unlink(async); |
|
270 |
uhci_async_cancel(async); |
|
271 |
} |
|
272 |
uhci_queue_free(queue); |
|
225 | 273 |
} |
226 | 274 |
} |
227 | 275 |
|
228 | 276 |
static void uhci_async_cancel_device(UHCIState *s, USBDevice *dev) |
229 | 277 |
{ |
278 |
UHCIQueue *queue; |
|
230 | 279 |
UHCIAsync *curr, *n; |
231 | 280 |
|
232 |
QTAILQ_FOREACH_SAFE(curr, &s->async_pending, next, n) { |
|
233 |
if (!usb_packet_is_inflight(&curr->packet) || |
|
234 |
curr->packet.ep->dev != dev) { |
|
235 |
continue; |
|
281 |
QTAILQ_FOREACH(queue, &s->queues, next) { |
|
282 |
QTAILQ_FOREACH_SAFE(curr, &queue->asyncs, next, n) { |
|
283 |
if (!usb_packet_is_inflight(&curr->packet) || |
|
284 |
curr->packet.ep->dev != dev) { |
|
285 |
continue; |
|
286 |
} |
|
287 |
uhci_async_unlink(curr); |
|
288 |
uhci_async_cancel(curr); |
|
236 | 289 |
} |
237 |
uhci_async_unlink(s, curr); |
|
238 |
uhci_async_cancel(s, curr); |
|
239 | 290 |
} |
240 | 291 |
} |
241 | 292 |
|
242 | 293 |
static void uhci_async_cancel_all(UHCIState *s) |
243 | 294 |
{ |
295 |
UHCIQueue *queue; |
|
244 | 296 |
UHCIAsync *curr, *n; |
245 | 297 |
|
246 |
QTAILQ_FOREACH_SAFE(curr, &s->async_pending, next, n) { |
|
247 |
uhci_async_unlink(s, curr); |
|
248 |
uhci_async_cancel(s, curr); |
|
298 |
QTAILQ_FOREACH(queue, &s->queues, next) { |
|
299 |
QTAILQ_FOREACH_SAFE(curr, &queue->asyncs, next, n) { |
|
300 |
uhci_async_unlink(curr); |
|
301 |
uhci_async_cancel(curr); |
|
302 |
} |
|
249 | 303 |
} |
250 | 304 |
} |
251 | 305 |
|
252 |
static UHCIAsync *uhci_async_find_td(UHCIState *s, uint32_t addr, uint32_t token)
|
|
306 |
static UHCIAsync *uhci_async_find_td(UHCIState *s, uint32_t addr, UHCI_TD *td)
|
|
253 | 307 |
{ |
308 |
uint32_t token = uhci_queue_token(td); |
|
309 |
UHCIQueue *queue; |
|
254 | 310 |
UHCIAsync *async; |
255 |
UHCIAsync *match = NULL; |
|
256 |
int count = 0; |
|
257 |
|
|
258 |
/* |
|
259 |
* We're looking for the best match here. ie both td addr and token. |
|
260 |
* Otherwise we return last good match. ie just token. |
|
261 |
* It's ok to match just token because it identifies the transaction |
|
262 |
* rather well, token includes: device addr, endpoint, size, etc. |
|
263 |
* |
|
264 |
* Also since we queue async transactions in reverse order by returning |
|
265 |
* last good match we restores the order. |
|
266 |
* |
|
267 |
* It's expected that we wont have a ton of outstanding transactions. |
|
268 |
* If we ever do we'd want to optimize this algorithm. |
|
269 |
*/ |
|
270 |
|
|
271 |
QTAILQ_FOREACH(async, &s->async_pending, next) { |
|
272 |
if (async->token == token) { |
|
273 |
/* Good match */ |
|
274 |
match = async; |
|
275 | 311 |
|
276 |
if (async->td == addr) { |
|
277 |
/* Best match */ |
|
278 |
break; |
|
279 |
} |
|
312 |
QTAILQ_FOREACH(queue, &s->queues, next) { |
|
313 |
if (queue->token == token) { |
|
314 |
break; |
|
280 | 315 |
} |
281 |
count++; |
|
316 |
} |
|
317 |
if (queue == NULL) { |
|
318 |
return NULL; |
|
282 | 319 |
} |
283 | 320 |
|
284 |
if (count > 64) |
|
285 |
fprintf(stderr, "uhci: warning lots of async transactions\n"); |
|
321 |
QTAILQ_FOREACH(async, &queue->asyncs, next) { |
|
322 |
if (async->td == addr) { |
|
323 |
return async; |
|
324 |
} |
|
325 |
} |
|
286 | 326 |
|
287 |
return match;
|
|
327 |
return NULL;
|
|
288 | 328 |
} |
289 | 329 |
|
290 | 330 |
static void uhci_update_irq(UHCIState *s) |
... | ... | |
753 | 793 |
{ |
754 | 794 |
UHCIAsync *async; |
755 | 795 |
int len = 0, max_len; |
756 |
uint8_t pid, isoc; |
|
757 |
uint32_t token; |
|
796 |
uint8_t pid; |
|
758 | 797 |
USBDevice *dev; |
759 | 798 |
USBEndpoint *ep; |
760 | 799 |
|
... | ... | |
762 | 801 |
if (!(td->ctrl & TD_CTRL_ACTIVE)) |
763 | 802 |
return 1; |
764 | 803 |
|
765 |
/* token field is not unique for isochronous requests, |
|
766 |
* so use the destination buffer |
|
767 |
*/ |
|
768 |
if (td->ctrl & TD_CTRL_IOS) { |
|
769 |
token = td->buffer; |
|
770 |
isoc = 1; |
|
771 |
} else { |
|
772 |
token = td->token; |
|
773 |
isoc = 0; |
|
774 |
} |
|
775 |
|
|
776 |
async = uhci_async_find_td(s, addr, token); |
|
804 |
async = uhci_async_find_td(s, addr, td); |
|
777 | 805 |
if (async) { |
778 | 806 |
/* Already submitted */ |
779 |
async->valid = 32; |
|
807 |
async->queue->valid = 32;
|
|
780 | 808 |
|
781 | 809 |
if (!async->done) |
782 | 810 |
return 1; |
783 | 811 |
|
784 |
uhci_async_unlink(s, async);
|
|
812 |
uhci_async_unlink(async); |
|
785 | 813 |
goto done; |
786 | 814 |
} |
787 | 815 |
|
788 | 816 |
/* Allocate new packet */ |
789 |
async = uhci_async_alloc(s);
|
|
817 |
async = uhci_async_alloc(uhci_queue_get(s, td));
|
|
790 | 818 |
if (!async) |
791 | 819 |
return 1; |
792 | 820 |
|
793 | 821 |
/* valid needs to be large enough to handle 10 frame delay |
794 | 822 |
* for initial isochronous requests |
795 | 823 |
*/ |
796 |
async->valid = 32; |
|
824 |
async->queue->valid = 32;
|
|
797 | 825 |
async->td = addr; |
798 |
async->token = token; |
|
799 |
async->isoc = isoc; |
|
826 |
async->isoc = td->ctrl & TD_CTRL_IOS; |
|
800 | 827 |
|
801 | 828 |
max_len = ((td->token >> 21) + 1) & 0x7ff; |
802 | 829 |
pid = td->token & 0xff; |
... | ... | |
821 | 848 |
|
822 | 849 |
default: |
823 | 850 |
/* invalid pid : frame interrupted */ |
824 |
uhci_async_free(s, async);
|
|
851 |
uhci_async_free(async); |
|
825 | 852 |
s->status |= UHCI_STS_HCPERR; |
826 | 853 |
uhci_update_irq(s); |
827 | 854 |
return -1; |
828 | 855 |
} |
829 | 856 |
|
830 | 857 |
if (len == USB_RET_ASYNC) { |
831 |
uhci_async_link(s, async);
|
|
858 |
uhci_async_link(async); |
|
832 | 859 |
return 2; |
833 | 860 |
} |
834 | 861 |
|
... | ... | |
837 | 864 |
done: |
838 | 865 |
len = uhci_complete_td(s, td, async, int_mask); |
839 | 866 |
usb_packet_unmap(&async->packet); |
840 |
uhci_async_free(s, async);
|
|
867 |
uhci_async_free(async); |
|
841 | 868 |
return len; |
842 | 869 |
} |
843 | 870 |
|
844 | 871 |
static void uhci_async_complete(USBPort *port, USBPacket *packet) |
845 | 872 |
{ |
846 | 873 |
UHCIAsync *async = container_of(packet, UHCIAsync, packet); |
847 |
UHCIState *s = async->uhci; |
|
874 |
UHCIState *s = async->queue->uhci;
|
|
848 | 875 |
|
849 | 876 |
DPRINTF("uhci: async complete. td 0x%x token 0x%x\n", async->td, async->token); |
850 | 877 |
|
... | ... | |
859 | 886 |
le32_to_cpus(&td.token); |
860 | 887 |
le32_to_cpus(&td.buffer); |
861 | 888 |
|
862 |
uhci_async_unlink(s, async);
|
|
889 |
uhci_async_unlink(async); |
|
863 | 890 |
uhci_complete_td(s, &td, async, &int_mask); |
864 | 891 |
s->pending_int_mask |= int_mask; |
865 | 892 |
|
866 | 893 |
/* update the status bits of the TD */ |
867 | 894 |
val = cpu_to_le32(td.ctrl); |
868 | 895 |
pci_dma_write(&s->dev, (link & ~0xf) + 4, &val, sizeof(val)); |
869 |
uhci_async_free(s, async);
|
|
896 |
uhci_async_free(async); |
|
870 | 897 |
} else { |
871 | 898 |
async->done = 1; |
872 | 899 |
uhci_process_frame(s); |
... | ... | |
1142 | 1169 |
} |
1143 | 1170 |
s->frame_timer = qemu_new_timer_ns(vm_clock, uhci_frame_timer, s); |
1144 | 1171 |
s->num_ports_vmstate = NB_PORTS; |
1145 |
QTAILQ_INIT(&s->async_pending);
|
|
1172 |
QTAILQ_INIT(&s->queues);
|
|
1146 | 1173 |
|
1147 | 1174 |
qemu_register_reset(uhci_reset, s); |
1148 | 1175 |
|
Also available in: Unified diff