// roughly based off https://github.com/xairy/raw-gadget/blob/master/examples/keyboard.c // NOTE: this is playing fast-and-loose with endianness, it'll break on big-endian systems. #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define SYSCHK(x) ({ \ typeof(x) __res = (x); \ if (__res == (typeof(x))-1) \ err(1, "SYSCHK(" #x ")"); \ __res; \ }) static int usb_fd; #define errx(code, ...) { printf(__VA_ARGS__); printf("\n"); while (1) pause(); } struct usb_raw_control_event { struct usb_raw_event inner; struct usb_ctrlrequest ctrl; }; static struct usb_device_descriptor usb_device = { .bLength = USB_DT_DEVICE_SIZE, .bDescriptorType = USB_DT_DEVICE, .bcdUSB = __constant_cpu_to_le16(0x0200), /* USB 2.0 */ .bDeviceClass = __constant_cpu_to_le16(USB_CLASS_COMM), // is this even used? .bMaxPacketSize0 = 64, /* maximum value the kernel lets us use */ /* "We are a very legit..." (checks notes) "ASIX 88172a demo board" (looks * back up) "and would like to help you get connected to the network!" */ .idVendor = __constant_cpu_to_le16(0x0b95), .idProduct = __constant_cpu_to_le16(0x172a), .iManufacturer = 1, .iProduct = 2, .iSerialNumber = 3, .bNumConfigurations = 1 }; struct usb_config_descriptor usb_config = { .bLength = USB_DT_CONFIG_SIZE, .bDescriptorType = USB_DT_CONFIG, .wTotalLength = 0, // fixed up later .bNumInterfaces = 1, .bConfigurationValue = 1, .iConfiguration = 4, .bmAttributes = USB_CONFIG_ATT_ONE |// must be set USB_CONFIG_ATT_SELFPOWER, .bMaxPower = 0 // no power draw from host }; struct usb_interface_descriptor usb_interface = { .bLength = USB_DT_INTERFACE_SIZE, .bDescriptorType = USB_DT_INTERFACE, .bInterfaceNumber = 0, .bAlternateSetting = 0, .bNumEndpoints = 3, .bInterfaceClass = USB_CLASS_COMM, // ? .bInterfaceSubClass = USB_CDC_SUBCLASS_ETHERNET, .bInterfaceProtocol = USB_CDC_PROTO_NONE, .iInterface = 5, }; struct usb_endpoint_descriptor usb_endpoint_in = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = USB_DIR_IN | 1, .bmAttributes = USB_ENDPOINT_XFER_BULK, .wMaxPacketSize = 512, //.bInterval = 1 }; struct usb_endpoint_descriptor usb_endpoint_out = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = USB_DIR_OUT | 1, .bmAttributes = USB_ENDPOINT_XFER_BULK, .wMaxPacketSize = 512, //.bInterval = 1 }; struct usb_endpoint_descriptor usb_endpoint_intr = { .bLength = USB_DT_ENDPOINT_SIZE, .bDescriptorType = USB_DT_ENDPOINT, .bEndpointAddress = USB_DIR_IN | 2, .bmAttributes = USB_ENDPOINT_XFER_INT, .wMaxPacketSize = 8, .bInterval = 1 }; unsigned short intr_handle; void usb_reply(void *data, size_t len, size_t req_length) { printf(" REPLY: size %lu, req_length %lu\n", (unsigned long)len, (unsigned long)req_length); if (len > req_length) len = req_length; struct usb_raw_ep_io *io = alloca(sizeof(struct usb_raw_ep_io)+len); io->ep = 0; io->flags = 0; io->length = len; memcpy(io->data, data, len); SYSCHK(ioctl(usb_fd, USB_RAW_IOCTL_EP0_WRITE, io)); } void usb_reply_zero(size_t req_length) { char *data = alloca(req_length); memset(data, '\0', req_length); usb_reply(data, req_length, req_length); } void usb_get_and_ack(void *buf, size_t len, size_t req_length) { printf(" ACK OUT: req_length %lu\n", (unsigned long)req_length); struct usb_raw_ep_io *io = alloca(sizeof(struct usb_raw_ep_io)+req_length); memset(io, 0xee, sizeof(struct usb_raw_ep_io)+req_length); io->ep = 0; io->flags = 0; io->length = req_length; //printf("issuing USB_RAW_IOCTL_EP0_READ with io->length=%u\n", io->length); int ret_len = SYSCHK(ioctl(usb_fd, USB_RAW_IOCTL_EP0_READ, io)); assert(ret_len == req_length); if (buf) { if (len > req_length) { memset(buf, '\0', len); len = req_length; } memcpy(buf, ((char*)io)+sizeof(struct usb_raw_ep_io), len); } } void usb_ack(size_t req_length) { usb_get_and_ack(NULL, 0, req_length); } void descr_append(void *buf, size_t *buf_len, void *descr, size_t descr_len) { printf(" descr_append(buf, len, descr, descr_len=%lu with first byte %hhu\n", (unsigned long)descr_len, *(unsigned char *)descr); assert(descr_len <= 255); assert(descr_len >= 2); assert(((unsigned char*)descr)[0] == descr_len); memcpy(buf + *buf_len, descr, descr_len); (*buf_len) += descr_len; ((struct usb_config_descriptor*)buf)->wTotalLength = __cpu_to_le16(*buf_len); } int state = 0; void handle_alarm(int sig) { if (state == 0) state = 1; } int main(int argc, char **argv) { setbuf(stdout, NULL); setbuf(stderr, NULL); usb_fd = SYSCHK(open("/dev/raw-gadget", O_RDWR)); struct usb_raw_init init_args = { .speed = USB_SPEED_HIGH }; strcpy(init_args.driver_name, argv[1]); strcpy(init_args.device_name, argv[2]); SYSCHK(ioctl(usb_fd, USB_RAW_IOCTL_INIT, &init_args)); SYSCHK(ioctl(usb_fd, USB_RAW_IOCTL_RUN, 0)); struct sigaction alarm_act = { .sa_handler = handle_alarm, .sa_flags = 0 }; if (sigaction(SIGALRM, &alarm_act, NULL)) err(1, "sigaction"); //alarm(30); while (1) { if (state == 1) { printf("===================== BRINGING LINK UP =====================\n"); state = 2; struct { struct usb_raw_ep_io io; struct { /*ax88172_int_data*/ unsigned short res1; unsigned char link; unsigned short res2; unsigned char status; unsigned short res3; unsigned int intdata2; } __attribute__((packed)) data; } io_with_data = { .io = { .ep = intr_handle, .flags = 0, .length = sizeof(io_with_data.data) }, .data = { .link = 1 } }; SYSCHK(ioctl(usb_fd, USB_RAW_IOCTL_EP_WRITE, &io_with_data)); /* close(usb_fd); exit(0); */ } struct usb_raw_control_event control_ev = { .inner = { .type = 0, .length = sizeof(control_ev.ctrl) } }; int res = ioctl(usb_fd, USB_RAW_IOCTL_EVENT_FETCH, &control_ev); if (res == -1) { if (errno == EINTR) continue; err(1, "USB_RAW_IOCTL_EVENT_FETCH"); } if (control_ev.inner.type == USB_RAW_EVENT_CONNECT) { // nothing to do } else if (control_ev.inner.type == USB_RAW_EVENT_CONTROL) { unsigned req_length = __le16_to_cpu(control_ev.ctrl.wLength); bool is_dir_in = (control_ev.ctrl.bRequestType & USB_DIR_IN) != 0; printf("got control (in=%d, wLength=%u):\n", is_dir_in, req_length); switch (control_ev.ctrl.bRequestType & USB_TYPE_MASK) { case USB_TYPE_STANDARD: switch(control_ev.ctrl.bRequest) { case USB_REQ_GET_DESCRIPTOR: { unsigned descriptor_type = control_ev.ctrl.wValue >> 8; switch (descriptor_type) { case USB_DT_DEVICE: printf(" getting device descriptor\n"); usb_reply(&usb_device, sizeof(usb_device), req_length); break; case USB_DT_CONFIG: { printf(" getting dt config\n"); char dt_config[1024*128]; size_t dt_config_len = 0; descr_append(dt_config, &dt_config_len, &usb_config, sizeof(usb_config)); descr_append(dt_config, &dt_config_len, &usb_interface, sizeof(usb_interface)); descr_append(dt_config, &dt_config_len, &usb_endpoint_in, USB_DT_ENDPOINT_SIZE); descr_append(dt_config, &dt_config_len, &usb_endpoint_out, USB_DT_ENDPOINT_SIZE); descr_append(dt_config, &dt_config_len, &usb_endpoint_intr, USB_DT_ENDPOINT_SIZE); usb_reply(dt_config, dt_config_len, req_length); } break; case USB_DT_STRING: { unsigned string_id = control_ev.ctrl.wValue & 0xff; printf(" getting string %d\n", string_id); if (string_id == 0) { unsigned char string_descr[] = { 4/*length*/, USB_DT_STRING, 0x09, 0x04 /* English - United States */ }; usb_reply(string_descr, sizeof(string_descr), req_length); } else { unsigned char string_descr[] = { 12/*length*/, USB_DT_STRING, 'D',0,'U',0,'M',0,'M',0,'Y',0 }; usb_reply(string_descr, sizeof(string_descr), req_length); } } break; default: errx(1, "USB_REQ_GET_DESCRIPTOR: descriptor_type=%x", descriptor_type); } } break; case USB_REQ_SET_CONFIGURATION: { printf(" set configuration\n"); SYSCHK(ioctl(usb_fd, USB_RAW_IOCTL_EP_ENABLE, &usb_endpoint_in)); SYSCHK(ioctl(usb_fd, USB_RAW_IOCTL_EP_ENABLE, &usb_endpoint_out)); intr_handle = SYSCHK(ioctl(usb_fd, USB_RAW_IOCTL_EP_ENABLE, &usb_endpoint_intr)); SYSCHK(ioctl(usb_fd, USB_RAW_IOCTL_CONFIGURE, 0)); usb_ack(req_length); } break; case USB_REQ_SET_INTERFACE: { printf(" set interface\n"); usb_ack(req_length); } break; default: errx(1, "unknown standard ctrlrequest 0x%x", (unsigned)control_ev.ctrl.bRequest); } break; case USB_TYPE_CLASS: errx(1, "unknown class ctrlrequest"); case USB_TYPE_VENDOR: printf(" vendor ctrlrequest bRequest=0x%02hhx wValue=0x%04hx wIndex=0x%04hx wLength=0x%04hx\n", control_ev.ctrl.bRequest, control_ev.ctrl.wValue, control_ev.ctrl.wIndex, control_ev.ctrl.wLength); if (!is_dir_in) { usb_ack(req_length); continue; } switch (control_ev.ctrl.bRequest) { #if 1 case 0x01/*AX_ACCESS_MAC*/: { static unsigned char srom_addr; static const unsigned short ledvalue = (1<<15/*LED_VALID*/); printf(" AX_ACCESS_MAC\n"); switch (control_ev.ctrl.wValue) { case 0x02/*PHYSICAL_LINK_STATUS*/: { printf(" PHYSICAL_LINK_STATUS\n"); /* Linux checks AX_USB_SS and AX_USB_HS, influences URB size: * ->rx_urb_size * can be 0x5000 / 0x6000 / 0x6800 / 0x6800 * We prefer the smallest one (0x5000), which we get from * setting AX_USB_SS plus GMII_PHY_PHYSR_GIGA. */ unsigned char pl_status = 0x04; /*AX_USB_SS*/ usb_reply(&pl_status, sizeof(pl_status), req_length); } break; case 0x03/*GENERAL_STATUS*/: { printf(" GENERAL_STATUS\n"); unsigned short general_status = __cpu_to_le16(0x04/*AX_SECLD*/); usb_reply(&general_status, 2, req_length); } break; case 0x07/*AX_SROM_ADDR*/: { printf(" AX_SROM_ADDR\n"); usb_get_and_ack(&srom_addr, 1, req_length); printf(" SROM address: 0x%hhx\n", srom_addr); } break; case 0x08/*AX_SROM_DATA_LOW*/: { printf(" AX_SROM_DATA_LOW from 0x%hhx\n", srom_addr); if (srom_addr < 6) { unsigned char eeprom_head[12] = { /*0-5*/0, 0, 0, 0, 0, 0, // dontcare (first must not be FF) /*6-9*/0, 0, 0, 0, // checksummed /*10*/0xff // checksum }; usb_reply(eeprom_head + srom_addr*2, 2, req_length); } else if (srom_addr == 0x42) { unsigned char b = 8 & 0xff; usb_reply(&b, 1, req_length); } else { errx(1, "unhandled SROM range"); } } break; case 0x09/*AX_SROM_DATA_HIGH*/: { printf(" AX_SROM_DATA_HIGH from 0x%hhx\n", srom_addr); if (srom_addr == 0x42) { unsigned char b = ledvalue >> 8; usb_reply(&b, 1, req_length); } else { errx(1, "unhandled SROM range"); } } break; case 0x0a/*AX_SROM_CMD*/: { printf(" AX_SROM_CMD\n"); if (is_dir_in) { unsigned char value = 0; // EEP_BUSY would spin until timeout usb_reply(&value, 1, req_length); } else { usb_ack(req_length); } } break; case 0x0b/*AX_RX_CTL*/: { unsigned short ax_rx_ctl_value; usb_get_and_ack(&ax_rx_ctl_value, sizeof(ax_rx_ctl_value), req_length); printf(" AX_RX_CTL = 0x%04hx ################\n", ax_rx_ctl_value); } break; case 0x16/*AX_MULFLTARY*/: { printf(" AX_MULFLTARY ||||||||||||||||||||||||||||||||||||||||||\n"); usb_ack(req_length); if (state == 0) state = 1; } break; case 0x22/*AX_MEDIUM_STATUS_MODE*/: { printf(" AX_MEDIUM_STATUS_MODE\n"); usb_ack(req_length); } break; case 0x24/*AX_MONITOR_MOD*/: { printf(" AX_MONITOR_MOD\n"); usb_ack(req_length); } break; case 0x26/*AX_PHYPWR_RSTCTL*/: { printf(" AX_PHYPWR_RSTCTL\n"); usb_ack(req_length); } break; case 0x2e/*AX_RX_BULKIN_QCTRL*/: { printf(" AX_RX_BULKIN_QCTRL\n"); usb_ack(req_length); } break; case 0x34/*AX_RXCOE_CTL*/: { printf(" AX_RXCOE_CTL\n"); usb_ack(req_length); } break; case 0x35/*AX_TXCOE_CTL*/: { printf(" AX_TXCOE_CTL\n"); usb_ack(req_length); } break; case 0x54/*AX_PAUSE_WATERLVL_HIGH*/: { printf(" AX_PAUSE_WATERLVL_HIGH\n"); usb_ack(req_length); } break; case 0x55/*AX_PAUSE_WATERLVL_LOW*/: { printf(" AX_PAUSE_WATERLVL_LOW\n"); usb_ack(req_length); } break; case 0x73/*AX_LEDCTRL*/: { printf(" AX_LEDCTRL\n"); usb_ack(req_length); } break; case 0x33/*AX_CLOCK_SELECT*/: { printf(" AX_CLOCK_SELECT\n"); usb_ack(req_length); } break; case 0x10/*AX_NODE_ID*/: { printf(" AX_NODE_ID\n"); if (is_dir_in) { unsigned char mac_addr[] = { 0x00, 0x12, 0x34, 0x56, 0x78, 0x90 }; usb_reply(mac_addr, sizeof(mac_addr), req_length); } else { usb_ack(req_length); } } break; default: if (is_dir_in) { errx(1, " unknown AX_ACCESS_MAC command 0x%02hhx", control_ev.ctrl.wValue); } else { printf(" ignoring unknown OUT AX_ACCESS_MAC command 0x%02hx\n", control_ev.ctrl.wValue); } } } break; case 0x02/*AX_ACCESS_PHY*/: { printf(" AX_ACCESS_PHY\n"); assert(control_ev.ctrl.wValue == 0x03/*AX88179_PHY_ID*/); static unsigned short mmd_id; static bool no_auto_increment; switch (control_ev.ctrl.wIndex) { case MII_BMCR: { /* Basic mode control register */ /* idk what any of these flags actually do in detail... */ printf(" MII_BMCR (basic mode control register)\n"); if (is_dir_in) { unsigned short bmcr_state = BMCR_SPEED1000 | BMCR_FULLDPLX; usb_reply(&bmcr_state, sizeof(bmcr_state), req_length); } else { usb_ack(req_length); } } break; case MII_MMD_CTRL: { printf(" MII_MMD_CTRL\n"); usb_get_and_ack(&mmd_id, 2, req_length); mmd_id &= ~0x4000; no_auto_increment = (mmd_id & 0x4000) != 0; printf(" set ID %d, no-auto-increment %d\n", mmd_id, no_auto_increment); } break; case MII_MMD_DATA: { printf(" MII_MMD_DATA (at ID %d)\n", mmd_id); if (is_dir_in) { switch (mmd_id) { case MDIO_MMD_PCS: printf(" Physical Coding Sublayer\n"); // decoded via mmd_eee_cap_to_ethtool_sup_t() unsigned short eee_caps = MDIO_EEE_1000T; usb_reply(&eee_caps, sizeof(eee_caps), req_length); break; case MDIO_MMD_AN: printf(" Auto-Negotiation\n"); // WARNING: this is actually used in two different // contexts unsigned short eee_adv = MDIO_EEE_1000T; usb_reply(&eee_adv, sizeof(eee_adv), req_length); break; default: errx(1, "unknown ID %d", mmd_id); } } else { usb_ack(req_length); } } break; case MII_PHYADDR: { printf(" MII_PHYADDR\n"); usb_ack(req_length); } break; case 0x11/*GMII_PHY_PHYSR*/: { printf(" MII_PHY_PHYSR\n"); unsigned short physr = 0x0400/*GMII_PHY_PHYSR_LINK*/ | 0x8000/*GMII_PHY_PHYSR_GIGA*/; usb_reply(&physr, sizeof(physr), req_length); } break; case 0x1a/*GMII_LED_ACT*/: { printf(" GMII_LED_ACT\n"); if (is_dir_in) { unsigned short ledact = 0; usb_reply(&ledact, sizeof(ledact), req_length); } else { usb_ack(req_length); } } break; case 0x1c/*GMII_LED_LINK*/: { printf(" GMII_LED_LINK\n"); if (is_dir_in) { unsigned short ledlink = 0; usb_reply(&ledlink, sizeof(ledlink), req_length); } else { usb_ack(req_length); } } break; case 0x1e/*GMII_PHYPAGE*/: { printf(" GMII_PHYPAGE\n"); usb_ack(req_length); } break; case 0x1f/*GMII_PHY_PAGE_SELECT*/: { printf(" GMII_PHY_PAGE_SELECT\n"); usb_ack(req_length); } break; default: errx(1, " unknown AX_ACCESS_PHY command 0x%02hhx", control_ev.ctrl.wIndex); } } break; case 0x04/*AX_ACCESS_PHY*/: { unsigned eeprom_idx = (unsigned)control_ev.ctrl.wValue; printf(" AX_ACCESS_EEPROM at 0x%x\n", eeprom_idx); if (is_dir_in) { printf(" EEPROM read\n"); switch (eeprom_idx) { case 0x43: {/* autodetach */ unsigned short autodetach = 0xffff; usb_reply(&autodetach, sizeof(autodetach), req_length); } break; default: errx(1, "unhandled EEPROM offset"); } } else { printf(" ignoring EEPROM write\n"); usb_ack(req_length); } } break; case 0x81: { printf(" 0x81 TX FIFO check\n"); unsigned int tx_fifo_state = 0; usb_reply(&tx_fifo_state, sizeof(tx_fifo_state), req_length); } break; #endif case 0x13/*AX_CMD_READ_NODE_ID*/: { unsigned char mac_addr[] = { 0x00, 0x12, 0x34, 0x56, 0x78, 0x90 }; usb_reply(mac_addr, sizeof(mac_addr), req_length); } break; case 0x21/*AX_CMD_SW_PHY_STATUS*/: case 0x1a/*AX_CMD_READ_MEDIUM_STATUS*/: case 0x0f/*AX_CMD_READ_RX_CTL*/: { usb_reply_zero(req_length); } break; case 0x19/*AX_CMD_READ_PHY_ID*/: { printf(" CMD_READ_PHY_ID\n"); unsigned short phy_id = 1; usb_reply(&phy_id, sizeof(phy_id), req_length); } break; case 0x09/*AX_CMD_STATMNGSTS_REG*/: { printf(" CMD_STATMNGSTS_REG\n"); unsigned char val = 0x01; /* AX_HOST_EN */ usb_reply(&val, sizeof(val), req_length); } break; case 0x07/*AX_CMD_READ_MII_REG*/: { printf(" CMD_READ_MII_REG\n"); /* unsigned char val = 0x01; usb_reply(&val, sizeof(val), req_length); */ switch (control_ev.ctrl.wIndex) { case MII_BMCR: { /* Basic mode control register */ /* idk what any of these flags actually do in detail... */ printf(" MII_BMCR (basic mode control register)\n"); unsigned short bmcr_state = BMCR_SPEED100 | BMCR_FULLDPLX; usb_reply(&bmcr_state, sizeof(bmcr_state), req_length); } break; case MII_BMSR: { /* Basic mode status register */ printf(" MII_BMSR (basic mode status register)\n"); /* say link is initially down */ unsigned short bmsr_state = ((state > 0) ? BMSR_LSTATUS : 0) | BMSR_100FULL; usb_reply(&bmsr_state, sizeof(bmsr_state), req_length); } break; case MII_ADVERTISE: { /* Advertisement control register */ printf(" MII_ADVERTISE\n"); unsigned short adv_state = ADVERTISE_100FULL; usb_reply(&adv_state, sizeof(adv_state), req_length); } break; case MII_LPA: { /* Link partner ability */ printf(" MII_LPA (Link partner ability)\n"); unsigned short lpa_state = LPA_100FULL; usb_reply(&lpa_state, sizeof(lpa_state), req_length); } break; case MII_PHYSID1: case MII_PHYSID2: { printf(" MII_PHYSID\n"); unsigned short physid = 1; usb_reply(&physid, sizeof(physid), req_length); } break; default: errx(1, " unknown READ_MII_REG command 0x%02hhx", control_ev.ctrl.wIndex); } } break; #if 0 case 0x10/*AX_CMD_WRITE_RX_CTL*/: { printf(" CMD_WRITE_RX_CTL = 0x%hx\n", control_ev.ctrl.wIndex); usb_ack(req_length); } break; case 0x16/*AX_CMD_WRITE_MULTI_FILTER*/: { printf(" CMD_WRITE_MULTI_FILTER\n"); usb_ack(req_length); } break; #endif default: if (is_dir_in) { errx(1, "unknown vendor ctrlrequest 0x%02hhx", control_ev.ctrl.bRequest); } else { printf(" ignoring unknown vendor ctrlrequest 0x%02hx\n", control_ev.ctrl.bRequest); usb_ack(req_length); } } break; default: errx(1, "USB_TYPE_* unknown"); } } else { printf("unknown event, type 0x%x\n", (unsigned)control_ev.inner.type); } } }