| /* |
| * Copyright (c) 2009, Microsoft Corporation. |
| * |
| * This program is free software; you can redistribute it and/or modify it |
| * under the terms and conditions of the GNU General Public License, |
| * version 2, as published by the Free Software Foundation. |
| * |
| * This program is distributed in the hope it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for |
| * more details. |
| * |
| * You should have received a copy of the GNU General Public License along with |
| * this program; if not, write to the Free Software Foundation, Inc., 59 Temple |
| * Place - Suite 330, Boston, MA 02111-1307 USA. |
| * |
| * Authors: |
| * Haiyang Zhang <haiyangz@microsoft.com> |
| * Hank Janssen <hjanssen@microsoft.com> |
| */ |
| #include <linux/kernel.h> |
| #include <linux/sched.h> |
| #include <linux/wait.h> |
| #include <linux/highmem.h> |
| #include <linux/slab.h> |
| #include <linux/io.h> |
| #include <linux/if_ether.h> |
| #include <linux/netdevice.h> |
| |
| #include "hyperv_net.h" |
| |
| |
| enum rndis_device_state { |
| RNDIS_DEV_UNINITIALIZED = 0, |
| RNDIS_DEV_INITIALIZING, |
| RNDIS_DEV_INITIALIZED, |
| RNDIS_DEV_DATAINITIALIZED, |
| }; |
| |
| struct rndis_device { |
| struct netvsc_device *net_dev; |
| |
| enum rndis_device_state state; |
| bool link_state; |
| atomic_t new_req_id; |
| |
| spinlock_t request_lock; |
| struct list_head req_list; |
| |
| unsigned char hw_mac_adr[ETH_ALEN]; |
| }; |
| |
| struct rndis_request { |
| struct list_head list_ent; |
| struct completion wait_event; |
| |
| /* |
| * FIXME: We assumed a fixed size response here. If we do ever need to |
| * handle a bigger response, we can either define a max response |
| * message or add a response buffer variable above this field |
| */ |
| struct rndis_message response_msg; |
| |
| /* Simplify allocation by having a netvsc packet inline */ |
| struct hv_netvsc_packet pkt; |
| struct hv_page_buffer buf; |
| /* FIXME: We assumed a fixed size request here. */ |
| struct rndis_message request_msg; |
| }; |
| |
| static void rndis_filter_send_completion(void *ctx); |
| |
| static void rndis_filter_send_request_completion(void *ctx); |
| |
| |
| |
| static struct rndis_device *get_rndis_device(void) |
| { |
| struct rndis_device *device; |
| |
| device = kzalloc(sizeof(struct rndis_device), GFP_KERNEL); |
| if (!device) |
| return NULL; |
| |
| spin_lock_init(&device->request_lock); |
| |
| INIT_LIST_HEAD(&device->req_list); |
| |
| device->state = RNDIS_DEV_UNINITIALIZED; |
| |
| return device; |
| } |
| |
| static struct rndis_request *get_rndis_request(struct rndis_device *dev, |
| u32 msg_type, |
| u32 msg_len) |
| { |
| struct rndis_request *request; |
| struct rndis_message *rndis_msg; |
| struct rndis_set_request *set; |
| unsigned long flags; |
| |
| request = kzalloc(sizeof(struct rndis_request), GFP_KERNEL); |
| if (!request) |
| return NULL; |
| |
| init_completion(&request->wait_event); |
| |
| rndis_msg = &request->request_msg; |
| rndis_msg->ndis_msg_type = msg_type; |
| rndis_msg->msg_len = msg_len; |
| |
| /* |
| * Set the request id. This field is always after the rndis header for |
| * request/response packet types so we just used the SetRequest as a |
| * template |
| */ |
| set = &rndis_msg->msg.set_req; |
| set->req_id = atomic_inc_return(&dev->new_req_id); |
| |
| /* Add to the request list */ |
| spin_lock_irqsave(&dev->request_lock, flags); |
| list_add_tail(&request->list_ent, &dev->req_list); |
| spin_unlock_irqrestore(&dev->request_lock, flags); |
| |
| return request; |
| } |
| |
| static void put_rndis_request(struct rndis_device *dev, |
| struct rndis_request *req) |
| { |
| unsigned long flags; |
| |
| spin_lock_irqsave(&dev->request_lock, flags); |
| list_del(&req->list_ent); |
| spin_unlock_irqrestore(&dev->request_lock, flags); |
| |
| kfree(req); |
| } |
| |
| static void dump_rndis_message(struct hv_device *hv_dev, |
| struct rndis_message *rndis_msg) |
| { |
| struct net_device *netdev; |
| struct netvsc_device *net_device; |
| |
| net_device = hv_get_drvdata(hv_dev); |
| netdev = net_device->ndev; |
| |
| switch (rndis_msg->ndis_msg_type) { |
| case REMOTE_NDIS_PACKET_MSG: |
| netdev_dbg(netdev, "REMOTE_NDIS_PACKET_MSG (len %u, " |
| "data offset %u data len %u, # oob %u, " |
| "oob offset %u, oob len %u, pkt offset %u, " |
| "pkt len %u\n", |
| rndis_msg->msg_len, |
| rndis_msg->msg.pkt.data_offset, |
| rndis_msg->msg.pkt.data_len, |
| rndis_msg->msg.pkt.num_oob_data_elements, |
| rndis_msg->msg.pkt.oob_data_offset, |
| rndis_msg->msg.pkt.oob_data_len, |
| rndis_msg->msg.pkt.per_pkt_info_offset, |
| rndis_msg->msg.pkt.per_pkt_info_len); |
| break; |
| |
| case REMOTE_NDIS_INITIALIZE_CMPLT: |
| netdev_dbg(netdev, "REMOTE_NDIS_INITIALIZE_CMPLT " |
| "(len %u, id 0x%x, status 0x%x, major %d, minor %d, " |
| "device flags %d, max xfer size 0x%x, max pkts %u, " |
| "pkt aligned %u)\n", |
| rndis_msg->msg_len, |
| rndis_msg->msg.init_complete.req_id, |
| rndis_msg->msg.init_complete.status, |
| rndis_msg->msg.init_complete.major_ver, |
| rndis_msg->msg.init_complete.minor_ver, |
| rndis_msg->msg.init_complete.dev_flags, |
| rndis_msg->msg.init_complete.max_xfer_size, |
| rndis_msg->msg.init_complete. |
| max_pkt_per_msg, |
| rndis_msg->msg.init_complete. |
| pkt_alignment_factor); |
| break; |
| |
| case REMOTE_NDIS_QUERY_CMPLT: |
| netdev_dbg(netdev, "REMOTE_NDIS_QUERY_CMPLT " |
| "(len %u, id 0x%x, status 0x%x, buf len %u, " |
| "buf offset %u)\n", |
| rndis_msg->msg_len, |
| rndis_msg->msg.query_complete.req_id, |
| rndis_msg->msg.query_complete.status, |
| rndis_msg->msg.query_complete. |
| info_buflen, |
| rndis_msg->msg.query_complete. |
| info_buf_offset); |
| break; |
| |
| case REMOTE_NDIS_SET_CMPLT: |
| netdev_dbg(netdev, |
| "REMOTE_NDIS_SET_CMPLT (len %u, id 0x%x, status 0x%x)\n", |
| rndis_msg->msg_len, |
| rndis_msg->msg.set_complete.req_id, |
| rndis_msg->msg.set_complete.status); |
| break; |
| |
| case REMOTE_NDIS_INDICATE_STATUS_MSG: |
| netdev_dbg(netdev, "REMOTE_NDIS_INDICATE_STATUS_MSG " |
| "(len %u, status 0x%x, buf len %u, buf offset %u)\n", |
| rndis_msg->msg_len, |
| rndis_msg->msg.indicate_status.status, |
| rndis_msg->msg.indicate_status.status_buflen, |
| rndis_msg->msg.indicate_status.status_buf_offset); |
| break; |
| |
| default: |
| netdev_dbg(netdev, "0x%x (len %u)\n", |
| rndis_msg->ndis_msg_type, |
| rndis_msg->msg_len); |
| break; |
| } |
| } |
| |
| static int rndis_filter_send_request(struct rndis_device *dev, |
| struct rndis_request *req) |
| { |
| int ret; |
| struct hv_netvsc_packet *packet; |
| |
| /* Setup the packet to send it */ |
| packet = &req->pkt; |
| |
| packet->is_data_pkt = false; |
| packet->total_data_buflen = req->request_msg.msg_len; |
| packet->page_buf_cnt = 1; |
| |
| packet->page_buf[0].pfn = virt_to_phys(&req->request_msg) >> |
| PAGE_SHIFT; |
| packet->page_buf[0].len = req->request_msg.msg_len; |
| packet->page_buf[0].offset = |
| (unsigned long)&req->request_msg & (PAGE_SIZE - 1); |
| |
| packet->completion.send.send_completion_ctx = req;/* packet; */ |
| packet->completion.send.send_completion = |
| rndis_filter_send_request_completion; |
| packet->completion.send.send_completion_tid = (unsigned long)dev; |
| |
| ret = netvsc_send(dev->net_dev->dev, packet); |
| return ret; |
| } |
| |
| static void rndis_filter_receive_response(struct rndis_device *dev, |
| struct rndis_message *resp) |
| { |
| struct rndis_request *request = NULL; |
| bool found = false; |
| unsigned long flags; |
| struct net_device *ndev; |
| |
| ndev = dev->net_dev->ndev; |
| |
| spin_lock_irqsave(&dev->request_lock, flags); |
| list_for_each_entry(request, &dev->req_list, list_ent) { |
| /* |
| * All request/response message contains RequestId as the 1st |
| * field |
| */ |
| if (request->request_msg.msg.init_req.req_id |
| == resp->msg.init_complete.req_id) { |
| found = true; |
| break; |
| } |
| } |
| spin_unlock_irqrestore(&dev->request_lock, flags); |
| |
| if (found) { |
| if (resp->msg_len <= sizeof(struct rndis_message)) { |
| memcpy(&request->response_msg, resp, |
| resp->msg_len); |
| } else { |
| netdev_err(ndev, |
| "rndis response buffer overflow " |
| "detected (size %u max %zu)\n", |
| resp->msg_len, |
| sizeof(struct rndis_filter_packet)); |
| |
| if (resp->ndis_msg_type == |
| REMOTE_NDIS_RESET_CMPLT) { |
| /* does not have a request id field */ |
| request->response_msg.msg.reset_complete. |
| status = STATUS_BUFFER_OVERFLOW; |
| } else { |
| request->response_msg.msg. |
| init_complete.status = |
| STATUS_BUFFER_OVERFLOW; |
| } |
| } |
| |
| complete(&request->wait_event); |
| } else { |
| netdev_err(ndev, |
| "no rndis request found for this response " |
| "(id 0x%x res type 0x%x)\n", |
| resp->msg.init_complete.req_id, |
| resp->ndis_msg_type); |
| } |
| } |
| |
| static void rndis_filter_receive_indicate_status(struct rndis_device *dev, |
| struct rndis_message *resp) |
| { |
| struct rndis_indicate_status *indicate = |
| &resp->msg.indicate_status; |
| |
| if (indicate->status == RNDIS_STATUS_MEDIA_CONNECT) { |
| netvsc_linkstatus_callback( |
| dev->net_dev->dev, 1); |
| } else if (indicate->status == RNDIS_STATUS_MEDIA_DISCONNECT) { |
| netvsc_linkstatus_callback( |
| dev->net_dev->dev, 0); |
| } else { |
| /* |
| * TODO: |
| */ |
| } |
| } |
| |
| static void rndis_filter_receive_data(struct rndis_device *dev, |
| struct rndis_message *msg, |
| struct hv_netvsc_packet *pkt) |
| { |
| struct rndis_packet *rndis_pkt; |
| u32 data_offset; |
| int i; |
| |
| rndis_pkt = &msg->msg.pkt; |
| |
| /* |
| * FIXME: Handle multiple rndis pkt msgs that maybe enclosed in this |
| * netvsc packet (ie TotalDataBufferLength != MessageLength) |
| */ |
| |
| /* Remove the rndis header and pass it back up the stack */ |
| data_offset = RNDIS_HEADER_SIZE + rndis_pkt->data_offset; |
| |
| pkt->total_data_buflen -= data_offset; |
| pkt->page_buf[0].offset += data_offset; |
| pkt->page_buf[0].len -= data_offset; |
| |
| /* Drop the 0th page, if rndis data go beyond page boundary */ |
| if (pkt->page_buf[0].offset >= PAGE_SIZE) { |
| pkt->page_buf[1].offset = pkt->page_buf[0].offset - PAGE_SIZE; |
| pkt->page_buf[1].len -= pkt->page_buf[1].offset; |
| pkt->page_buf_cnt--; |
| for (i = 0; i < pkt->page_buf_cnt; i++) |
| pkt->page_buf[i] = pkt->page_buf[i+1]; |
| } |
| |
| pkt->is_data_pkt = true; |
| |
| netvsc_recv_callback(dev->net_dev->dev, pkt); |
| } |
| |
| int rndis_filter_receive(struct hv_device *dev, |
| struct hv_netvsc_packet *pkt) |
| { |
| struct netvsc_device *net_dev = hv_get_drvdata(dev); |
| struct rndis_device *rndis_dev; |
| struct rndis_message rndis_msg; |
| struct rndis_message *rndis_hdr; |
| struct net_device *ndev; |
| |
| if (!net_dev) |
| return -EINVAL; |
| |
| ndev = net_dev->ndev; |
| |
| /* Make sure the rndis device state is initialized */ |
| if (!net_dev->extension) { |
| netdev_err(ndev, "got rndis message but no rndis device - " |
| "dropping this message!\n"); |
| return -ENODEV; |
| } |
| |
| rndis_dev = (struct rndis_device *)net_dev->extension; |
| if (rndis_dev->state == RNDIS_DEV_UNINITIALIZED) { |
| netdev_err(ndev, "got rndis message but rndis device " |
| "uninitialized...dropping this message!\n"); |
| return -ENODEV; |
| } |
| |
| rndis_hdr = (struct rndis_message *)kmap_atomic( |
| pfn_to_page(pkt->page_buf[0].pfn), KM_IRQ0); |
| |
| rndis_hdr = (void *)((unsigned long)rndis_hdr + |
| pkt->page_buf[0].offset); |
| |
| /* Make sure we got a valid rndis message */ |
| if ((rndis_hdr->ndis_msg_type != REMOTE_NDIS_PACKET_MSG) && |
| (rndis_hdr->msg_len > sizeof(struct rndis_message))) { |
| netdev_err(ndev, "incoming rndis message buffer overflow " |
| "detected (got %u, max %zu)..marking it an error!\n", |
| rndis_hdr->msg_len, |
| sizeof(struct rndis_message)); |
| } |
| |
| memcpy(&rndis_msg, rndis_hdr, |
| (rndis_hdr->msg_len > sizeof(struct rndis_message)) ? |
| sizeof(struct rndis_message) : |
| rndis_hdr->msg_len); |
| |
| kunmap_atomic(rndis_hdr - pkt->page_buf[0].offset, KM_IRQ0); |
| |
| dump_rndis_message(dev, &rndis_msg); |
| |
| switch (rndis_msg.ndis_msg_type) { |
| case REMOTE_NDIS_PACKET_MSG: |
| /* data msg */ |
| rndis_filter_receive_data(rndis_dev, &rndis_msg, pkt); |
| break; |
| |
| case REMOTE_NDIS_INITIALIZE_CMPLT: |
| case REMOTE_NDIS_QUERY_CMPLT: |
| case REMOTE_NDIS_SET_CMPLT: |
| /* completion msgs */ |
| rndis_filter_receive_response(rndis_dev, &rndis_msg); |
| break; |
| |
| case REMOTE_NDIS_INDICATE_STATUS_MSG: |
| /* notification msgs */ |
| rndis_filter_receive_indicate_status(rndis_dev, &rndis_msg); |
| break; |
| default: |
| netdev_err(ndev, |
| "unhandled rndis message (type %u len %u)\n", |
| rndis_msg.ndis_msg_type, |
| rndis_msg.msg_len); |
| break; |
| } |
| |
| return 0; |
| } |
| |
| static int rndis_filter_query_device(struct rndis_device *dev, u32 oid, |
| void *result, u32 *result_size) |
| { |
| struct rndis_request *request; |
| u32 inresult_size = *result_size; |
| struct rndis_query_request *query; |
| struct rndis_query_complete *query_complete; |
| int ret = 0; |
| int t; |
| |
| if (!result) |
| return -EINVAL; |
| |
| *result_size = 0; |
| request = get_rndis_request(dev, REMOTE_NDIS_QUERY_MSG, |
| RNDIS_MESSAGE_SIZE(struct rndis_query_request)); |
| if (!request) { |
| ret = -ENOMEM; |
| goto cleanup; |
| } |
| |
| /* Setup the rndis query */ |
| query = &request->request_msg.msg.query_req; |
| query->oid = oid; |
| query->info_buf_offset = sizeof(struct rndis_query_request); |
| query->info_buflen = 0; |
| query->dev_vc_handle = 0; |
| |
| ret = rndis_filter_send_request(dev, request); |
| if (ret != 0) |
| goto cleanup; |
| |
| t = wait_for_completion_timeout(&request->wait_event, 5*HZ); |
| if (t == 0) { |
| ret = -ETIMEDOUT; |
| goto cleanup; |
| } |
| |
| /* Copy the response back */ |
| query_complete = &request->response_msg.msg.query_complete; |
| |
| if (query_complete->info_buflen > inresult_size) { |
| ret = -1; |
| goto cleanup; |
| } |
| |
| memcpy(result, |
| (void *)((unsigned long)query_complete + |
| query_complete->info_buf_offset), |
| query_complete->info_buflen); |
| |
| *result_size = query_complete->info_buflen; |
| |
| cleanup: |
| if (request) |
| put_rndis_request(dev, request); |
| |
| return ret; |
| } |
| |
| static int rndis_filter_query_device_mac(struct rndis_device *dev) |
| { |
| u32 size = ETH_ALEN; |
| |
| return rndis_filter_query_device(dev, |
| RNDIS_OID_802_3_PERMANENT_ADDRESS, |
| dev->hw_mac_adr, &size); |
| } |
| |
| static int rndis_filter_query_device_link_status(struct rndis_device *dev) |
| { |
| u32 size = sizeof(u32); |
| u32 link_status; |
| int ret; |
| |
| ret = rndis_filter_query_device(dev, |
| RNDIS_OID_GEN_MEDIA_CONNECT_STATUS, |
| &link_status, &size); |
| dev->link_state = (link_status != 0) ? true : false; |
| |
| return ret; |
| } |
| |
| static int rndis_filter_set_packet_filter(struct rndis_device *dev, |
| u32 new_filter) |
| { |
| struct rndis_request *request; |
| struct rndis_set_request *set; |
| struct rndis_set_complete *set_complete; |
| u32 status; |
| int ret, t; |
| struct net_device *ndev; |
| |
| ndev = dev->net_dev->ndev; |
| |
| request = get_rndis_request(dev, REMOTE_NDIS_SET_MSG, |
| RNDIS_MESSAGE_SIZE(struct rndis_set_request) + |
| sizeof(u32)); |
| if (!request) { |
| ret = -ENOMEM; |
| goto cleanup; |
| } |
| |
| /* Setup the rndis set */ |
| set = &request->request_msg.msg.set_req; |
| set->oid = RNDIS_OID_GEN_CURRENT_PACKET_FILTER; |
| set->info_buflen = sizeof(u32); |
| set->info_buf_offset = sizeof(struct rndis_set_request); |
| |
| memcpy((void *)(unsigned long)set + sizeof(struct rndis_set_request), |
| &new_filter, sizeof(u32)); |
| |
| ret = rndis_filter_send_request(dev, request); |
| if (ret != 0) |
| goto cleanup; |
| |
| t = wait_for_completion_timeout(&request->wait_event, 5*HZ); |
| |
| if (t == 0) { |
| netdev_err(ndev, |
| "timeout before we got a set response...\n"); |
| /* |
| * We can't deallocate the request since we may still receive a |
| * send completion for it. |
| */ |
| goto exit; |
| } else { |
| set_complete = &request->response_msg.msg.set_complete; |
| status = set_complete->status; |
| } |
| |
| cleanup: |
| if (request) |
| put_rndis_request(dev, request); |
| exit: |
| return ret; |
| } |
| |
| |
| static int rndis_filter_init_device(struct rndis_device *dev) |
| { |
| struct rndis_request *request; |
| struct rndis_initialize_request *init; |
| struct rndis_initialize_complete *init_complete; |
| u32 status; |
| int ret, t; |
| |
| request = get_rndis_request(dev, REMOTE_NDIS_INITIALIZE_MSG, |
| RNDIS_MESSAGE_SIZE(struct rndis_initialize_request)); |
| if (!request) { |
| ret = -ENOMEM; |
| goto cleanup; |
| } |
| |
| /* Setup the rndis set */ |
| init = &request->request_msg.msg.init_req; |
| init->major_ver = RNDIS_MAJOR_VERSION; |
| init->minor_ver = RNDIS_MINOR_VERSION; |
| /* FIXME: Use 1536 - rounded ethernet frame size */ |
| init->max_xfer_size = 2048; |
| |
| dev->state = RNDIS_DEV_INITIALIZING; |
| |
| ret = rndis_filter_send_request(dev, request); |
| if (ret != 0) { |
| dev->state = RNDIS_DEV_UNINITIALIZED; |
| goto cleanup; |
| } |
| |
| |
| t = wait_for_completion_timeout(&request->wait_event, 5*HZ); |
| |
| if (t == 0) { |
| ret = -ETIMEDOUT; |
| goto cleanup; |
| } |
| |
| init_complete = &request->response_msg.msg.init_complete; |
| status = init_complete->status; |
| if (status == RNDIS_STATUS_SUCCESS) { |
| dev->state = RNDIS_DEV_INITIALIZED; |
| ret = 0; |
| } else { |
| dev->state = RNDIS_DEV_UNINITIALIZED; |
| ret = -EINVAL; |
| } |
| |
| cleanup: |
| if (request) |
| put_rndis_request(dev, request); |
| |
| return ret; |
| } |
| |
| static void rndis_filter_halt_device(struct rndis_device *dev) |
| { |
| struct rndis_request *request; |
| struct rndis_halt_request *halt; |
| |
| /* Attempt to do a rndis device halt */ |
| request = get_rndis_request(dev, REMOTE_NDIS_HALT_MSG, |
| RNDIS_MESSAGE_SIZE(struct rndis_halt_request)); |
| if (!request) |
| goto cleanup; |
| |
| /* Setup the rndis set */ |
| halt = &request->request_msg.msg.halt_req; |
| halt->req_id = atomic_inc_return(&dev->new_req_id); |
| |
| /* Ignore return since this msg is optional. */ |
| rndis_filter_send_request(dev, request); |
| |
| dev->state = RNDIS_DEV_UNINITIALIZED; |
| |
| cleanup: |
| if (request) |
| put_rndis_request(dev, request); |
| return; |
| } |
| |
| static int rndis_filter_open_device(struct rndis_device *dev) |
| { |
| int ret; |
| |
| if (dev->state != RNDIS_DEV_INITIALIZED) |
| return 0; |
| |
| ret = rndis_filter_set_packet_filter(dev, |
| NDIS_PACKET_TYPE_BROADCAST | |
| NDIS_PACKET_TYPE_ALL_MULTICAST | |
| NDIS_PACKET_TYPE_DIRECTED); |
| if (ret == 0) |
| dev->state = RNDIS_DEV_DATAINITIALIZED; |
| |
| return ret; |
| } |
| |
| static int rndis_filter_close_device(struct rndis_device *dev) |
| { |
| int ret; |
| |
| if (dev->state != RNDIS_DEV_DATAINITIALIZED) |
| return 0; |
| |
| ret = rndis_filter_set_packet_filter(dev, 0); |
| if (ret == 0) |
| dev->state = RNDIS_DEV_INITIALIZED; |
| |
| return ret; |
| } |
| |
| int rndis_filter_device_add(struct hv_device *dev, |
| void *additional_info) |
| { |
| int ret; |
| struct netvsc_device *net_device; |
| struct rndis_device *rndis_device; |
| struct netvsc_device_info *device_info = additional_info; |
| |
| rndis_device = get_rndis_device(); |
| if (!rndis_device) |
| return -ENODEV; |
| |
| /* |
| * Let the inner driver handle this first to create the netvsc channel |
| * NOTE! Once the channel is created, we may get a receive callback |
| * (RndisFilterOnReceive()) before this call is completed |
| */ |
| ret = netvsc_device_add(dev, additional_info); |
| if (ret != 0) { |
| kfree(rndis_device); |
| return ret; |
| } |
| |
| |
| /* Initialize the rndis device */ |
| net_device = hv_get_drvdata(dev); |
| |
| net_device->extension = rndis_device; |
| rndis_device->net_dev = net_device; |
| |
| /* Send the rndis initialization message */ |
| ret = rndis_filter_init_device(rndis_device); |
| if (ret != 0) { |
| /* |
| * TODO: If rndis init failed, we will need to shut down the |
| * channel |
| */ |
| } |
| |
| /* Get the mac address */ |
| ret = rndis_filter_query_device_mac(rndis_device); |
| if (ret != 0) { |
| /* |
| * TODO: shutdown rndis device and the channel |
| */ |
| } |
| |
| memcpy(device_info->mac_adr, rndis_device->hw_mac_adr, ETH_ALEN); |
| |
| rndis_filter_query_device_link_status(rndis_device); |
| |
| device_info->link_state = rndis_device->link_state; |
| |
| dev_info(&dev->device, "Device MAC %pM link state %s\n", |
| rndis_device->hw_mac_adr, |
| device_info->link_state ? "down" : "up"); |
| |
| return ret; |
| } |
| |
| void rndis_filter_device_remove(struct hv_device *dev) |
| { |
| struct netvsc_device *net_dev = hv_get_drvdata(dev); |
| struct rndis_device *rndis_dev = net_dev->extension; |
| |
| /* Halt and release the rndis device */ |
| rndis_filter_halt_device(rndis_dev); |
| |
| kfree(rndis_dev); |
| net_dev->extension = NULL; |
| |
| netvsc_device_remove(dev); |
| } |
| |
| |
| int rndis_filter_open(struct hv_device *dev) |
| { |
| struct netvsc_device *net_device = hv_get_drvdata(dev); |
| |
| if (!net_device) |
| return -EINVAL; |
| |
| return rndis_filter_open_device(net_device->extension); |
| } |
| |
| int rndis_filter_close(struct hv_device *dev) |
| { |
| struct netvsc_device *netDevice = hv_get_drvdata(dev); |
| |
| if (!netDevice) |
| return -EINVAL; |
| |
| return rndis_filter_close_device(netDevice->extension); |
| } |
| |
| int rndis_filter_send(struct hv_device *dev, |
| struct hv_netvsc_packet *pkt) |
| { |
| int ret; |
| struct rndis_filter_packet *filterPacket; |
| struct rndis_message *rndisMessage; |
| struct rndis_packet *rndisPacket; |
| u32 rndisMessageSize; |
| |
| /* Add the rndis header */ |
| filterPacket = (struct rndis_filter_packet *)pkt->extension; |
| |
| memset(filterPacket, 0, sizeof(struct rndis_filter_packet)); |
| |
| rndisMessage = &filterPacket->msg; |
| rndisMessageSize = RNDIS_MESSAGE_SIZE(struct rndis_packet); |
| |
| rndisMessage->ndis_msg_type = REMOTE_NDIS_PACKET_MSG; |
| rndisMessage->msg_len = pkt->total_data_buflen + |
| rndisMessageSize; |
| |
| rndisPacket = &rndisMessage->msg.pkt; |
| rndisPacket->data_offset = sizeof(struct rndis_packet); |
| rndisPacket->data_len = pkt->total_data_buflen; |
| |
| pkt->is_data_pkt = true; |
| pkt->page_buf[0].pfn = virt_to_phys(rndisMessage) >> PAGE_SHIFT; |
| pkt->page_buf[0].offset = |
| (unsigned long)rndisMessage & (PAGE_SIZE-1); |
| pkt->page_buf[0].len = rndisMessageSize; |
| |
| /* Save the packet send completion and context */ |
| filterPacket->completion = pkt->completion.send.send_completion; |
| filterPacket->completion_ctx = |
| pkt->completion.send.send_completion_ctx; |
| |
| /* Use ours */ |
| pkt->completion.send.send_completion = rndis_filter_send_completion; |
| pkt->completion.send.send_completion_ctx = filterPacket; |
| |
| ret = netvsc_send(dev, pkt); |
| if (ret != 0) { |
| /* |
| * Reset the completion to originals to allow retries from |
| * above |
| */ |
| pkt->completion.send.send_completion = |
| filterPacket->completion; |
| pkt->completion.send.send_completion_ctx = |
| filterPacket->completion_ctx; |
| } |
| |
| return ret; |
| } |
| |
| static void rndis_filter_send_completion(void *ctx) |
| { |
| struct rndis_filter_packet *filterPacket = ctx; |
| |
| /* Pass it back to the original handler */ |
| filterPacket->completion(filterPacket->completion_ctx); |
| } |
| |
| |
| static void rndis_filter_send_request_completion(void *ctx) |
| { |
| /* Noop */ |
| } |