diff options
author | whu <whu@FreeBSD.org> | 2015-06-24 06:01:29 +0000 |
---|---|---|
committer | whu <whu@FreeBSD.org> | 2015-06-24 06:01:29 +0000 |
commit | 71f5e477907b183fef33897b05d98b3a721f7670 (patch) | |
tree | 180aec2b405866aefcf04b49bf9360abbdca513e /sys/dev/hyperv/netvsc/hv_rndis_filter.c | |
parent | cce1064454bef10bbe1d0b8a157d4cdee65e95b7 (diff) | |
download | FreeBSD-src-71f5e477907b183fef33897b05d98b3a721f7670.zip FreeBSD-src-71f5e477907b183fef33897b05d98b3a721f7670.tar.gz |
TSO and checksum offloading support for Netvsc driver on Hyper-V.
Submitted by: whu
Reviewed by: royger
Approved by: royger
MFC after: 1 week
Relnotes: yes
Sponsored by: Microsoft OSTC
Differential Revision: https://reviews.freebsd.org/D2517
Diffstat (limited to 'sys/dev/hyperv/netvsc/hv_rndis_filter.c')
-rw-r--r-- | sys/dev/hyperv/netvsc/hv_rndis_filter.c | 369 |
1 files changed, 201 insertions, 168 deletions
diff --git a/sys/dev/hyperv/netvsc/hv_rndis_filter.c b/sys/dev/hyperv/netvsc/hv_rndis_filter.c index 4197c16..691badd 100644 --- a/sys/dev/hyperv/netvsc/hv_rndis_filter.c +++ b/sys/dev/hyperv/netvsc/hv_rndis_filter.c @@ -67,9 +67,64 @@ static int hv_rf_set_packet_filter(rndis_device *device, uint32_t new_filter); static int hv_rf_init_device(rndis_device *device); static int hv_rf_open_device(rndis_device *device); static int hv_rf_close_device(rndis_device *device); -static void hv_rf_on_send_completion(void *context); static void hv_rf_on_send_request_completion(void *context); static void hv_rf_on_send_request_halt_completion(void *context); +int +hv_rf_send_offload_request(struct hv_device *device, + rndis_offload_params *offloads); +/* + * Set the Per-Packet-Info with the specified type + */ +void * +hv_set_rppi_data(rndis_msg *rndis_mesg, uint32_t rppi_size, + int pkt_type) +{ + rndis_packet *rndis_pkt; + rndis_per_packet_info *rppi; + + rndis_pkt = &rndis_mesg->msg.packet; + rndis_pkt->data_offset += rppi_size; + + rppi = (rndis_per_packet_info *)((char *)rndis_pkt + + rndis_pkt->per_pkt_info_offset + rndis_pkt->per_pkt_info_length); + + rppi->size = rppi_size; + rppi->type = pkt_type; + rppi->per_packet_info_offset = sizeof(rndis_per_packet_info); + + rndis_pkt->per_pkt_info_length += rppi_size; + + return (rppi); +} + +/* + * Get the Per-Packet-Info with the specified type + * return NULL if not found. + */ +void * +hv_get_ppi_data(rndis_packet *rpkt, uint32_t type) +{ + rndis_per_packet_info *ppi; + int len; + + if (rpkt->per_pkt_info_offset == 0) + return (NULL); + + ppi = (rndis_per_packet_info *)((unsigned long)rpkt + + rpkt->per_pkt_info_offset); + len = rpkt->per_pkt_info_length; + + while (len > 0) { + if (ppi->type == type) + return (void *)((unsigned long)ppi + + ppi->per_packet_info_offset); + + len -= ppi->size; + ppi = (rndis_per_packet_info *)((unsigned long)ppi + ppi->size); + } + + return (NULL); +} /* @@ -80,7 +135,7 @@ hv_get_rndis_device(void) { rndis_device *device; - device = malloc(sizeof(rndis_device), M_DEVBUF, M_NOWAIT | M_ZERO); + device = malloc(sizeof(rndis_device), M_NETVSC, M_NOWAIT | M_ZERO); if (device == NULL) { return (NULL); } @@ -102,7 +157,7 @@ static inline void hv_put_rndis_device(rndis_device *device) { mtx_destroy(&device->req_lock); - free(device, M_DEVBUF); + free(device, M_NETVSC); } /* @@ -116,7 +171,7 @@ hv_rndis_request(rndis_device *device, uint32_t message_type, rndis_msg *rndis_mesg; rndis_set_request *set; - request = malloc(sizeof(rndis_request), M_DEVBUF, M_NOWAIT | M_ZERO); + request = malloc(sizeof(rndis_request), M_NETVSC, M_NOWAIT | M_ZERO); if (request == NULL) { return (NULL); } @@ -161,7 +216,7 @@ hv_put_rndis_request(rndis_device *device, rndis_request *request) mtx_unlock_spin(&device->req_lock); sema_destroy(&request->wait_sema); - free(request, M_DEVBUF); + free(request, M_NETVSC); } /* @@ -169,7 +224,7 @@ hv_put_rndis_request(rndis_device *device, rndis_request *request) */ static int hv_rf_send_request(rndis_device *device, rndis_request *request, - uint32_t message_type) + uint32_t message_type) { int ret; netvsc_packet *packet; @@ -196,6 +251,9 @@ hv_rf_send_request(rndis_device *device, rndis_request *request, hv_rf_on_send_request_halt_completion; } packet->compl.send.send_completion_tid = (unsigned long)device; + packet->send_buf_section_idx = + NVSP_1_CHIMNEY_SEND_INVALID_SECTION_INDEX; + packet->send_buf_section_size = 0; ret = hv_nv_on_send(device->net_dev->dev, packet); @@ -248,6 +306,84 @@ hv_rf_receive_response(rndis_device *device, rndis_msg *response) } } +int +hv_rf_send_offload_request(struct hv_device *device, + rndis_offload_params *offloads) +{ + rndis_request *request; + rndis_set_request *set; + rndis_offload_params *offload_req; + rndis_set_complete *set_complete; + rndis_device *rndis_dev; + hn_softc_t *sc = device_get_softc(device->device); + device_t dev = device->device; + netvsc_dev *net_dev = sc->net_dev; + uint32_t vsp_version = net_dev->nvsp_version; + uint32_t extlen = sizeof(rndis_offload_params); + int ret; + + if (vsp_version <= NVSP_PROTOCOL_VERSION_4) { + extlen = VERSION_4_OFFLOAD_SIZE; + /* On NVSP_PROTOCOL_VERSION_4 and below, we do not support + * UDP checksum offload. + */ + offloads->udp_ipv4_csum = 0; + offloads->udp_ipv6_csum = 0; + } + + rndis_dev = net_dev->extension; + + request = hv_rndis_request(rndis_dev, REMOTE_NDIS_SET_MSG, + RNDIS_MESSAGE_SIZE(rndis_set_request) + extlen); + if (!request) + return (ENOMEM); + + set = &request->request_msg.msg.set_request; + set->oid = RNDIS_OID_TCP_OFFLOAD_PARAMETERS; + set->info_buffer_length = extlen; + set->info_buffer_offset = sizeof(rndis_set_request); + set->device_vc_handle = 0; + + offload_req = (rndis_offload_params *)((unsigned long)set + + set->info_buffer_offset); + *offload_req = *offloads; + offload_req->header.type = RNDIS_OBJECT_TYPE_DEFAULT; + offload_req->header.revision = RNDIS_OFFLOAD_PARAMETERS_REVISION_3; + offload_req->header.size = extlen; + + ret = hv_rf_send_request(rndis_dev, request, REMOTE_NDIS_SET_MSG); + if (ret != 0) { + device_printf(dev, "hv send offload request failed, ret=%d!\n", + ret); + goto cleanup; + } + + ret = sema_timedwait(&request->wait_sema, 500); + if (ret != 0) { + device_printf(dev, "hv send offload request timeout\n"); + goto cleanup; + } + + set_complete = &request->response_msg.msg.set_complete; + if (set_complete->status == RNDIS_STATUS_SUCCESS) { + device_printf(dev, "hv send offload request succeeded\n"); + ret = 0; + } else { + if (set_complete->status == STATUS_NOT_SUPPORTED) { + device_printf(dev, "HV Not support offload\n"); + ret = 0; + } else { + ret = set_complete->status; + } + } + +cleanup: + if (request) + hv_put_rndis_request(rndis_dev, request); + + return (ret); +} + /* * RNDIS filter receive indicate status */ @@ -256,12 +392,18 @@ hv_rf_receive_indicate_status(rndis_device *device, rndis_msg *response) { rndis_indicate_status *indicate = &response->msg.indicate_status; - if (indicate->status == RNDIS_STATUS_MEDIA_CONNECT) { + switch(indicate->status) { + case RNDIS_STATUS_MEDIA_CONNECT: netvsc_linkstatus_callback(device->net_dev->dev, 1); - } else if (indicate->status == RNDIS_STATUS_MEDIA_DISCONNECT) { + break; + case RNDIS_STATUS_MEDIA_DISCONNECT: netvsc_linkstatus_callback(device->net_dev->dev, 0); - } else { + break; + default: /* TODO: */ + device_printf(device->net_dev->dev->device, + "unknown status %d received\n", indicate->status); + break; } } @@ -272,9 +414,10 @@ static void hv_rf_receive_data(rndis_device *device, rndis_msg *message, netvsc_packet *pkt) { rndis_packet *rndis_pkt; - rndis_per_packet_info *rppi; - ndis_8021q_info *rppi_vlan_info; + ndis_8021q_info *rppi_vlan_info; uint32_t data_offset; + rndis_tcp_ip_csum_info *csum_info = NULL; + device_t dev = device->net_dev->dev->device; rndis_pkt = &message->msg.packet; @@ -286,88 +429,57 @@ hv_rf_receive_data(rndis_device *device, rndis_msg *message, netvsc_packet *pkt) /* Remove rndis header, then pass data packet up the stack */ data_offset = RNDIS_HEADER_SIZE + rndis_pkt->data_offset; - /* L2 frame length, with L2 header, not including CRC */ - pkt->tot_data_buf_len = rndis_pkt->data_length; - pkt->page_buffers[0].offset += data_offset; - /* Buffer length now L2 frame length plus trailing junk */ - pkt->page_buffers[0].length -= data_offset; - - pkt->is_data_pkt = TRUE; + pkt->tot_data_buf_len -= data_offset; + if (pkt->tot_data_buf_len < rndis_pkt->data_length) { + pkt->status = nvsp_status_failure; + device_printf(dev, + "total length %u is less than data length %u\n", + pkt->tot_data_buf_len, rndis_pkt->data_length); + return; + } - pkt->vlan_tci = 0; + pkt->tot_data_buf_len = rndis_pkt->data_length; + pkt->data = (void *)((unsigned long)pkt->data + data_offset); - /* - * Read the VLAN ID if supplied by the Hyper-V infrastructure. - * Let higher-level driver code decide if it wants to use it. - * Ignore CFI, priority for now as FreeBSD does not support these. - */ - if (rndis_pkt->per_pkt_info_offset != 0) { - /* rppi struct exists; compute its address */ - rppi = (rndis_per_packet_info *)((uint8_t *)rndis_pkt + - rndis_pkt->per_pkt_info_offset); - /* if VLAN ppi struct, get the VLAN ID */ - if (rppi->type == ieee_8021q_info) { - rppi_vlan_info = (ndis_8021q_info *)((uint8_t *)rppi - + rppi->per_packet_info_offset); - pkt->vlan_tci = rppi_vlan_info->u1.s1.vlan_id; - } + rppi_vlan_info = hv_get_ppi_data(rndis_pkt, ieee_8021q_info); + if (rppi_vlan_info) { + pkt->vlan_tci = rppi_vlan_info->u1.s1.vlan_id; + } else { + pkt->vlan_tci = 0; } - netvsc_recv(device->net_dev->dev, pkt); + csum_info = hv_get_ppi_data(rndis_pkt, tcpip_chksum_info); + netvsc_recv(device->net_dev->dev, pkt, csum_info); } /* * RNDIS filter on receive */ int -hv_rf_on_receive(struct hv_device *device, netvsc_packet *pkt) +hv_rf_on_receive(netvsc_dev *net_dev, struct hv_device *device, netvsc_packet *pkt) { - hn_softc_t *sc = device_get_softc(device->device); - netvsc_dev *net_dev = sc->net_dev; rndis_device *rndis_dev; - rndis_msg rndis_mesg; rndis_msg *rndis_hdr; /* Make sure the rndis device state is initialized */ - if (net_dev->extension == NULL) + if (net_dev->extension == NULL) { + pkt->status = nvsp_status_failure; return (ENODEV); + } rndis_dev = (rndis_device *)net_dev->extension; - if (rndis_dev->state == RNDIS_DEV_UNINITIALIZED) + if (rndis_dev->state == RNDIS_DEV_UNINITIALIZED) { + pkt->status = nvsp_status_failure; return (EINVAL); - - /* Shift virtual page number to form virtual page address */ - rndis_hdr = (rndis_msg *)(uintptr_t)(pkt->page_buffers[0].pfn << PAGE_SHIFT); - - rndis_hdr = (void *)((unsigned long)rndis_hdr - + pkt->page_buffers[0].offset); - - /* - * Make sure we got a valid rndis message - * Fixme: There seems to be a bug in set completion msg where - * its msg_len is 16 bytes but the byte_count field in the - * xfer page range shows 52 bytes - */ -#if 0 - if (pkt->tot_data_buf_len != rndis_hdr->msg_len) { - DPRINT_ERR(NETVSC, "invalid rndis message? (expected %u " - "bytes got %u)... dropping this message!", - rndis_hdr->msg_len, pkt->tot_data_buf_len); - DPRINT_EXIT(NETVSC); - - return (-1); } -#endif - memcpy(&rndis_mesg, rndis_hdr, - (rndis_hdr->msg_len > sizeof(rndis_msg)) ? - sizeof(rndis_msg) : rndis_hdr->msg_len); + rndis_hdr = pkt->data; - switch (rndis_mesg.ndis_msg_type) { + switch (rndis_hdr->ndis_msg_type) { /* data message */ case REMOTE_NDIS_PACKET_MSG: - hv_rf_receive_data(rndis_dev, &rndis_mesg, pkt); + hv_rf_receive_data(rndis_dev, rndis_hdr, pkt); break; /* completion messages */ case REMOTE_NDIS_INITIALIZE_CMPLT: @@ -375,15 +487,15 @@ hv_rf_on_receive(struct hv_device *device, netvsc_packet *pkt) case REMOTE_NDIS_SET_CMPLT: case REMOTE_NDIS_RESET_CMPLT: case REMOTE_NDIS_KEEPALIVE_CMPLT: - hv_rf_receive_response(rndis_dev, &rndis_mesg); + hv_rf_receive_response(rndis_dev, rndis_hdr); break; /* notification message */ case REMOTE_NDIS_INDICATE_STATUS_MSG: - hv_rf_receive_indicate_status(rndis_dev, &rndis_mesg); + hv_rf_receive_indicate_status(rndis_dev, rndis_hdr); break; default: printf("hv_rf_on_receive(): Unknown msg_type 0x%x\n", - rndis_mesg.ndis_msg_type); + rndis_hdr->ndis_msg_type); break; } @@ -711,7 +823,9 @@ hv_rf_on_device_add(struct hv_device *device, void *additl_info) int ret; netvsc_dev *net_dev; rndis_device *rndis_dev; + rndis_offload_params offloads; netvsc_device_info *dev_info = (netvsc_device_info *)additl_info; + device_t dev = device->device; rndis_dev = hv_get_rndis_device(); if (rndis_dev == NULL) { @@ -752,6 +866,22 @@ hv_rf_on_device_add(struct hv_device *device, void *additl_info) if (ret != 0) { /* TODO: shut down rndis device and the channel */ } + + /* config csum offload and send request to host */ + memset(&offloads, 0, sizeof(offloads)); + offloads.ipv4_csum = RNDIS_OFFLOAD_PARAMETERS_TX_RX_ENABLED; + offloads.tcp_ipv4_csum = RNDIS_OFFLOAD_PARAMETERS_TX_RX_ENABLED; + offloads.udp_ipv4_csum = RNDIS_OFFLOAD_PARAMETERS_TX_RX_ENABLED; + offloads.tcp_ipv6_csum = RNDIS_OFFLOAD_PARAMETERS_TX_RX_ENABLED; + offloads.udp_ipv6_csum = RNDIS_OFFLOAD_PARAMETERS_TX_RX_ENABLED; + offloads.lso_v2_ipv4 = RNDIS_OFFLOAD_PARAMETERS_LSOV2_ENABLED; + + ret = hv_rf_send_offload_request(device, &offloads); + if (ret != 0) { + /* TODO: shut down rndis device and the channel */ + device_printf(dev, + "hv_rf_send_offload_request failed, ret=%d\n", ret); + } memcpy(dev_info->mac_addr, rndis_dev->hw_mac_addr, HW_MACADDR_LEN); @@ -810,103 +940,6 @@ hv_rf_on_close(struct hv_device *device) } /* - * RNDIS filter on send - */ -int -hv_rf_on_send(struct hv_device *device, netvsc_packet *pkt) -{ - rndis_filter_packet *filter_pkt; - rndis_msg *rndis_mesg; - rndis_packet *rndis_pkt; - rndis_per_packet_info *rppi; - ndis_8021q_info *rppi_vlan_info; - uint32_t rndis_msg_size; - int ret = 0; - - /* Add the rndis header */ - filter_pkt = (rndis_filter_packet *)pkt->extension; - - memset(filter_pkt, 0, sizeof(rndis_filter_packet)); - - rndis_mesg = &filter_pkt->message; - rndis_msg_size = RNDIS_MESSAGE_SIZE(rndis_packet); - - if (pkt->vlan_tci != 0) { - rndis_msg_size += sizeof(rndis_per_packet_info) + - sizeof(ndis_8021q_info); - } - - rndis_mesg->ndis_msg_type = REMOTE_NDIS_PACKET_MSG; - rndis_mesg->msg_len = pkt->tot_data_buf_len + rndis_msg_size; - - rndis_pkt = &rndis_mesg->msg.packet; - rndis_pkt->data_offset = sizeof(rndis_packet); - rndis_pkt->data_length = pkt->tot_data_buf_len; - - pkt->is_data_pkt = TRUE; - pkt->page_buffers[0].pfn = hv_get_phys_addr(rndis_mesg) >> PAGE_SHIFT; - pkt->page_buffers[0].offset = - (unsigned long)rndis_mesg & (PAGE_SIZE - 1); - pkt->page_buffers[0].length = rndis_msg_size; - - /* Save the packet context */ - filter_pkt->completion_context = - pkt->compl.send.send_completion_context; - - /* Use ours */ - pkt->compl.send.on_send_completion = hv_rf_on_send_completion; - pkt->compl.send.send_completion_context = filter_pkt; - - /* - * If there is a VLAN tag, we need to set up some additional - * fields so the Hyper-V infrastructure will stuff the VLAN tag - * into the frame. - */ - if (pkt->vlan_tci != 0) { - /* Move data offset past end of rppi + VLAN structs */ - rndis_pkt->data_offset += sizeof(rndis_per_packet_info) + - sizeof(ndis_8021q_info); - - /* must be set when we have rppi, VLAN info */ - rndis_pkt->per_pkt_info_offset = sizeof(rndis_packet); - rndis_pkt->per_pkt_info_length = sizeof(rndis_per_packet_info) + - sizeof(ndis_8021q_info); - - /* rppi immediately follows rndis_pkt */ - rppi = (rndis_per_packet_info *)(rndis_pkt + 1); - rppi->size = sizeof(rndis_per_packet_info) + - sizeof(ndis_8021q_info); - rppi->type = ieee_8021q_info; - rppi->per_packet_info_offset = sizeof(rndis_per_packet_info); - - /* VLAN info immediately follows rppi struct */ - rppi_vlan_info = (ndis_8021q_info *)(rppi + 1); - /* FreeBSD does not support CFI or priority */ - rppi_vlan_info->u1.s1.vlan_id = pkt->vlan_tci & 0xfff; - } - - /* - * Invoke netvsc send. If return status is bad, the caller now - * resets the context pointers before retrying. - */ - ret = hv_nv_on_send(device, pkt); - - return (ret); -} - -/* - * RNDIS filter on send completion callback - */ -static void -hv_rf_on_send_completion(void *context) -{ - rndis_filter_packet *filter_pkt = (rndis_filter_packet *)context; - - /* Pass it back to the original handler */ - netvsc_xmit_completion(filter_pkt->completion_context); -} - -/* * RNDIS filter on send request completion callback */ static void |