root / hw / usb-desc.c @ f76e1d81
History | View | Annotate | Download (15.1 kB)
1 | 37fb59d3 | Gerd Hoffmann | #include "usb.h" |
---|---|---|---|
2 | 37fb59d3 | Gerd Hoffmann | #include "usb-desc.h" |
3 | 37fb59d3 | Gerd Hoffmann | #include "trace.h" |
4 | 37fb59d3 | Gerd Hoffmann | |
5 | 37fb59d3 | Gerd Hoffmann | /* ------------------------------------------------------------------ */
|
6 | 37fb59d3 | Gerd Hoffmann | |
7 | 37fb59d3 | Gerd Hoffmann | static uint8_t usb_lo(uint16_t val)
|
8 | 37fb59d3 | Gerd Hoffmann | { |
9 | 37fb59d3 | Gerd Hoffmann | return val & 0xff; |
10 | 37fb59d3 | Gerd Hoffmann | } |
11 | 37fb59d3 | Gerd Hoffmann | |
12 | 37fb59d3 | Gerd Hoffmann | static uint8_t usb_hi(uint16_t val)
|
13 | 37fb59d3 | Gerd Hoffmann | { |
14 | 37fb59d3 | Gerd Hoffmann | return (val >> 8) & 0xff; |
15 | 37fb59d3 | Gerd Hoffmann | } |
16 | 37fb59d3 | Gerd Hoffmann | |
17 | 37fb59d3 | Gerd Hoffmann | int usb_desc_device(const USBDescID *id, const USBDescDevice *dev, |
18 | 37fb59d3 | Gerd Hoffmann | uint8_t *dest, size_t len) |
19 | 37fb59d3 | Gerd Hoffmann | { |
20 | 37fb59d3 | Gerd Hoffmann | uint8_t bLength = 0x12;
|
21 | 37fb59d3 | Gerd Hoffmann | |
22 | 37fb59d3 | Gerd Hoffmann | if (len < bLength) {
|
23 | 37fb59d3 | Gerd Hoffmann | return -1; |
24 | 37fb59d3 | Gerd Hoffmann | } |
25 | 37fb59d3 | Gerd Hoffmann | |
26 | 37fb59d3 | Gerd Hoffmann | dest[0x00] = bLength;
|
27 | 37fb59d3 | Gerd Hoffmann | dest[0x01] = USB_DT_DEVICE;
|
28 | 37fb59d3 | Gerd Hoffmann | |
29 | 37fb59d3 | Gerd Hoffmann | dest[0x02] = usb_lo(dev->bcdUSB);
|
30 | 37fb59d3 | Gerd Hoffmann | dest[0x03] = usb_hi(dev->bcdUSB);
|
31 | 37fb59d3 | Gerd Hoffmann | dest[0x04] = dev->bDeviceClass;
|
32 | 37fb59d3 | Gerd Hoffmann | dest[0x05] = dev->bDeviceSubClass;
|
33 | 37fb59d3 | Gerd Hoffmann | dest[0x06] = dev->bDeviceProtocol;
|
34 | 37fb59d3 | Gerd Hoffmann | dest[0x07] = dev->bMaxPacketSize0;
|
35 | 37fb59d3 | Gerd Hoffmann | |
36 | 37fb59d3 | Gerd Hoffmann | dest[0x08] = usb_lo(id->idVendor);
|
37 | 37fb59d3 | Gerd Hoffmann | dest[0x09] = usb_hi(id->idVendor);
|
38 | 37fb59d3 | Gerd Hoffmann | dest[0x0a] = usb_lo(id->idProduct);
|
39 | 37fb59d3 | Gerd Hoffmann | dest[0x0b] = usb_hi(id->idProduct);
|
40 | 37fb59d3 | Gerd Hoffmann | dest[0x0c] = usb_lo(id->bcdDevice);
|
41 | 37fb59d3 | Gerd Hoffmann | dest[0x0d] = usb_hi(id->bcdDevice);
|
42 | 37fb59d3 | Gerd Hoffmann | dest[0x0e] = id->iManufacturer;
|
43 | 37fb59d3 | Gerd Hoffmann | dest[0x0f] = id->iProduct;
|
44 | 37fb59d3 | Gerd Hoffmann | dest[0x10] = id->iSerialNumber;
|
45 | 37fb59d3 | Gerd Hoffmann | |
46 | 37fb59d3 | Gerd Hoffmann | dest[0x11] = dev->bNumConfigurations;
|
47 | 37fb59d3 | Gerd Hoffmann | |
48 | 37fb59d3 | Gerd Hoffmann | return bLength;
|
49 | 37fb59d3 | Gerd Hoffmann | } |
50 | 37fb59d3 | Gerd Hoffmann | |
51 | 25620cba | Gerd Hoffmann | int usb_desc_device_qualifier(const USBDescDevice *dev, |
52 | 25620cba | Gerd Hoffmann | uint8_t *dest, size_t len) |
53 | 25620cba | Gerd Hoffmann | { |
54 | 25620cba | Gerd Hoffmann | uint8_t bLength = 0x0a;
|
55 | 25620cba | Gerd Hoffmann | |
56 | 25620cba | Gerd Hoffmann | if (len < bLength) {
|
57 | 25620cba | Gerd Hoffmann | return -1; |
58 | 25620cba | Gerd Hoffmann | } |
59 | 25620cba | Gerd Hoffmann | |
60 | 25620cba | Gerd Hoffmann | dest[0x00] = bLength;
|
61 | 25620cba | Gerd Hoffmann | dest[0x01] = USB_DT_DEVICE_QUALIFIER;
|
62 | 25620cba | Gerd Hoffmann | |
63 | 25620cba | Gerd Hoffmann | dest[0x02] = usb_lo(dev->bcdUSB);
|
64 | 25620cba | Gerd Hoffmann | dest[0x03] = usb_hi(dev->bcdUSB);
|
65 | 25620cba | Gerd Hoffmann | dest[0x04] = dev->bDeviceClass;
|
66 | 25620cba | Gerd Hoffmann | dest[0x05] = dev->bDeviceSubClass;
|
67 | 25620cba | Gerd Hoffmann | dest[0x06] = dev->bDeviceProtocol;
|
68 | 25620cba | Gerd Hoffmann | dest[0x07] = dev->bMaxPacketSize0;
|
69 | 25620cba | Gerd Hoffmann | dest[0x08] = dev->bNumConfigurations;
|
70 | 25620cba | Gerd Hoffmann | dest[0x09] = 0; /* reserved */ |
71 | 25620cba | Gerd Hoffmann | |
72 | 25620cba | Gerd Hoffmann | return bLength;
|
73 | 25620cba | Gerd Hoffmann | } |
74 | 25620cba | Gerd Hoffmann | |
75 | 37fb59d3 | Gerd Hoffmann | int usb_desc_config(const USBDescConfig *conf, uint8_t *dest, size_t len) |
76 | 37fb59d3 | Gerd Hoffmann | { |
77 | 37fb59d3 | Gerd Hoffmann | uint8_t bLength = 0x09;
|
78 | 37fb59d3 | Gerd Hoffmann | uint16_t wTotalLength = 0;
|
79 | fef13fa8 | Brad Hards | int i, rc;
|
80 | 37fb59d3 | Gerd Hoffmann | |
81 | 37fb59d3 | Gerd Hoffmann | if (len < bLength) {
|
82 | 37fb59d3 | Gerd Hoffmann | return -1; |
83 | 37fb59d3 | Gerd Hoffmann | } |
84 | 37fb59d3 | Gerd Hoffmann | |
85 | 37fb59d3 | Gerd Hoffmann | dest[0x00] = bLength;
|
86 | 37fb59d3 | Gerd Hoffmann | dest[0x01] = USB_DT_CONFIG;
|
87 | 37fb59d3 | Gerd Hoffmann | dest[0x04] = conf->bNumInterfaces;
|
88 | 37fb59d3 | Gerd Hoffmann | dest[0x05] = conf->bConfigurationValue;
|
89 | 37fb59d3 | Gerd Hoffmann | dest[0x06] = conf->iConfiguration;
|
90 | 37fb59d3 | Gerd Hoffmann | dest[0x07] = conf->bmAttributes;
|
91 | 37fb59d3 | Gerd Hoffmann | dest[0x08] = conf->bMaxPower;
|
92 | 37fb59d3 | Gerd Hoffmann | wTotalLength += bLength; |
93 | 37fb59d3 | Gerd Hoffmann | |
94 | 6e625fc7 | Brad Hards | /* handle grouped interfaces if any*/
|
95 | 6e625fc7 | Brad Hards | for (i = 0; i < conf->nif_groups; i++) { |
96 | 6e625fc7 | Brad Hards | rc = usb_desc_iface_group(&(conf->if_groups[i]), |
97 | 6e625fc7 | Brad Hards | dest + wTotalLength, |
98 | 6e625fc7 | Brad Hards | len - wTotalLength); |
99 | 6e625fc7 | Brad Hards | if (rc < 0) { |
100 | 6e625fc7 | Brad Hards | return rc;
|
101 | 6e625fc7 | Brad Hards | } |
102 | 6e625fc7 | Brad Hards | wTotalLength += rc; |
103 | 6e625fc7 | Brad Hards | } |
104 | 6e625fc7 | Brad Hards | |
105 | 6e625fc7 | Brad Hards | /* handle normal (ungrouped / no IAD) interfaces if any */
|
106 | fef13fa8 | Brad Hards | for (i = 0; i < conf->nif; i++) { |
107 | 37fb59d3 | Gerd Hoffmann | rc = usb_desc_iface(conf->ifs + i, dest + wTotalLength, len - wTotalLength); |
108 | 37fb59d3 | Gerd Hoffmann | if (rc < 0) { |
109 | 37fb59d3 | Gerd Hoffmann | return rc;
|
110 | 37fb59d3 | Gerd Hoffmann | } |
111 | 37fb59d3 | Gerd Hoffmann | wTotalLength += rc; |
112 | 37fb59d3 | Gerd Hoffmann | } |
113 | 37fb59d3 | Gerd Hoffmann | |
114 | 37fb59d3 | Gerd Hoffmann | dest[0x02] = usb_lo(wTotalLength);
|
115 | 37fb59d3 | Gerd Hoffmann | dest[0x03] = usb_hi(wTotalLength);
|
116 | 37fb59d3 | Gerd Hoffmann | return wTotalLength;
|
117 | 37fb59d3 | Gerd Hoffmann | } |
118 | 37fb59d3 | Gerd Hoffmann | |
119 | 6e625fc7 | Brad Hards | int usb_desc_iface_group(const USBDescIfaceAssoc *iad, uint8_t *dest, |
120 | 6e625fc7 | Brad Hards | size_t len) |
121 | 6e625fc7 | Brad Hards | { |
122 | 6e625fc7 | Brad Hards | int pos = 0; |
123 | 6e625fc7 | Brad Hards | int i = 0; |
124 | 6e625fc7 | Brad Hards | |
125 | 6e625fc7 | Brad Hards | /* handle interface association descriptor */
|
126 | 6e625fc7 | Brad Hards | uint8_t bLength = 0x08;
|
127 | 6e625fc7 | Brad Hards | |
128 | 6e625fc7 | Brad Hards | if (len < bLength) {
|
129 | 6e625fc7 | Brad Hards | return -1; |
130 | 6e625fc7 | Brad Hards | } |
131 | 6e625fc7 | Brad Hards | |
132 | 6e625fc7 | Brad Hards | dest[0x00] = bLength;
|
133 | 6e625fc7 | Brad Hards | dest[0x01] = USB_DT_INTERFACE_ASSOC;
|
134 | 6e625fc7 | Brad Hards | dest[0x02] = iad->bFirstInterface;
|
135 | 6e625fc7 | Brad Hards | dest[0x03] = iad->bInterfaceCount;
|
136 | 6e625fc7 | Brad Hards | dest[0x04] = iad->bFunctionClass;
|
137 | 6e625fc7 | Brad Hards | dest[0x05] = iad->bFunctionSubClass;
|
138 | 6e625fc7 | Brad Hards | dest[0x06] = iad->bFunctionProtocol;
|
139 | 6e625fc7 | Brad Hards | dest[0x07] = iad->iFunction;
|
140 | 6e625fc7 | Brad Hards | pos += bLength; |
141 | 6e625fc7 | Brad Hards | |
142 | 6e625fc7 | Brad Hards | /* handle associated interfaces in this group */
|
143 | 6e625fc7 | Brad Hards | for (i = 0; i < iad->nif; i++) { |
144 | 6e625fc7 | Brad Hards | int rc = usb_desc_iface(&(iad->ifs[i]), dest + pos, len - pos);
|
145 | 6e625fc7 | Brad Hards | if (rc < 0) { |
146 | 6e625fc7 | Brad Hards | return rc;
|
147 | 6e625fc7 | Brad Hards | } |
148 | 6e625fc7 | Brad Hards | pos += rc; |
149 | 6e625fc7 | Brad Hards | } |
150 | 6e625fc7 | Brad Hards | |
151 | 6e625fc7 | Brad Hards | return pos;
|
152 | 6e625fc7 | Brad Hards | } |
153 | 6e625fc7 | Brad Hards | |
154 | 37fb59d3 | Gerd Hoffmann | int usb_desc_iface(const USBDescIface *iface, uint8_t *dest, size_t len) |
155 | 37fb59d3 | Gerd Hoffmann | { |
156 | 37fb59d3 | Gerd Hoffmann | uint8_t bLength = 0x09;
|
157 | 37fb59d3 | Gerd Hoffmann | int i, rc, pos = 0; |
158 | 37fb59d3 | Gerd Hoffmann | |
159 | 37fb59d3 | Gerd Hoffmann | if (len < bLength) {
|
160 | 37fb59d3 | Gerd Hoffmann | return -1; |
161 | 37fb59d3 | Gerd Hoffmann | } |
162 | 37fb59d3 | Gerd Hoffmann | |
163 | 37fb59d3 | Gerd Hoffmann | dest[0x00] = bLength;
|
164 | 37fb59d3 | Gerd Hoffmann | dest[0x01] = USB_DT_INTERFACE;
|
165 | 37fb59d3 | Gerd Hoffmann | dest[0x02] = iface->bInterfaceNumber;
|
166 | 37fb59d3 | Gerd Hoffmann | dest[0x03] = iface->bAlternateSetting;
|
167 | 37fb59d3 | Gerd Hoffmann | dest[0x04] = iface->bNumEndpoints;
|
168 | 37fb59d3 | Gerd Hoffmann | dest[0x05] = iface->bInterfaceClass;
|
169 | 37fb59d3 | Gerd Hoffmann | dest[0x06] = iface->bInterfaceSubClass;
|
170 | 37fb59d3 | Gerd Hoffmann | dest[0x07] = iface->bInterfaceProtocol;
|
171 | 37fb59d3 | Gerd Hoffmann | dest[0x08] = iface->iInterface;
|
172 | 37fb59d3 | Gerd Hoffmann | pos += bLength; |
173 | 37fb59d3 | Gerd Hoffmann | |
174 | 37fb59d3 | Gerd Hoffmann | for (i = 0; i < iface->ndesc; i++) { |
175 | 37fb59d3 | Gerd Hoffmann | rc = usb_desc_other(iface->descs + i, dest + pos, len - pos); |
176 | 37fb59d3 | Gerd Hoffmann | if (rc < 0) { |
177 | 37fb59d3 | Gerd Hoffmann | return rc;
|
178 | 37fb59d3 | Gerd Hoffmann | } |
179 | 37fb59d3 | Gerd Hoffmann | pos += rc; |
180 | 37fb59d3 | Gerd Hoffmann | } |
181 | 37fb59d3 | Gerd Hoffmann | |
182 | 37fb59d3 | Gerd Hoffmann | for (i = 0; i < iface->bNumEndpoints; i++) { |
183 | 37fb59d3 | Gerd Hoffmann | rc = usb_desc_endpoint(iface->eps + i, dest + pos, len - pos); |
184 | 37fb59d3 | Gerd Hoffmann | if (rc < 0) { |
185 | 37fb59d3 | Gerd Hoffmann | return rc;
|
186 | 37fb59d3 | Gerd Hoffmann | } |
187 | 37fb59d3 | Gerd Hoffmann | pos += rc; |
188 | 37fb59d3 | Gerd Hoffmann | } |
189 | 37fb59d3 | Gerd Hoffmann | |
190 | 37fb59d3 | Gerd Hoffmann | return pos;
|
191 | 37fb59d3 | Gerd Hoffmann | } |
192 | 37fb59d3 | Gerd Hoffmann | |
193 | 37fb59d3 | Gerd Hoffmann | int usb_desc_endpoint(const USBDescEndpoint *ep, uint8_t *dest, size_t len) |
194 | 37fb59d3 | Gerd Hoffmann | { |
195 | cc5f1395 | Gerd Hoffmann | uint8_t bLength = ep->is_audio ? 0x09 : 0x07; |
196 | cc5f1395 | Gerd Hoffmann | uint8_t extralen = ep->extra ? ep->extra[0] : 0; |
197 | 37fb59d3 | Gerd Hoffmann | |
198 | cc5f1395 | Gerd Hoffmann | if (len < bLength + extralen) {
|
199 | 37fb59d3 | Gerd Hoffmann | return -1; |
200 | 37fb59d3 | Gerd Hoffmann | } |
201 | 37fb59d3 | Gerd Hoffmann | |
202 | 37fb59d3 | Gerd Hoffmann | dest[0x00] = bLength;
|
203 | 37fb59d3 | Gerd Hoffmann | dest[0x01] = USB_DT_ENDPOINT;
|
204 | 37fb59d3 | Gerd Hoffmann | dest[0x02] = ep->bEndpointAddress;
|
205 | 37fb59d3 | Gerd Hoffmann | dest[0x03] = ep->bmAttributes;
|
206 | 37fb59d3 | Gerd Hoffmann | dest[0x04] = usb_lo(ep->wMaxPacketSize);
|
207 | 37fb59d3 | Gerd Hoffmann | dest[0x05] = usb_hi(ep->wMaxPacketSize);
|
208 | 37fb59d3 | Gerd Hoffmann | dest[0x06] = ep->bInterval;
|
209 | cc5f1395 | Gerd Hoffmann | if (ep->is_audio) {
|
210 | cc5f1395 | Gerd Hoffmann | dest[0x07] = ep->bRefresh;
|
211 | cc5f1395 | Gerd Hoffmann | dest[0x08] = ep->bSynchAddress;
|
212 | cc5f1395 | Gerd Hoffmann | } |
213 | cc5f1395 | Gerd Hoffmann | if (ep->extra) {
|
214 | cc5f1395 | Gerd Hoffmann | memcpy(dest + bLength, ep->extra, extralen); |
215 | cc5f1395 | Gerd Hoffmann | } |
216 | 37fb59d3 | Gerd Hoffmann | |
217 | cc5f1395 | Gerd Hoffmann | return bLength + extralen;
|
218 | 37fb59d3 | Gerd Hoffmann | } |
219 | 37fb59d3 | Gerd Hoffmann | |
220 | 37fb59d3 | Gerd Hoffmann | int usb_desc_other(const USBDescOther *desc, uint8_t *dest, size_t len) |
221 | 37fb59d3 | Gerd Hoffmann | { |
222 | 37fb59d3 | Gerd Hoffmann | int bLength = desc->length ? desc->length : desc->data[0]; |
223 | 37fb59d3 | Gerd Hoffmann | |
224 | 37fb59d3 | Gerd Hoffmann | if (len < bLength) {
|
225 | 37fb59d3 | Gerd Hoffmann | return -1; |
226 | 37fb59d3 | Gerd Hoffmann | } |
227 | 37fb59d3 | Gerd Hoffmann | |
228 | 37fb59d3 | Gerd Hoffmann | memcpy(dest, desc->data, bLength); |
229 | 37fb59d3 | Gerd Hoffmann | return bLength;
|
230 | 37fb59d3 | Gerd Hoffmann | } |
231 | 37fb59d3 | Gerd Hoffmann | |
232 | 132a3f55 | Gerd Hoffmann | /* ------------------------------------------------------------------ */
|
233 | 132a3f55 | Gerd Hoffmann | |
234 | 83a53bbc | Gerd Hoffmann | static void usb_desc_ep_init(USBDevice *dev) |
235 | 83a53bbc | Gerd Hoffmann | { |
236 | 83a53bbc | Gerd Hoffmann | const USBDescIface *iface;
|
237 | 83a53bbc | Gerd Hoffmann | int i, e, pid, ep;
|
238 | 83a53bbc | Gerd Hoffmann | |
239 | 83a53bbc | Gerd Hoffmann | usb_ep_init(dev); |
240 | 83a53bbc | Gerd Hoffmann | for (i = 0; i < dev->ninterfaces; i++) { |
241 | 83a53bbc | Gerd Hoffmann | iface = dev->ifaces[i]; |
242 | 83a53bbc | Gerd Hoffmann | if (iface == NULL) { |
243 | 83a53bbc | Gerd Hoffmann | continue;
|
244 | 83a53bbc | Gerd Hoffmann | } |
245 | 83a53bbc | Gerd Hoffmann | for (e = 0; e < iface->bNumEndpoints; e++) { |
246 | 83a53bbc | Gerd Hoffmann | pid = (iface->eps[e].bEndpointAddress & USB_DIR_IN) ? |
247 | 83a53bbc | Gerd Hoffmann | USB_TOKEN_IN : USB_TOKEN_OUT; |
248 | 83a53bbc | Gerd Hoffmann | ep = iface->eps[e].bEndpointAddress & 0x0f;
|
249 | 83a53bbc | Gerd Hoffmann | usb_ep_set_type(dev, pid, ep, iface->eps[e].bmAttributes & 0x03);
|
250 | 83a53bbc | Gerd Hoffmann | usb_ep_set_ifnum(dev, pid, ep, iface->bInterfaceNumber); |
251 | f003397c | Gerd Hoffmann | usb_ep_set_max_packet_size(dev, pid, ep, |
252 | f003397c | Gerd Hoffmann | iface->eps[e].wMaxPacketSize); |
253 | 83a53bbc | Gerd Hoffmann | } |
254 | 83a53bbc | Gerd Hoffmann | } |
255 | 83a53bbc | Gerd Hoffmann | } |
256 | 83a53bbc | Gerd Hoffmann | |
257 | 1de14d43 | Gerd Hoffmann | static const USBDescIface *usb_desc_find_interface(USBDevice *dev, |
258 | 1de14d43 | Gerd Hoffmann | int nif, int alt) |
259 | 1de14d43 | Gerd Hoffmann | { |
260 | 1de14d43 | Gerd Hoffmann | const USBDescIface *iface;
|
261 | 1de14d43 | Gerd Hoffmann | int g, i;
|
262 | 1de14d43 | Gerd Hoffmann | |
263 | 1de14d43 | Gerd Hoffmann | if (!dev->config) {
|
264 | 1de14d43 | Gerd Hoffmann | return NULL; |
265 | 1de14d43 | Gerd Hoffmann | } |
266 | 1de14d43 | Gerd Hoffmann | for (g = 0; g < dev->config->nif_groups; g++) { |
267 | 1de14d43 | Gerd Hoffmann | for (i = 0; i < dev->config->if_groups[g].nif; i++) { |
268 | 1de14d43 | Gerd Hoffmann | iface = &dev->config->if_groups[g].ifs[i]; |
269 | 1de14d43 | Gerd Hoffmann | if (iface->bInterfaceNumber == nif &&
|
270 | 1de14d43 | Gerd Hoffmann | iface->bAlternateSetting == alt) { |
271 | 1de14d43 | Gerd Hoffmann | return iface;
|
272 | 1de14d43 | Gerd Hoffmann | } |
273 | 1de14d43 | Gerd Hoffmann | } |
274 | 1de14d43 | Gerd Hoffmann | } |
275 | 1de14d43 | Gerd Hoffmann | for (i = 0; i < dev->config->nif; i++) { |
276 | 1de14d43 | Gerd Hoffmann | iface = &dev->config->ifs[i]; |
277 | 1de14d43 | Gerd Hoffmann | if (iface->bInterfaceNumber == nif &&
|
278 | 1de14d43 | Gerd Hoffmann | iface->bAlternateSetting == alt) { |
279 | 1de14d43 | Gerd Hoffmann | return iface;
|
280 | 1de14d43 | Gerd Hoffmann | } |
281 | 1de14d43 | Gerd Hoffmann | } |
282 | 1de14d43 | Gerd Hoffmann | return NULL; |
283 | 1de14d43 | Gerd Hoffmann | } |
284 | 1de14d43 | Gerd Hoffmann | |
285 | 1de14d43 | Gerd Hoffmann | static int usb_desc_set_interface(USBDevice *dev, int index, int value) |
286 | 1de14d43 | Gerd Hoffmann | { |
287 | 1de14d43 | Gerd Hoffmann | const USBDescIface *iface;
|
288 | 1de14d43 | Gerd Hoffmann | int old;
|
289 | 1de14d43 | Gerd Hoffmann | |
290 | 1de14d43 | Gerd Hoffmann | iface = usb_desc_find_interface(dev, index, value); |
291 | 1de14d43 | Gerd Hoffmann | if (iface == NULL) { |
292 | 1de14d43 | Gerd Hoffmann | return -1; |
293 | 1de14d43 | Gerd Hoffmann | } |
294 | 1de14d43 | Gerd Hoffmann | |
295 | 1de14d43 | Gerd Hoffmann | old = dev->altsetting[index]; |
296 | 1de14d43 | Gerd Hoffmann | dev->altsetting[index] = value; |
297 | 1de14d43 | Gerd Hoffmann | dev->ifaces[index] = iface; |
298 | 83a53bbc | Gerd Hoffmann | usb_desc_ep_init(dev); |
299 | 1de14d43 | Gerd Hoffmann | |
300 | 62aed765 | Anthony Liguori | if (old != value) {
|
301 | 62aed765 | Anthony Liguori | usb_device_set_interface(dev, index, old, value); |
302 | 1de14d43 | Gerd Hoffmann | } |
303 | 1de14d43 | Gerd Hoffmann | return 0; |
304 | 1de14d43 | Gerd Hoffmann | } |
305 | 1de14d43 | Gerd Hoffmann | |
306 | 65360511 | Gerd Hoffmann | static int usb_desc_set_config(USBDevice *dev, int value) |
307 | 65360511 | Gerd Hoffmann | { |
308 | 65360511 | Gerd Hoffmann | int i;
|
309 | 65360511 | Gerd Hoffmann | |
310 | 65360511 | Gerd Hoffmann | if (value == 0) { |
311 | 65360511 | Gerd Hoffmann | dev->configuration = 0;
|
312 | 65360511 | Gerd Hoffmann | dev->ninterfaces = 0;
|
313 | 65360511 | Gerd Hoffmann | dev->config = NULL;
|
314 | 65360511 | Gerd Hoffmann | } else {
|
315 | 65360511 | Gerd Hoffmann | for (i = 0; i < dev->device->bNumConfigurations; i++) { |
316 | 65360511 | Gerd Hoffmann | if (dev->device->confs[i].bConfigurationValue == value) {
|
317 | 65360511 | Gerd Hoffmann | dev->configuration = value; |
318 | 65360511 | Gerd Hoffmann | dev->ninterfaces = dev->device->confs[i].bNumInterfaces; |
319 | 65360511 | Gerd Hoffmann | dev->config = dev->device->confs + i; |
320 | 1de14d43 | Gerd Hoffmann | assert(dev->ninterfaces <= USB_MAX_INTERFACES); |
321 | 65360511 | Gerd Hoffmann | } |
322 | 65360511 | Gerd Hoffmann | } |
323 | 65360511 | Gerd Hoffmann | if (i < dev->device->bNumConfigurations) {
|
324 | 65360511 | Gerd Hoffmann | return -1; |
325 | 65360511 | Gerd Hoffmann | } |
326 | 65360511 | Gerd Hoffmann | } |
327 | 1de14d43 | Gerd Hoffmann | |
328 | 1de14d43 | Gerd Hoffmann | for (i = 0; i < dev->ninterfaces; i++) { |
329 | 1de14d43 | Gerd Hoffmann | usb_desc_set_interface(dev, i, 0);
|
330 | 1de14d43 | Gerd Hoffmann | } |
331 | 1de14d43 | Gerd Hoffmann | for (; i < USB_MAX_INTERFACES; i++) {
|
332 | 1de14d43 | Gerd Hoffmann | dev->altsetting[i] = 0;
|
333 | 1de14d43 | Gerd Hoffmann | dev->ifaces[i] = NULL;
|
334 | 1de14d43 | Gerd Hoffmann | } |
335 | 1de14d43 | Gerd Hoffmann | |
336 | 65360511 | Gerd Hoffmann | return 0; |
337 | 65360511 | Gerd Hoffmann | } |
338 | 65360511 | Gerd Hoffmann | |
339 | 32d41919 | Gerd Hoffmann | static void usb_desc_setdefaults(USBDevice *dev) |
340 | a980a065 | Gerd Hoffmann | { |
341 | 62aed765 | Anthony Liguori | const USBDesc *desc = usb_device_get_usb_desc(dev);
|
342 | a980a065 | Gerd Hoffmann | |
343 | a980a065 | Gerd Hoffmann | assert(desc != NULL);
|
344 | 32d41919 | Gerd Hoffmann | switch (dev->speed) {
|
345 | 32d41919 | Gerd Hoffmann | case USB_SPEED_LOW:
|
346 | 32d41919 | Gerd Hoffmann | case USB_SPEED_FULL:
|
347 | 32d41919 | Gerd Hoffmann | dev->device = desc->full; |
348 | 32d41919 | Gerd Hoffmann | break;
|
349 | 32d41919 | Gerd Hoffmann | case USB_SPEED_HIGH:
|
350 | 32d41919 | Gerd Hoffmann | dev->device = desc->high; |
351 | 32d41919 | Gerd Hoffmann | break;
|
352 | 32d41919 | Gerd Hoffmann | } |
353 | 65360511 | Gerd Hoffmann | usb_desc_set_config(dev, 0);
|
354 | a980a065 | Gerd Hoffmann | } |
355 | a980a065 | Gerd Hoffmann | |
356 | 32d41919 | Gerd Hoffmann | void usb_desc_init(USBDevice *dev)
|
357 | 32d41919 | Gerd Hoffmann | { |
358 | 62aed765 | Anthony Liguori | const USBDesc *desc = usb_device_get_usb_desc(dev);
|
359 | ba3f9bfb | Hans de Goede | |
360 | ba3f9bfb | Hans de Goede | assert(desc != NULL);
|
361 | 32d41919 | Gerd Hoffmann | dev->speed = USB_SPEED_FULL; |
362 | ba3f9bfb | Hans de Goede | dev->speedmask = 0;
|
363 | ba3f9bfb | Hans de Goede | if (desc->full) {
|
364 | ba3f9bfb | Hans de Goede | dev->speedmask |= USB_SPEED_MASK_FULL; |
365 | ba3f9bfb | Hans de Goede | } |
366 | ba3f9bfb | Hans de Goede | if (desc->high) {
|
367 | ba3f9bfb | Hans de Goede | dev->speedmask |= USB_SPEED_MASK_HIGH; |
368 | ba3f9bfb | Hans de Goede | } |
369 | 32d41919 | Gerd Hoffmann | usb_desc_setdefaults(dev); |
370 | 32d41919 | Gerd Hoffmann | } |
371 | 32d41919 | Gerd Hoffmann | |
372 | 32d41919 | Gerd Hoffmann | void usb_desc_attach(USBDevice *dev)
|
373 | 32d41919 | Gerd Hoffmann | { |
374 | 62aed765 | Anthony Liguori | const USBDesc *desc = usb_device_get_usb_desc(dev);
|
375 | 32d41919 | Gerd Hoffmann | |
376 | 32d41919 | Gerd Hoffmann | assert(desc != NULL);
|
377 | 32d41919 | Gerd Hoffmann | if (desc->high && (dev->port->speedmask & USB_SPEED_MASK_HIGH)) {
|
378 | 32d41919 | Gerd Hoffmann | dev->speed = USB_SPEED_HIGH; |
379 | 32d41919 | Gerd Hoffmann | } else if (desc->full && (dev->port->speedmask & USB_SPEED_MASK_FULL)) { |
380 | 32d41919 | Gerd Hoffmann | dev->speed = USB_SPEED_FULL; |
381 | 32d41919 | Gerd Hoffmann | } else {
|
382 | 32d41919 | Gerd Hoffmann | fprintf(stderr, "usb: port/device speed mismatch for \"%s\"\n",
|
383 | 62aed765 | Anthony Liguori | usb_device_get_product_desc(dev)); |
384 | 32d41919 | Gerd Hoffmann | return;
|
385 | 32d41919 | Gerd Hoffmann | } |
386 | 32d41919 | Gerd Hoffmann | usb_desc_setdefaults(dev); |
387 | 32d41919 | Gerd Hoffmann | } |
388 | 32d41919 | Gerd Hoffmann | |
389 | 132a3f55 | Gerd Hoffmann | void usb_desc_set_string(USBDevice *dev, uint8_t index, const char *str) |
390 | 132a3f55 | Gerd Hoffmann | { |
391 | 132a3f55 | Gerd Hoffmann | USBDescString *s; |
392 | 132a3f55 | Gerd Hoffmann | |
393 | 132a3f55 | Gerd Hoffmann | QLIST_FOREACH(s, &dev->strings, next) { |
394 | 132a3f55 | Gerd Hoffmann | if (s->index == index) {
|
395 | 132a3f55 | Gerd Hoffmann | break;
|
396 | 132a3f55 | Gerd Hoffmann | } |
397 | 132a3f55 | Gerd Hoffmann | } |
398 | 132a3f55 | Gerd Hoffmann | if (s == NULL) { |
399 | 7267c094 | Anthony Liguori | s = g_malloc0(sizeof(*s));
|
400 | 132a3f55 | Gerd Hoffmann | s->index = index; |
401 | 132a3f55 | Gerd Hoffmann | QLIST_INSERT_HEAD(&dev->strings, s, next); |
402 | 132a3f55 | Gerd Hoffmann | } |
403 | 7267c094 | Anthony Liguori | g_free(s->str); |
404 | 7267c094 | Anthony Liguori | s->str = g_strdup(str); |
405 | 132a3f55 | Gerd Hoffmann | } |
406 | 132a3f55 | Gerd Hoffmann | |
407 | 132a3f55 | Gerd Hoffmann | const char *usb_desc_get_string(USBDevice *dev, uint8_t index) |
408 | 132a3f55 | Gerd Hoffmann | { |
409 | 132a3f55 | Gerd Hoffmann | USBDescString *s; |
410 | 132a3f55 | Gerd Hoffmann | |
411 | 132a3f55 | Gerd Hoffmann | QLIST_FOREACH(s, &dev->strings, next) { |
412 | 132a3f55 | Gerd Hoffmann | if (s->index == index) {
|
413 | 132a3f55 | Gerd Hoffmann | return s->str;
|
414 | 132a3f55 | Gerd Hoffmann | } |
415 | 132a3f55 | Gerd Hoffmann | } |
416 | 132a3f55 | Gerd Hoffmann | return NULL; |
417 | 132a3f55 | Gerd Hoffmann | } |
418 | 132a3f55 | Gerd Hoffmann | |
419 | 132a3f55 | Gerd Hoffmann | int usb_desc_string(USBDevice *dev, int index, uint8_t *dest, size_t len) |
420 | 37fb59d3 | Gerd Hoffmann | { |
421 | 37fb59d3 | Gerd Hoffmann | uint8_t bLength, pos, i; |
422 | 132a3f55 | Gerd Hoffmann | const char *str; |
423 | 37fb59d3 | Gerd Hoffmann | |
424 | 37fb59d3 | Gerd Hoffmann | if (len < 4) { |
425 | 37fb59d3 | Gerd Hoffmann | return -1; |
426 | 37fb59d3 | Gerd Hoffmann | } |
427 | 37fb59d3 | Gerd Hoffmann | |
428 | 37fb59d3 | Gerd Hoffmann | if (index == 0) { |
429 | 37fb59d3 | Gerd Hoffmann | /* language ids */
|
430 | 37fb59d3 | Gerd Hoffmann | dest[0] = 4; |
431 | 37fb59d3 | Gerd Hoffmann | dest[1] = USB_DT_STRING;
|
432 | 37fb59d3 | Gerd Hoffmann | dest[2] = 0x09; |
433 | 37fb59d3 | Gerd Hoffmann | dest[3] = 0x04; |
434 | 37fb59d3 | Gerd Hoffmann | return 4; |
435 | 37fb59d3 | Gerd Hoffmann | } |
436 | 37fb59d3 | Gerd Hoffmann | |
437 | 132a3f55 | Gerd Hoffmann | str = usb_desc_get_string(dev, index); |
438 | 132a3f55 | Gerd Hoffmann | if (str == NULL) { |
439 | 62aed765 | Anthony Liguori | str = usb_device_get_usb_desc(dev)->str[index]; |
440 | 132a3f55 | Gerd Hoffmann | if (str == NULL) { |
441 | 132a3f55 | Gerd Hoffmann | return 0; |
442 | 132a3f55 | Gerd Hoffmann | } |
443 | 37fb59d3 | Gerd Hoffmann | } |
444 | 132a3f55 | Gerd Hoffmann | |
445 | 132a3f55 | Gerd Hoffmann | bLength = strlen(str) * 2 + 2; |
446 | 37fb59d3 | Gerd Hoffmann | dest[0] = bLength;
|
447 | 37fb59d3 | Gerd Hoffmann | dest[1] = USB_DT_STRING;
|
448 | 37fb59d3 | Gerd Hoffmann | i = 0; pos = 2; |
449 | 37fb59d3 | Gerd Hoffmann | while (pos+1 < bLength && pos+1 < len) { |
450 | 132a3f55 | Gerd Hoffmann | dest[pos++] = str[i++]; |
451 | 37fb59d3 | Gerd Hoffmann | dest[pos++] = 0;
|
452 | 37fb59d3 | Gerd Hoffmann | } |
453 | 37fb59d3 | Gerd Hoffmann | return pos;
|
454 | 37fb59d3 | Gerd Hoffmann | } |
455 | 37fb59d3 | Gerd Hoffmann | |
456 | 37fb59d3 | Gerd Hoffmann | int usb_desc_get_descriptor(USBDevice *dev, int value, uint8_t *dest, size_t len) |
457 | 37fb59d3 | Gerd Hoffmann | { |
458 | 62aed765 | Anthony Liguori | const USBDesc *desc = usb_device_get_usb_desc(dev);
|
459 | 25620cba | Gerd Hoffmann | const USBDescDevice *other_dev;
|
460 | 37fb59d3 | Gerd Hoffmann | uint8_t buf[256];
|
461 | 37fb59d3 | Gerd Hoffmann | uint8_t type = value >> 8;
|
462 | 37fb59d3 | Gerd Hoffmann | uint8_t index = value & 0xff;
|
463 | 37fb59d3 | Gerd Hoffmann | int ret = -1; |
464 | 37fb59d3 | Gerd Hoffmann | |
465 | 25620cba | Gerd Hoffmann | if (dev->speed == USB_SPEED_HIGH) {
|
466 | 62aed765 | Anthony Liguori | other_dev = usb_device_get_usb_desc(dev)->full; |
467 | 25620cba | Gerd Hoffmann | } else {
|
468 | 62aed765 | Anthony Liguori | other_dev = usb_device_get_usb_desc(dev)->high; |
469 | 25620cba | Gerd Hoffmann | } |
470 | 25620cba | Gerd Hoffmann | |
471 | 37fb59d3 | Gerd Hoffmann | switch(type) {
|
472 | 37fb59d3 | Gerd Hoffmann | case USB_DT_DEVICE:
|
473 | a980a065 | Gerd Hoffmann | ret = usb_desc_device(&desc->id, dev->device, buf, sizeof(buf));
|
474 | 37fb59d3 | Gerd Hoffmann | trace_usb_desc_device(dev->addr, len, ret); |
475 | 37fb59d3 | Gerd Hoffmann | break;
|
476 | 37fb59d3 | Gerd Hoffmann | case USB_DT_CONFIG:
|
477 | a980a065 | Gerd Hoffmann | if (index < dev->device->bNumConfigurations) {
|
478 | a980a065 | Gerd Hoffmann | ret = usb_desc_config(dev->device->confs + index, buf, sizeof(buf));
|
479 | 37fb59d3 | Gerd Hoffmann | } |
480 | 37fb59d3 | Gerd Hoffmann | trace_usb_desc_config(dev->addr, index, len, ret); |
481 | 37fb59d3 | Gerd Hoffmann | break;
|
482 | 37fb59d3 | Gerd Hoffmann | case USB_DT_STRING:
|
483 | 132a3f55 | Gerd Hoffmann | ret = usb_desc_string(dev, index, buf, sizeof(buf));
|
484 | 37fb59d3 | Gerd Hoffmann | trace_usb_desc_string(dev->addr, index, len, ret); |
485 | 37fb59d3 | Gerd Hoffmann | break;
|
486 | 25620cba | Gerd Hoffmann | |
487 | 25620cba | Gerd Hoffmann | case USB_DT_DEVICE_QUALIFIER:
|
488 | 25620cba | Gerd Hoffmann | if (other_dev != NULL) { |
489 | 25620cba | Gerd Hoffmann | ret = usb_desc_device_qualifier(other_dev, buf, sizeof(buf));
|
490 | 25620cba | Gerd Hoffmann | } |
491 | 25620cba | Gerd Hoffmann | trace_usb_desc_device_qualifier(dev->addr, len, ret); |
492 | 25620cba | Gerd Hoffmann | break;
|
493 | 25620cba | Gerd Hoffmann | case USB_DT_OTHER_SPEED_CONFIG:
|
494 | 25620cba | Gerd Hoffmann | if (other_dev != NULL && index < other_dev->bNumConfigurations) { |
495 | 25620cba | Gerd Hoffmann | ret = usb_desc_config(other_dev->confs + index, buf, sizeof(buf));
|
496 | 25620cba | Gerd Hoffmann | buf[0x01] = USB_DT_OTHER_SPEED_CONFIG;
|
497 | 25620cba | Gerd Hoffmann | } |
498 | 25620cba | Gerd Hoffmann | trace_usb_desc_other_speed_config(dev->addr, index, len, ret); |
499 | 25620cba | Gerd Hoffmann | break;
|
500 | 25620cba | Gerd Hoffmann | |
501 | a7fb71d1 | Gerd Hoffmann | case USB_DT_DEBUG:
|
502 | a7fb71d1 | Gerd Hoffmann | /* ignore silently */
|
503 | a7fb71d1 | Gerd Hoffmann | break;
|
504 | a7fb71d1 | Gerd Hoffmann | |
505 | 37fb59d3 | Gerd Hoffmann | default:
|
506 | 37fb59d3 | Gerd Hoffmann | fprintf(stderr, "%s: %d unknown type %d (len %zd)\n", __FUNCTION__,
|
507 | 37fb59d3 | Gerd Hoffmann | dev->addr, type, len); |
508 | 37fb59d3 | Gerd Hoffmann | break;
|
509 | 37fb59d3 | Gerd Hoffmann | } |
510 | 37fb59d3 | Gerd Hoffmann | |
511 | 37fb59d3 | Gerd Hoffmann | if (ret > 0) { |
512 | 37fb59d3 | Gerd Hoffmann | if (ret > len) {
|
513 | 37fb59d3 | Gerd Hoffmann | ret = len; |
514 | 37fb59d3 | Gerd Hoffmann | } |
515 | 37fb59d3 | Gerd Hoffmann | memcpy(dest, buf, ret); |
516 | 37fb59d3 | Gerd Hoffmann | } |
517 | 37fb59d3 | Gerd Hoffmann | return ret;
|
518 | 37fb59d3 | Gerd Hoffmann | } |
519 | 37fb59d3 | Gerd Hoffmann | |
520 | 007fd62f | Hans de Goede | int usb_desc_handle_control(USBDevice *dev, USBPacket *p,
|
521 | 007fd62f | Hans de Goede | int request, int value, int index, int length, uint8_t *data) |
522 | 37fb59d3 | Gerd Hoffmann | { |
523 | 62aed765 | Anthony Liguori | const USBDesc *desc = usb_device_get_usb_desc(dev);
|
524 | 65360511 | Gerd Hoffmann | int ret = -1; |
525 | 37fb59d3 | Gerd Hoffmann | |
526 | 37fb59d3 | Gerd Hoffmann | assert(desc != NULL);
|
527 | 37fb59d3 | Gerd Hoffmann | switch(request) {
|
528 | 41c6abbd | Gerd Hoffmann | case DeviceOutRequest | USB_REQ_SET_ADDRESS:
|
529 | 41c6abbd | Gerd Hoffmann | dev->addr = value; |
530 | 41c6abbd | Gerd Hoffmann | trace_usb_set_addr(dev->addr); |
531 | 41c6abbd | Gerd Hoffmann | ret = 0;
|
532 | 41c6abbd | Gerd Hoffmann | break;
|
533 | 41c6abbd | Gerd Hoffmann | |
534 | 37fb59d3 | Gerd Hoffmann | case DeviceRequest | USB_REQ_GET_DESCRIPTOR:
|
535 | 37fb59d3 | Gerd Hoffmann | ret = usb_desc_get_descriptor(dev, value, data, length); |
536 | 37fb59d3 | Gerd Hoffmann | break;
|
537 | a980a065 | Gerd Hoffmann | |
538 | a980a065 | Gerd Hoffmann | case DeviceRequest | USB_REQ_GET_CONFIGURATION:
|
539 | a980a065 | Gerd Hoffmann | data[0] = dev->config->bConfigurationValue;
|
540 | a980a065 | Gerd Hoffmann | ret = 1;
|
541 | a980a065 | Gerd Hoffmann | break;
|
542 | a980a065 | Gerd Hoffmann | case DeviceOutRequest | USB_REQ_SET_CONFIGURATION:
|
543 | 65360511 | Gerd Hoffmann | ret = usb_desc_set_config(dev, value); |
544 | a980a065 | Gerd Hoffmann | trace_usb_set_config(dev->addr, value, ret); |
545 | a980a065 | Gerd Hoffmann | break;
|
546 | ed5a83dd | Gerd Hoffmann | |
547 | ed5a83dd | Gerd Hoffmann | case DeviceRequest | USB_REQ_GET_STATUS:
|
548 | ed5a83dd | Gerd Hoffmann | data[0] = 0; |
549 | ed5a83dd | Gerd Hoffmann | if (dev->config->bmAttributes & 0x40) { |
550 | ed5a83dd | Gerd Hoffmann | data[0] |= 1 << USB_DEVICE_SELF_POWERED; |
551 | ed5a83dd | Gerd Hoffmann | } |
552 | ed5a83dd | Gerd Hoffmann | if (dev->remote_wakeup) {
|
553 | ed5a83dd | Gerd Hoffmann | data[0] |= 1 << USB_DEVICE_REMOTE_WAKEUP; |
554 | ed5a83dd | Gerd Hoffmann | } |
555 | ed5a83dd | Gerd Hoffmann | data[1] = 0x00; |
556 | ed5a83dd | Gerd Hoffmann | ret = 2;
|
557 | ed5a83dd | Gerd Hoffmann | break;
|
558 | ed5a83dd | Gerd Hoffmann | case DeviceOutRequest | USB_REQ_CLEAR_FEATURE:
|
559 | ed5a83dd | Gerd Hoffmann | if (value == USB_DEVICE_REMOTE_WAKEUP) {
|
560 | ed5a83dd | Gerd Hoffmann | dev->remote_wakeup = 0;
|
561 | ed5a83dd | Gerd Hoffmann | ret = 0;
|
562 | ed5a83dd | Gerd Hoffmann | } |
563 | ed5a83dd | Gerd Hoffmann | trace_usb_clear_device_feature(dev->addr, value, ret); |
564 | ed5a83dd | Gerd Hoffmann | break;
|
565 | ed5a83dd | Gerd Hoffmann | case DeviceOutRequest | USB_REQ_SET_FEATURE:
|
566 | ed5a83dd | Gerd Hoffmann | if (value == USB_DEVICE_REMOTE_WAKEUP) {
|
567 | ed5a83dd | Gerd Hoffmann | dev->remote_wakeup = 1;
|
568 | ed5a83dd | Gerd Hoffmann | ret = 0;
|
569 | ed5a83dd | Gerd Hoffmann | } |
570 | ed5a83dd | Gerd Hoffmann | trace_usb_set_device_feature(dev->addr, value, ret); |
571 | ed5a83dd | Gerd Hoffmann | break;
|
572 | 1de14d43 | Gerd Hoffmann | |
573 | 1de14d43 | Gerd Hoffmann | case InterfaceRequest | USB_REQ_GET_INTERFACE:
|
574 | 1de14d43 | Gerd Hoffmann | if (index < 0 || index >= dev->ninterfaces) { |
575 | 1de14d43 | Gerd Hoffmann | break;
|
576 | 1de14d43 | Gerd Hoffmann | } |
577 | 1de14d43 | Gerd Hoffmann | data[0] = dev->altsetting[index];
|
578 | 1de14d43 | Gerd Hoffmann | ret = 1;
|
579 | 1de14d43 | Gerd Hoffmann | break;
|
580 | 1de14d43 | Gerd Hoffmann | case InterfaceOutRequest | USB_REQ_SET_INTERFACE:
|
581 | 1de14d43 | Gerd Hoffmann | ret = usb_desc_set_interface(dev, index, value); |
582 | 1de14d43 | Gerd Hoffmann | trace_usb_set_interface(dev->addr, index, value, ret); |
583 | 1de14d43 | Gerd Hoffmann | break;
|
584 | 1de14d43 | Gerd Hoffmann | |
585 | 37fb59d3 | Gerd Hoffmann | } |
586 | 37fb59d3 | Gerd Hoffmann | return ret;
|
587 | 37fb59d3 | Gerd Hoffmann | } |