int main(void) { uint32_t tTime; main_init(); tTime = millis(); while(1) { if( millis()-tTime > 100 ) { tTime = millis(); led_toggle(0); } #if 0 uint8_t ch; static uint32_t cnt = 0; vcp_printf("cnd : %d \r\n", cnt++); if( vcp_is_available() ) { ch = vcp_getch(); vcp_printf("pressed : 0x%02X \r\n", ch); //vcp_printf("float test %f\r\n",fvalue); } #else msg_process_vcp(); #endif } }
void HID_Default_Data_Handler(void* p_io, uint8_t* data, uint16_t len) { USBH_DevIO_t* p_in = (USBH_DevIO_t*)p_io; vcp_printf("%s:C%d:IntIN:", USBH_Dev_DebugPrint(p_in->pdev, p_in->ep), len); for (uint8_t k = 0; k < len; k++) { vcp_printf(" 0x%02X", data[k]); } vcp_printf("\r\n"); //dbg_printf(DBGMODE_DEBUG, "%s:C%d:IntIN:\r\n", USBH_Dev_DebugPrint(p_in->pdev, p_in->ep), len); HID_Rpt_Parsing_Params_t* parser = (HID_Rpt_Parsing_Params_t*)(p_in->extra); if (parser->mouse_exists > 0 && parser->mouse_ep == p_in->ep->bEndpointAddress && (parser->mouse_report_id <= 0 || parser->mouse_report_id == data[0])) { if (parser->mouse_report_id > 0) { data = &data[1]; len--; } kbm2c_handleMouseReport(data, len, parser); } if (parser->kb_exists > 0 && parser->kb_ep == p_in->ep->bEndpointAddress && (parser->kb_report_id <= 0 || parser->kb_report_id == data[0])) { if (parser->kb_report_id > 0) { data = &data[1]; len--; } kbm2c_handleKeyReport(data[0], &data[2], len - 2); } }
void USBH_Dev_DefaultCB_EnumerationDone(USB_OTG_CORE_HANDLE *pcore , USBH_DEV *pdev) { vcp_printf("Unknown Class Device V%04XP%04XA%d Enumerated\r\n", pdev->device_prop.Dev_Desc.idVendor, pdev->device_prop.Dev_Desc.idProduct, pdev->device_prop.address); }
void USBH_Dev_HID_EnumerationDone(USB_OTG_CORE_HANDLE *pcore, USBH_DEV *pdev) { if (pdev->Usr_Data != 0) { free(pdev->Usr_Data); pdev->Usr_Data = 0; } uint8_t numIntf = pdev->device_prop.Cfg_Desc.bNumInterfaces; char isHid = 0; pdev->Usr_Data = calloc(1, sizeof(HID_Data_t)); HID_Data_t* HID_Data = pdev->Usr_Data; HID_Data->io = calloc(numIntf, sizeof(USBH_DevIO_t*)); HID_Data->ioIdx = 0; dbg_printf(DBGMODE_DEBUG, "USBH_Dev_HID_EnumerationDone, bNumInterfaces: %d\r\n", numIntf); for (int i = 0; i < numIntf; i++) { // filter out non-HID class interfaces if (pdev->device_prop.Itf_Desc[i].bInterfaceClass != 0x03) { continue; } isHid = 1; USBH_DevD2H_DataHandler_t dataHandler = 0; HID_Rpt_Parsing_Params_t* parser = 0; uint8_t parserEp = 0; if (pdev->device_prop.Dev_Desc.idVendor == SONY_VID && pdev->device_prop.Dev_Desc.idProduct == DUALSHOCK3_PID) { // DUALSHOCK3 dataHandler = USBH_DS3_Data_Handler; dbg_printf(DBGMODE_TRACE, "Dualshock 3 Detected\r\n"); } else if (pdev->device_prop.Dev_Desc.idVendor == SONY_VID && pdev->device_prop.Dev_Desc.idProduct == DUALSHOCK4_PID) { // DUALSHOCK4 dataHandler = USBH_DS4_Data_Handler; HID_Data->init_handler = USBH_DS4_Init_Handler; HID_Data->deinit_handler = USBH_DS4_DeInit_Handler; HID_Data->custom_task = USBH_DS4_Task; dbg_printf(DBGMODE_TRACE, "Dualshock 4 Detected\r\n"); } // TODO handle others, like Xbox controllers else { // other, probably keyboard or mouse parser = malloc(sizeof(HID_Rpt_Parsing_Params_t)); dataHandler = HID_Default_Data_Handler; } uint8_t maxEP = ( (pdev->device_prop.Itf_Desc[i].bNumEndpoints <= USBH_MAX_NUM_ENDPOINTS) ? pdev->device_prop.Itf_Desc[i].bNumEndpoints : USBH_MAX_NUM_ENDPOINTS); // assign interrupt endpoints to channels, and polling parameters for the D2H endpoint // warning: assume 1 interrupt endpoint per direction for (int j = 0; j < maxEP; j++) { USBH_EpDesc_TypeDef* epDesc = &(pdev->device_prop.Ep_Desc[i][j]); if ((epDesc->bmAttributes & EP_TYPE_MSK) == USB_EP_TYPE_INTR) { if ((epDesc->bEndpointAddress & USB_EP_DIR_MSK) == USB_EP_DIR_IN) { HID_Data->io[i] = USBH_DevIO_Manager_New(pcore, pdev, epDesc, #ifdef USBH_HID_ENABLE_DYNAMIC_HC_ALLOC 1 #else 0 #endif , dataHandler, parser ); parserEp = epDesc->bEndpointAddress; } } } USBH_Status status; if (parser != 0) { USBH_HIDDesc_TypeDef hidDesc; status = USBH_Get_HID_Descriptor_Blocking (pcore, pdev, i); if (status == USBH_OK) { USBH_ParseHIDDesc(&hidDesc, pcore->host.Rx_Buffer); vcp_printf("%sI%d:C%d:HIDDesc:\r\n", USBH_Dev_DebugPrint(pdev, 0), i, USB_HID_DESC_SIZE); for (uint8_t k = 0; k < USB_HID_DESC_SIZE; k++) { vcp_printf(" 0x%02X", pcore->host.Rx_Buffer[k]); } vcp_printf("\r\n"); } else { dbg_printf(DBGMODE_ERR, "USBH_Get_HID_Descriptor_Blocking failed status: 0x%04X\r\n", status); USBH_ErrorHandle(pcore, pdev, status); continue; } uint8_t repIdList[8] = { 0, 0, 0, 0, 0, 0, 0, 0}; status = USBH_Get_HID_ReportDescriptor_Blocking(pcore , pdev, i, hidDesc.wItemLength); if (status == USBH_OK) { dbg_printf(DBGMODE_DEBUG, "USBH_Get_HID_ReportDescriptor_Blocking OK: 0x%04X\r\n", status); vcp_printf("%sI%d:C%d:ReptDesc:", USBH_Dev_DebugPrint(pdev, 0), i, hidDesc.wItemLength); for (uint8_t k = 0; k < hidDesc.wItemLength; k++) { vcp_printf(" 0x%02X", pcore->host.Rx_Buffer[k]); } vcp_printf("\r\n"); HID_Rpt_Parsing_Params_Reset(parser); HID_Rpt_Desc_Parse(pcore->host.Rx_Buffer, hidDesc.wItemLength, parser, parserEp, repIdList); //dbg_printf(DBGMODE_DEBUG, "HID_Rpt_Desc_Parse Complete \r\n"); HID_Rpt_Parsing_Params_Debug_Dump(parser); } else { dbg_printf(DBGMODE_ERR, "USBH_Get_HID_ReportDescriptor_Blocking failed status: 0x%04X\r\n", status); USBH_ErrorHandle(pcore, pdev, status); continue; } char hasSetIdle = 0; for (int ridIdx = 0; ridIdx < 8; ridIdx++) { uint8_t rid = repIdList[ridIdx]; if (rid > 0) { hasSetIdle = 1; status = USBH_Set_Idle_Blocking (pcore, pdev, i, 0, rid); if (status == USBH_OK) { dbg_printf(DBGMODE_DEBUG, "USBH_Set_Idle_Blocking[%d] OK: 0x%04X\r\n", rid, status); } else if(status == USBH_NOT_SUPPORTED || status == USBH_STALL) { dbg_printf(DBGMODE_DEBUG, "USBH_Set_Idle_Blocking[%d] NOT SUPPORTED\r\n", rid); } else { dbg_printf(DBGMODE_ERR, "USBH_Set_Idle_Blocking[%d] failed status: 0x%04X\r\n", rid, status); USBH_ErrorHandle(pcore, pdev, status); } } // end of list if (rid == 0) { break; } } if (hasSetIdle == 0) { status = USBH_Set_Idle_Blocking (pcore, pdev, i, 0, 0); if (status == USBH_OK) { dbg_printf(DBGMODE_DEBUG, "USBH_Set_Idle_Blocking[only %d] OK: 0x%04X\r\n", 0, status); } else if(status == USBH_NOT_SUPPORTED || status == USBH_STALL) { dbg_printf(DBGMODE_DEBUG, "USBH_Set_Idle_Blocking[only %d] NOT SUPPORTED\r\n", 0); } else { dbg_printf(DBGMODE_ERR, "USBH_Set_Idle_Blocking[only %d] failed status: 0x%04X\r\n", 0, status); USBH_ErrorHandle(pcore, pdev, status); } } } if (pdev->device_prop.Dev_Desc.idVendor == SONY_VID && pdev->device_prop.Dev_Desc.idProduct == DUALSHOCK4_PID) { // TODO } else { status = USBH_Set_Protocol_Blocking(pcore, pdev, i, 0); // this sets the protocol = "report" if (status == USBH_OK) { dbg_printf(DBGMODE_DEBUG, "USBH_Set_Protocol_Blocking OK: 0x%04X\r\n", status); } else { dbg_printf(DBGMODE_ERR, "USBH_Set_Protocol_Blocking failed status: 0x%04X\r\n", status); USBH_ErrorHandle(pcore, pdev, status); } } } if (isHid == 0) { // none of the interfaces are HID free(pdev->Usr_Data); pdev->Usr_Data = 0; pdev->cb = &USBH_Dev_CB_Default; // this will cause the device to not be serviced } if (HID_Data->init_handler != 0) { HID_Data->init_handler(pcore, pdev); } USBH_Dev_HID_Cnt++; }
void USBH_Dev_Hub_DataHandler(void* p_io, uint8_t* data, uint16_t len) { USBH_DevIO_t* p_in = p_io; USBH_DEV* pdev = p_in->pdev; USB_OTG_CORE_HANDLE* pcore = p_in->pcore; Hub_Data_t* Hub_Data = pdev->Usr_Data; USBH_Status errCode; int8_t alloc = 0; USBH_Dev_AllocControl(pcore, pdev); // when a device connects or disconnects from the hub, the hub will issue an interrupt-in message // iterate all the ports for (int pn = 0; pn < Hub_Data->num_ports; pn++) { int pnp1 = pn + 1; uint8_t bitIdx = pnp1 % 8; uint8_t byteIdx = pnp1 / 8; // did this port cause the event? if (data[byteIdx] & (1 << bitIdx)) { if (Hub_Data->port_busy != 0 && Hub_Data->port_busy != pnp1) { // we can only handle one new device at a time, or else they all listen to address 0 continue; } if (alloc == 0) { alloc = USBH_Dev_AllocControl(p_in->pcore, p_in->pdev); } uint16_t wPortStatus, wPortChange; errCode = USBH_Dev_Hub_GetPortStatus(pcore, pdev, pn, &wPortStatus, &wPortChange); if (errCode != USBH_OK) { dbg_printf(DBGMODE_ERR, "USBH_Dev_Hub_Handle_InterruptIn GetPortStatus (pn %d) failed (status 0x%04X) \r\n", pn, errCode); if (errCode == USBH_STALL || errCode == USBH_NOT_SUPPORTED) { errCode = USBH_ClrFeature_Blocking(pcore, pdev, 0, 0); } continue; } dbg_printf(DBGMODE_DEBUG, "Hub_Handle_InterruptIn Hub_GetPortStatus, pn: %d, s: 0x%04X, c: 0x%04X\r\n", pnp1, wPortStatus, wPortChange); if ((wPortStatus & (1 << HUBWPORTSTATUS_POWER_BIT)) == 0) { errCode = USBH_Dev_Hub_SetPortFeature(pcore, pdev, pnp1, HUBREQ_PORT_POWER); if (errCode == USBH_STALL || errCode == USBH_NOT_SUPPORTED) { errCode = USBH_ClrFeature_Blocking(pcore, pdev, 0, 0); } } if ((wPortStatus & (1 << HUBWPORTCHANGE_ENABLED_BIT)) == 0) { errCode = USBH_Dev_Hub_SetPortFeature(pcore, pdev, pn, HUBREQ_PORT_ENABLE); if (errCode == USBH_STALL || errCode == USBH_NOT_SUPPORTED) { errCode = USBH_ClrFeature_Blocking(pcore, pdev, 0, 0); } } if ((wPortChange & (1 << HUBWPORTCHANGE_RESET_BIT)) != 0) { errCode = USBH_Dev_Hub_ClearPortFeature(pcore, pdev, pn, HUBREQ_C_PORT_RESET, 0); if (errCode == USBH_STALL || errCode == USBH_NOT_SUPPORTED) { errCode = USBH_ClrFeature_Blocking(pcore, pdev, 0, 0); } } if ((wPortChange & (1 << HUBWPORTCHANGE_CONNSTAT_BIT)) != 0) { errCode = USBH_Dev_Hub_ClearPortFeature(pcore, pdev, pn, HUBREQ_C_PORT_CONNECTION, 0); if (errCode == USBH_STALL || errCode == USBH_NOT_SUPPORTED) { errCode = USBH_ClrFeature_Blocking(pcore, pdev, 0, 0); } } if ((wPortChange & (1 << HUBWPORTCHANGE_ENABLED_BIT)) != 0) { errCode = USBH_Dev_Hub_ClearPortFeature(pcore, pdev, pn, HUBREQ_C_PORT_ENABLE, 0); if (errCode == USBH_STALL || errCode == USBH_NOT_SUPPORTED) { errCode = USBH_ClrFeature_Blocking(pcore, pdev, 0, 0); } } if ((wPortStatus & (1 << HUBWPORTSTATUS_RESET_BIT)) != 0) { if (Hub_Data->children[pn] != 0 && Hub_Data->children[pn]->gState == HOST_IDLE) { Hub_Data->children[pn]->gState = HOST_DEV_RESET_PENDING; } } else { if (((wPortStatus & (1 << HUBWPORTSTATUS_CURCONN_BIT)) == 0 || (wPortStatus & (1 << HUBWPORTSTATUS_ENABLED_BIT)) == 0) && Hub_Data->children[pn] != 0 && Hub_Data->children[pn]->cb != 0 && Hub_Data->children[pn]->gState != HOST_IDLE && Hub_Data->children[pn]->gState != HOST_DEV_RESET_PENDING) { dbg_printf(DBGMODE_TRACE, "Hub %s disconnected device (pn %d)\r\n", USBH_Dev_DebugPrint(pdev, 0), pnp1); vcp_printf("Hub %s Lost Device on Port %d\r\n", USBH_Dev_DebugPrint(pdev, 0), pnp1); ((USBH_Device_cb_TypeDef*)Hub_Data->children[pn]->cb)->DeviceDisconnected(pcore, pdev); USBH_DeInit(pcore, Hub_Data->children[pn]); // frees channels ((USBH_Device_cb_TypeDef*)Hub_Data->children[pn]->cb)->DeInit(pcore, Hub_Data->children[pn]); ((USBH_Device_cb_TypeDef*)Hub_Data->children[pn]->cb)->DeInitDev(pcore, Hub_Data->children[pn]); // this is the one that frees memory free(Hub_Data->children[pn]); Hub_Data->children[pn] = 0; } else if ((wPortStatus & (1 << HUBWPORTSTATUS_CURCONN_BIT)) != 0) { if ((wPortStatus & (1 << HUBWPORTSTATUS_RESET_BIT)) == 0 && (wPortStatus & (1 << HUBWPORTSTATUS_ENABLED_BIT)) != 0 && Hub_Data->children[pn] != 0 && Hub_Data->children[pn]->cb != 0 && (Hub_Data->children[pn]->gState == HOST_DEV_RESET_PENDING || Hub_Data->children[pn]->gState == HOST_IDLE)) { if (((USBH_Device_cb_TypeDef*)Hub_Data->children[pn]->cb)->ResetDevice != 0) ((USBH_Device_cb_TypeDef*)Hub_Data->children[pn]->cb)->ResetDevice(pcore, pdev); Hub_Data->children[pn]->device_prop.speed = (wPortStatus & (1 << 9)) ? HPRT0_PRTSPD_LOW_SPEED : HPRT0_PRTSPD_FULL_SPEED; // if bit 9 is 1, then it is low speed (0x02), or else it is full speed (0x01) if (((USBH_Device_cb_TypeDef*)Hub_Data->children[pn]->cb)->DeviceSpeedDetected != 0) ((USBH_Device_cb_TypeDef*)Hub_Data->children[pn]->cb)->DeviceSpeedDetected(pcore, Hub_Data->children[pn], Hub_Data->children[pn]->device_prop.speed); Hub_Data->children[pn]->gState = HOST_DEV_DELAY; USBH_Dev_Reset_Timer = systick_1ms_cnt; Hub_Data->children[pn]->device_prop.address = 0; // new attached devices to a hub is always address 0 Hub_Data->port_busy = pnp1; Hub_Data->intInEpIo->enabled = 0; // we are allowed to pass this on to the upper state machine now, it will seem like it was attached normally dbg_printf(DBGMODE_TRACE, "Hub passed new device (pn %d) to upper state machine \r\n", pnp1); vcp_printf("Hub %s New Device on Port %d\r\n", USBH_Dev_DebugPrint(pdev, 0), pnp1); } else if (Hub_Data->children[pn] == 0) { errCode = USBH_Dev_Hub_SetPortFeature(pcore, pdev, pn, HUBREQ_PORT_RESET); Hub_Data->children[pn] = calloc(1, sizeof(USBH_DEV)); Hub_Data->children[pn]->Parent = pdev; Hub_Data->children[pn]->gState = HOST_IDLE; Hub_Data->children[pn]->gStateBkp = HOST_IDLE; Hub_Data->children[pn]->EnumState = ENUM_IDLE; Hub_Data->children[pn]->RequestState = CMD_SEND; Hub_Data->children[pn]->device_prop.address = USBH_DEVICE_ADDRESS_DEFAULT; // this better be 0 Hub_Data->children[pn]->device_prop.speed = HPRT0_PRTSPD_LOW_SPEED; Hub_Data->children[pn]->port_num = pnp1; Hub_Data->children[pn]->Control.hc_num_in = -1; Hub_Data->children[pn]->Control.hc_num_out = -1; Hub_Data->children[pn]->Control.hc_in_tgl_in = -1; Hub_Data->children[pn]->Control.hc_in_tgl_out = -1; Hub_Data->children[pn]->Control.hc_out_tgl_in = -1; Hub_Data->children[pn]->Control.hc_out_tgl_out = -1; Hub_Data->children[pn]->Control.state = CTRL_IDLE; Hub_Data->children[pn]->Control.ep0size = USB_OTG_MAX_EP0_SIZE; Hub_Data->children[pn]->cb = (void*)&USBH_Dev_CB_Default; Hub_Data->port_busy = pnp1; dbg_printf(DBGMODE_TRACE, "Hub %s created new child (pn %d) \r\n", USBH_Dev_DebugPrint(pdev, 0), pnp1); } } } } } if (alloc != 0) { USBH_Dev_FreeControl(p_in->pcore, p_in->pdev); } }
void USBH_Dev_Hub_EnumerationDone(USB_OTG_CORE_HANDLE *pcore , USBH_DEV *pdev) { dbg_printf(DBGMODE_TRACE, "Hub_EnumerationDone \r\n"); if (pdev->Usr_Data != 0) { free(pdev->Usr_Data); pdev->Usr_Data = 0; } pdev->Usr_Data = calloc(1, sizeof(Hub_Data_t)); Hub_Data_t* Hub_Data = pdev->Usr_Data; uint8_t maxEP = ( (pdev->device_prop.Itf_Desc[0].bNumEndpoints <= USBH_MAX_NUM_ENDPOINTS) ? pdev->device_prop.Itf_Desc[0].bNumEndpoints : USBH_MAX_NUM_ENDPOINTS); for (uint8_t num=0; num < maxEP; num++) { USBH_EpDesc_TypeDef* epDesc = &(pdev->device_prop.Ep_Desc[0][num]); if ((epDesc->bmAttributes & EP_TYPE_MSK) == USB_EP_TYPE_INTR && (epDesc->bEndpointAddress & USB_EP_DIR_MSK) == USB_EP_DIR_IN) { //if (epDesc->bInterval > 50) epDesc->bInterval = 50; Hub_Data->intInEpIo = USBH_DevIO_Manager_New(pcore, pdev, epDesc, #ifdef HUB_ENABLE_DYNAMIC_HC_ALLOC 1 #else 0 #endif , USBH_Dev_Hub_DataHandler, 0); Hub_Data->intInEpIo->force_poll_interval = 1; Hub_Data->intInEpIo->timeout = 10; } } if (Hub_Data->intInEpIo == 0) { dbg_printf(DBGMODE_ERR, "Hub %s has no interrupt-in endpoints!\r\n", USBH_Dev_DebugPrint(pdev, 0)); } // the old example HID code used a state machine to perform the sequence of requests, but here we will just do everything in a sequence // get the hub descriptor so we know the number of ports available USBH_Status status = USBH_GetDescriptor_Blocking(pcore, pdev, USB_REQ_RECIPIENT_DEVICE | USB_REQ_TYPE_CLASS, USB_DESC_HUB, 0, pcore->host.Rx_Buffer, USB_HUB_DESC_SIZE ); if (status != USBH_OK) { dbg_printf(DBGMODE_ERR, "Hub_EnumerationDone GetDescriptor failed, status 0x%02X \r\n", status); USBH_ErrorHandle(pcore, pdev, status); return; } Hub_Data->num_ports = pcore->host.Rx_Buffer[2]; //dbg_printf(DBGMODE_DEBUG, "\tbDescriptorType = 0x%02X\r\n", pcore->host.Rx_Buffer[1]); dbg_printf(DBGMODE_DEBUG, "\tnum_ports = %d\r\n", Hub_Data->num_ports); vcp_printf("Hub V%04XP%04XA%d Enumerated, %d ports\r\n", pdev->device_prop.Dev_Desc.idVendor, pdev->device_prop.Dev_Desc.idProduct, pdev->device_prop.address, Hub_Data->num_ports); // allocate memory for the children devices if (Hub_Data->children != 0) { free(Hub_Data->children); Hub_Data->children = 0; } Hub_Data->children = calloc(Hub_Data->num_ports, sizeof(USBH_DEV*)); // iterate all the ports for (int pn = 0; pn < Hub_Data->num_ports; pn++) { Hub_Data->children[pn] = 0; // reset uint16_t wps, wpc; status = USBH_Dev_Hub_GetPortStatus(pcore, pdev, pn, &wps, &wpc); if (status != USBH_OK) { dbg_printf(DBGMODE_ERR, "Hub_EnumerationDone GetPortStatus (pn %d) failed, status 0x%02X \r\n", pn + 1, status); USBH_ErrorHandle(pcore, pdev, status); continue; } status = USBH_Dev_Hub_SetPortFeature(pcore, pdev, pn, HUBREQ_PORT_POWER); if (status != USBH_OK) { dbg_printf(DBGMODE_ERR, "Hub_EnumerationDone SetPortFeature (pn %d) failed, status 0x%02X \r\n", pn + 1, status); USBH_ErrorHandle(pcore, pdev, status); continue; } } // wait for power ready char pwrRdy; volatile int pwrTries = 700 / Hub_Data->num_ports; do { pwrRdy = 1; for (int pn = 0; pn < Hub_Data->num_ports; pn++) { uint16_t wps, wpc; status = USBH_Dev_Hub_GetPortStatus(pcore, pdev, pn, &wps, &wpc); if (status != USBH_OK) { dbg_printf(DBGMODE_ERR, "Hub_EnumerationDone GetPortStatus (pn %d) failed, status 0x%02X \r\n", pn + 1, status); //USBH_ErrorHandle(pcore, pdev, status); continue; } if (wps & (1 << HUBWPORTSTATUS_POWER_BIT) == 0) { pwrRdy = 0; } } } while (pwrRdy == 0 && pwrTries--); if (pwrRdy != 0) { dbg_printf(DBGMODE_TRACE, "Hub_EnumerationDone all port power ready \r\n"); } else { dbg_printf(DBGMODE_ERR, "Hub_EnumerationDone error, power not ready \r\n"); } vcp_printf("%s:Hub Ready, Ports: %d \r\n", USBH_Dev_DebugPrint(pdev, 0), Hub_Data->num_ports); }