LibXenon
Bare-metal Xbox 360 homebrew library
Loading...
Searching...
No Matches
dev_usb_catc.c
Go to the documentation of this file.
1/* *********************************************************************
2 * Broadcom Common Firmware Environment (CFE)
3 *
4 * USB Ethernet File: dev_usb_catc.c
5 *
6 * Driver for USB Ethernet devices using the CATC Netmate chip.
7 *
8 *********************************************************************
9 *
10 * Copyright 2000,2001,2002,2003,2005
11 * Broadcom Corporation. All rights reserved.
12 *
13 * This software is furnished under license and may be used and
14 * copied only in accordance with the following terms and
15 * conditions. Subject to these conditions, you may download,
16 * copy, install, use, modify and distribute modified or unmodified
17 * copies of this software in source and/or binary form. No title
18 * or ownership is transferred hereby.
19 *
20 * 1) Any source code used, modified or distributed must reproduce
21 * and retain this copyright notice and list of conditions
22 * as they appear in the source file.
23 *
24 * 2) No right is granted to use any trade name, trademark, or
25 * logo of Broadcom Corporation. The "Broadcom Corporation"
26 * name may not be used to endorse or promote products derived
27 * from this software without the prior written permission of
28 * Broadcom Corporation.
29 *
30 * 3) THIS SOFTWARE IS PROVIDED "AS-IS" AND ANY EXPRESS OR
31 * IMPLIED WARRANTIES, INCLUDING BUT NOT LIMITED TO, ANY IMPLIED
32 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
33 * PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED. IN NO EVENT
34 * SHALL BROADCOM BE LIABLE FOR ANY DAMAGES WHATSOEVER, AND IN
35 * PARTICULAR, BROADCOM SHALL NOT BE LIABLE FOR DIRECT, INDIRECT,
36 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
37 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
38 * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
39 * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
40 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
41 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE), EVEN IF ADVISED OF
42 * THE POSSIBILITY OF SUCH DAMAGE.
43 ********************************************************************* */
44
45/* *********************************************************************
46 * USB-Ethernet driver - CFE Network Layer Interfaces
47 ********************************************************************* */
48
49#include "cfe.h"
50
51#include "usbd.h"
52#include "usbeth.h"
53
54#if 0
55#define USBETH_TRACE( x, y ... ) xprintf( x, ##y )
56#else
57#define USBETH_TRACE( x, y ... ) ((void)0)
58#endif
59
60#define FAIL -1
61
62#define CACHE_ALIGN 32 /* XXX place holder, big enough to now. */
63#define ALIGN(n,align) (((n)+((align)-1)) & ~((align)-1))
64
65#define usb_dma_alloc(n) (KMALLOC(ALIGN((n),CACHE_ALIGN),CACHE_ALIGN))
66#define usb_dma_free(p) (KFREE(p))
67
68/******************************************************************************
69 Debug functions
70******************************************************************************/
71
72#ifndef USBETH_DEBUG
73#define USBETH_DEBUG 0
74#endif
75
76#if USBETH_DEBUG
77static void hexdump( unsigned char *src, int srclen, int rowlen, int rows )
78{
79 unsigned char *rowptr;
80 unsigned char *srcstp;
81 unsigned char *byteptr;
82
83 srcstp = src + srclen;
84
85 for( rowptr = src; rowptr < src + rowlen * rows; rowptr += rowlen ) {
86 for( byteptr = rowptr; byteptr < rowptr + rowlen && byteptr < srcstp; byteptr++ ) {
87 xprintf( "%2X ", *byteptr );
88 }
89 xprintf( "\n" );
90 }
91 xprintf( "\n" );
92}
93#else
94#define hexdump(src,srclen,rowlen,rows) ((void)0)
95#endif
96
97
98/* *********************************************************************
99 * Interface functions for USB-Ethernet adapters
100 ********************************************************************* */
101
103static const char *VENDOR_NAMES[] = {
104 "?", "CATC-Netmate", "Belkin/CATC", "Yikes!"
105};
106
107static const int ID_TBL[] = {
108 0x0423, 0x000a, CATC_NM, /* CATC (Netmate I) */
109 0x0423, 0x000c, BELKIN_CATC, /* Belkin/CATC (Netmate II) */
110 -1
111};
112
113typedef struct catc_softc_s {
120 uint8_t rxbuf[1600]; /* arbitrary but enough for ethernet packet */
122
123
124/* **************************************
125 * CATC I/F Functions
126 ************************************** */
127
128static int catc_send_eth_frame( void *ctx, hsaddr_t buf, int len );
129static int catc_get_eth_frame( void *ctx, hsaddr_t buf );
130static int catc_data_rx( void *ctx );
131static int catc_get_dev_addr( void *ctx, hsaddr_t mac_addr );
132
133static usbeth_disp_t usbeth_catc = {
134 catc_get_eth_frame,
135 catc_data_rx,
136 catc_send_eth_frame,
137 catc_get_dev_addr
138};
139
140#if 0
141static int catc_get_reg( usbdev_t *dev, int16_t reg, uint8_t *val )
142{
144 CATC_GET_REG, 0, reg, val, 1 );
145}
146#endif
147
148static int catc_set_reg( usbdev_t *dev, int16_t reg, int16_t val )
149{
151 CATC_SET_REG, val, reg, NULL, 0 );
152}
153
154static int catc_set_mem( usbdev_t *dev, int16_t addr,
155 uint8_t *data, int16_t len )
156{
158 CATC_SET_MEM, 0, addr, data, len );
159}
160
161static int catc_get_mac_addr( usbdev_t *dev, uint8_t *mac_addr )
162{
164 CATC_GET_MAC_ADDR, 0, 0, mac_addr, 6 );
165}
166
167static int catc_init_device( catc_softc_t *softc )
168{
169 usb_device_descr_t dev_desc;
170 uint16_t vendor_id, product_id;
171 const int *ptr=ID_TBL;
172 usbdev_t *dev = softc->dev;
173 unsigned char *mcast_tbl;
174
175
176 /* find out which device is connected */
177 usb_get_device_descriptor( softc->dev, &dev_desc, 0 );
178 vendor_id = (dev_desc.idVendorHigh << 8) + dev_desc.idVendorLow;
179 product_id = (dev_desc.idProductHigh << 8) + dev_desc.idProductLow;
180
181 while( *ptr != -1 ) {
182 if( (vendor_id == ptr[0]) && (product_id == ptr[1]) ) {
183 softc->ven_code = ptr[2];
184 break;
185 }
186 ptr += 3;
187 }
188 if( *ptr == -1 ) {
189 xprintf( "Unrecognized CATC USB-Ethernet device\n" );
190 return -1;
191 }
192
195 1, /* alt setting 1 */
196 0, NULL, 0 );
197
198 catc_set_reg(dev, CATC_TX_BUF_CNT_REG, 0x04 );
199 catc_set_reg(dev, CATC_RX_BUF_CNT_REG, 0x10 );
200 catc_set_reg(dev, CATC_ADV_OP_MODES_REG, 0x01 );
201 catc_set_reg(dev, CATC_LED_CTRL_REG, 0x08 );
202
203 /* Enable broadcast rx via bit in multicast table */
204 mcast_tbl = KMALLOC(64, CACHE_ALIGN);
205 memset( mcast_tbl, 0, 64 );
206 mcast_tbl[31] = 0x80; /* broadcast bit */
207 catc_set_mem( dev, CATC_MCAST_TBL_ADDR, mcast_tbl, 64 );
208 KFREE(mcast_tbl);
209
210 /* Read the adapter's MAC addr */
211 catc_get_mac_addr( dev, softc->mac_addr );
212
213 /* display adapter info */
214 xprintf( "%s USB-Ethernet Adapter (%a)\n",
215 VENDOR_NAMES[softc->ven_code], softc->mac_addr);
216
217 return 0;
218}
219
220static int catc_get_dev_addr( void *ctx, hsaddr_t mac_addr )
221{
222 catc_softc_t *softc = (catc_softc_t *) ctx;
223 hs_memcpy_to_hs( mac_addr, softc->mac_addr, 6 );
224 return 0;
225}
226
227static void catc_queue_rx( catc_softc_t *softc )
228{
229 softc->rx_ur = usb_make_request(softc->dev, softc->bulk_inpipe,
230 softc->rxbuf, sizeof(softc->rxbuf),
232 usb_queue_request(softc->rx_ur);
233}
234
235static int catc_data_rx( void *ctx )
236{
237 catc_softc_t *softc = (catc_softc_t *) ctx;
238 usb_poll(softc->dev->ud_bus);
239 return( !softc->rx_ur->ur_inprogress );
240}
241
242static int catc_get_eth_frame( void *ctx, hsaddr_t buf )
243{
244 catc_softc_t *softc = (catc_softc_t *) ctx;
245 int len = 0;
246 uint8_t *rxbuf;
247
248 if( !softc->rx_ur->ur_inprogress ) {
249 rxbuf = softc->rxbuf;
250 len = softc->rx_ur->ur_xferred;
251 if (len > 0) {
252#if CATC_DEBUG
253 xprintf( "Incoming packet :\n" );
254 hexdump( rxbuf, len, 16, len / 16 + 1 );
255#endif
256 hs_memcpy_to_hs( buf, rxbuf, len );
257 }
258 usb_free_request(softc->rx_ur);
259 catc_queue_rx( softc );
260 }
261 else
262 xprintf( "Bulk data is not available yet!\n" );
263
264 return( len );
265}
266
267static int catc_send_eth_frame( void *ctx, hsaddr_t buf, int len )
268{
269 catc_softc_t *softc = (catc_softc_t *) ctx;
270 usbreq_t *ur;
271 int txlen = len;
272 unsigned char *txbuf;
273
274 txbuf = usb_dma_alloc(len+2);
275 txbuf[0] = txlen & 0xff;
276 txbuf[1] = (txlen >> 8) & 0xff; /* 1st two bytes...little endian */
277 hs_memcpy_from_hs( &txbuf[2], buf, txlen );
278 txlen += 2;
279#if USBETH_DEBUG
280 xprintf( "Outgoing packet :\n" );
281 hexdump( txbuf, txlen, 16, txlen / 16 + 1 );
282#endif
283 ur = usb_make_request(softc->dev, softc->bulk_outpipe,
284 txbuf, txlen, UR_FLAG_OUT);
287 usb_dma_free(txbuf);
288
289 return( len );
290}
291
292static void catc_open_device( catc_softc_t *softc )
293{
294 int i;
295
296 for(i = 0; i < 6; ++i)
297 catc_set_reg( softc->dev, (CATC_ETH_ADDR_0_REG - i), softc->mac_addr[i] );
298
299 /* Enable adapter to receive packets */
300 catc_set_reg( softc->dev, CATC_ETH_CTRL_REG, 0x09 );
301
302 /* kick start the receive */
303 catc_queue_rx( softc );
304}
305
306static void catc_close_device( catc_softc_t *softc )
307{
308 usbdev_t *dev = softc->dev;
309
310 /* Disable adapter from receiving packets */
311 catc_set_reg( dev, CATC_ETH_CTRL_REG, 0 );
312}
313
314
315/* *********************************************************************
316 * CFE-USB interfaces
317 ********************************************************************* */
318
319/* *********************************************************************
320 * catc_attach(dev,drv)
321 *
322 * This routine is called when the bus scan stuff finds a usb-ethernet
323 * device. We finish up the initialization by configuring the
324 * device and allocating our softc here.
325 *
326 * Input parameters:
327 * dev - usb device, in the "addressed" state.
328 * drv - the driver table entry that matched
329 *
330 * Return value:
331 * 0
332 ********************************************************************* */
333
334const cfe_driver_t usbcatcdrv; /* forward declaration */
335
336static int catc_attach(usbdev_t *dev, usb_driver_t *drv)
337{
338 usb_config_descr_t *cfgdscr = dev->ud_cfgdescr;
339 usb_endpoint_descr_t *epdscr;
340 usb_endpoint_descr_t *indscr = NULL;
341 usb_endpoint_descr_t *outdscr = NULL;
342 usb_interface_descr_t *ifdscr;
343 catc_softc_t *softc;
344 int idx;
345
346 dev->ud_drv = drv;
347
348 softc = (catc_softc_t *) KMALLOC( sizeof(catc_softc_t), 0 );
349 if( softc == NULL ) {
350 xprintf( "Failed to allocate softc memory.\n" );
351 return -1;
352 }
353 memset( softc, 0, sizeof(catc_softc_t) );
354 dev->ud_private = softc;
355 softc->dev = dev;
356
358 if (ifdscr == NULL) {
359 xprintf("USBETH: ERROR...no interace descriptor\n");
360 return -1;
361 }
362
363 for (idx = 0; idx < 2; idx++) {
366 outdscr = epdscr;
367 else
368 indscr = epdscr;
369 }
370
371 if (!indscr || !outdscr) {
372 /*
373 * Could not get descriptors, something is very wrong.
374 * Leave device addressed but not configured.
375 */
376 xprintf("USBETH: ERROR...no endpoint descriptors\n");
377 return -1;
378 }
379
380 /* Choose the standard configuration. */
382
383 /* Quit if not able to initialize the device */
384 if (catc_init_device(softc) < 0)
385 return -1;
386
387 /* Open the pipes. */
388 softc->bulk_inpipe = usb_open_pipe(dev,indscr);
389 softc->bulk_outpipe = usb_open_pipe(dev,outdscr);
390
391 /* Register the device */
392 usbeth_register(&usbeth_catc,softc);
393
394 /* Open the device */
395 catc_open_device( softc );
396
397 return 0;
398}
399
400/* *********************************************************************
401 * catc_detach(dev)
402 *
403 * This routine is called when the bus scanner notices that
404 * this device has been removed from the system. We should
405 * do any cleanup that is required. The pending requests
406 * will be cancelled automagically.
407 *
408 * Input parameters:
409 * dev - usb device
410 *
411 * Return value:
412 * 0
413 ********************************************************************* */
414
415static int catc_detach(usbdev_t *dev)
416{
417 catc_softc_t *softc = (catc_softc_t *) dev->ud_private;
418
419 if (softc != NULL) {
420 usbeth_unregister( softc );
421 catc_close_device ( softc );
422 dev->ud_private = NULL;
423 softc->dev = NULL;
424 KFREE(softc);
425 }
426
427 return 0;
428}
429
430/* CFE USB device interface structure */
432{
433 "Ethernet Device",
434 catc_attach,
435 catc_detach
436};
437
438
439/* *********************************************************************
440 * CFE-Ethernet device interfaces
441 ********************************************************************* */
442
443
444static int catc_ether_open(cfe_devctx_t *ctx)
445{
446 catc_softc_t *softc = (catc_softc_t *) ctx->dev_softc;
447
448 if (softc->dev == NULL)
449 return CFE_ERR_NOTREADY;
450
451 USBETH_TRACE( "%s called.\n", __FUNCTION__ );
452 catc_open_device( softc );
453
454 return 0;
455}
456
457static int catc_ether_read( cfe_devctx_t *ctx, iocb_buffer_t *buffer )
458{
459 catc_softc_t *softc = (catc_softc_t *) ctx->dev_softc;
460
461 if (softc->dev == NULL)
462 return CFE_ERR_NOTREADY;
463
464 buffer->buf_retlen = catc_get_eth_frame( softc, buffer->buf_ptr );
465
466 return 0;
467}
468
469
470static int catc_ether_inpstat( cfe_devctx_t *ctx, iocb_inpstat_t *inpstat )
471{
472 catc_softc_t *softc = (catc_softc_t *) ctx->dev_softc;
473
474 if (softc->dev == NULL)
475 return CFE_ERR_NOTREADY;
476
477 inpstat->inp_status = catc_data_rx( softc );
478 return 0;
479}
480
481
482static int catc_ether_write(cfe_devctx_t *ctx,iocb_buffer_t *buffer)
483{
484 catc_softc_t *softc = (catc_softc_t *) ctx->dev_softc;
485
486 if (softc->dev == NULL)
487 return CFE_ERR_NOTREADY;
488
489 /* Block until hw notifies you data is sent. */
490 catc_send_eth_frame( softc, buffer->buf_ptr, buffer->buf_length );
491
492 return 0;
493}
494
495
496static int catc_ether_ioctl(cfe_devctx_t *ctx,iocb_buffer_t *buffer)
497{
498 catc_softc_t *softc = (catc_softc_t *) ctx->dev_softc;
499 int retval = 0;
500
501 if (softc->dev == NULL)
502 return CFE_ERR_NOTREADY;
503
504 switch( (int)buffer->buf_ioctlcmd ) {
505 case IOCTL_ETHER_GETHWADDR:
506 USBETH_TRACE( "IOCTL_ETHER_GETHWADDR called.\n" );
507 catc_get_dev_addr( softc, buffer->buf_ptr );
508 break;
509 case IOCTL_ETHER_SETHWADDR:
510 xprintf( "IOCTL_ETHER_SETHWADDR not implemented.\n" );
511 break;
512#if 0
513 case IOCTL_ETHER_GETSPEED:
514 xprintf( "GETSPEED not implemented.\n" );
515 retval = -1;
516 break;
517 case IOCTL_ETHER_SETSPEED:
518 xprintf( "SETSPEED not implemented.\n" );
519 retval = -1;
520 break;
521 case IOCTL_ETHER_GETLINK:
522 xprintf( "GETLINK not implemented.\n" );
523 retval = -1;
524 break;
525 case IOCTL_ETHER_GETLOOPBACK:
526 xprintf( "GETLOOPBACK not implemented.\n" );
527 retval = -1;
528 break;
529 case IOCTL_ETHER_SETLOOPBACK:
530 xprintf( "SETLOOPBACK not implemented.\n" );
531 retval = -1;
532 break;
533#endif
534 default:
535 xprintf( "Invalid IOCTL to catc_ether_ioctl.\n" );
536 retval = -1;
537 }
538
539 return retval;
540}
541
542
543static int catc_ether_close(cfe_devctx_t *ctx)
544{
545 catc_softc_t *softc = (catc_softc_t *) ctx->dev_softc;
546
547 if (softc->dev == NULL)
548 return CFE_ERR_NOTREADY;
549
550 USBETH_TRACE( "%s called.\n", __FUNCTION__ );
551 catc_close_device( softc );
552 return 0;
553}
554
555
556/* CFE ethernet device interface structures */
557
558const static cfe_devdisp_t catc_ether_dispatch =
559{
560 catc_ether_open,
561 catc_ether_read,
562 catc_ether_inpstat,
563 catc_ether_write,
564 catc_ether_ioctl,
565 catc_ether_close,
566 NULL,
567 NULL
568};
569
570const cfe_driver_t usbcatcdrv =
571{
572 "USB-Ethernet Device",
573 "eth",
574 CFE_DEV_NETWORK,
575 &catc_ether_dispatch,
576 NULL, /* probe...not needed */
577};
578
void * hsaddr_t
Definition: cfe.h:11
#define NULL
Definition: def.h:47
usb_driver_t usbcatc_driver
Definition: dev_usb_catc.c:431
#define CACHE_ALIGN
Definition: dev_usb_catc.c:62
const cfe_driver_t usbcatcdrv
Definition: dev_usb_catc.c:334
#define hexdump(src, srclen, rowlen, rows)
Definition: dev_usb_catc.c:94
#define usb_dma_free(p)
Definition: dev_usb_catc.c:66
#define usb_dma_alloc(n)
Definition: dev_usb_catc.c:65
#define USBETH_TRACE(x, y ...)
Definition: dev_usb_catc.c:57
struct catc_softc_s catc_softc_t
@ CATC_NM
Definition: dev_usb_catc.c:102
@ BELKIN_CATC
Definition: dev_usb_catc.c:102
@ VEN_NONE
Definition: dev_usb_catc.c:102
static uint32_t val
Definition: io.h:17
u32 ptr
Definition: iso9660.c:536
#define KMALLOC(size, align)
Definition: lib_malloc.h:92
#define KFREE(ptr)
Definition: lib_malloc.h:93
s16 int16_t
Definition: libfdt_env.h:15
u16 uint16_t
Definition: libfdt_env.h:10
u8 uint8_t
Definition: libfdt_env.h:9
usbreq_t * rx_ur
Definition: dev_usb_catc.c:119
usbdev_t * dev
Definition: dev_usb_catc.c:114
uint8_t mac_addr[6]
Definition: dev_usb_catc.c:118
uint8_t rxbuf[1600]
Definition: dev_usb_catc.c:120
uint8_t bConfigurationValue
Definition: usbchap9.h:216
uint8_t idVendorLow
Definition: usbchap9.h:184
uint8_t idProductLow
Definition: usbchap9.h:185
uint8_t idProductHigh
Definition: usbchap9.h:185
uint8_t idVendorHigh
Definition: usbchap9.h:184
uint8_t bEndpointAddress
Definition: usbchap9.h:196
Definition: usbd.h:141
usb_config_descr_t * ud_cfgdescr
Definition: usbd.h:150
usbbus_t * ud_bus
Definition: usbd.h:143
void * ud_private
Definition: usbd.h:148
usb_driver_t * ud_drv
Definition: usbd.h:142
Definition: usbd.h:171
int ur_inprogress
Definition: usbd.h:195
int ur_xferred
Definition: usbd.h:187
#define USBREQ_TYPE_VENDOR
Definition: usbchap9.h:366
#define USB_ENDPOINT_DESCRIPTOR_TYPE
Definition: usbchap9.h:66
#define USB_ENDPOINT_DIR_OUT(addr)
Definition: usbchap9.h:107
#define USBREQ_DIR_OUT
Definition: usbchap9.h:363
#define USB_INTERFACE_DESCRIPTOR_TYPE
Definition: usbchap9.h:65
#define USBREQ_DIR_IN
Definition: usbchap9.h:362
#define USBREQ_REC_INTERFACE
Definition: usbchap9.h:369
#define USBREQ_TYPE_STD
Definition: usbchap9.h:364
#define USB_REQUEST_SET_INTERFACE
Definition: usbchap9.h:134
void * usb_find_cfg_descr(usbdev_t *dev, int dtype, int idx)
Definition: usbd.c:1237
int usb_std_request(usbdev_t *dev, uint8_t bmRequestType, uint8_t bRequest, uint16_t wValue, uint16_t wIndex, uint8_t *buffer, int length)
Definition: usbd.c:796
int usb_sync_request(usbreq_t *ur)
Definition: usbd.c:550
usbreq_t * usb_make_request(usbdev_t *dev, int epaddr, uint8_t *buf, int length, int flags)
Definition: usbd.c:353
void usb_free_request(usbreq_t *ur)
Definition: usbd.c:457
void usb_poll(usbbus_t *bus)
Definition: usbd.c:392
int usb_queue_request(usbreq_t *ur)
Definition: usbd.c:502
int usb_get_device_descriptor(usbdev_t *dev, usb_device_descr_t *dscr, int smallflg)
Definition: usbd.c:1006
int usb_set_configuration(usbdev_t *dev, int config)
Definition: usbd.c:619
int usb_open_pipe(usbdev_t *dev, usb_endpoint_descr_t *epdesc)
Definition: usbd.c:165
#define UR_FLAG_OUT
Definition: usbd.h:165
#define UR_FLAG_IN
Definition: usbd.h:164
#define UR_FLAG_SHORTOK
Definition: usbd.h:168
int usbeth_register(usbeth_disp_t *disp, void *softc)
Definition: usbeth.c:78
void usbeth_unregister(void *softc)
Definition: usbeth.c:97
#define CATC_RX_BUF_CNT_REG
Definition: usbeth.h:64
#define CATC_TX_BUF_CNT_REG
Definition: usbeth.h:63
#define CATC_ETH_CTRL_REG
Definition: usbeth.h:68
#define CATC_ADV_OP_MODES_REG
Definition: usbeth.h:65
#define CATC_MCAST_TBL_ADDR
Definition: usbeth.h:56
#define CATC_GET_REG
Definition: usbeth.h:60
#define CATC_GET_MAC_ADDR
Definition: usbeth.h:58
#define CATC_SET_MEM
Definition: usbeth.h:61
#define CATC_LED_CTRL_REG
Definition: usbeth.h:71
#define CATC_SET_REG
Definition: usbeth.h:59
#define CATC_ETH_ADDR_0_REG
Definition: usbeth.h:70
int xprintf(char *str)
Definition: usbhack.c:404
union @15 data