From b3b43e70eb23a53affef35600e033760bf21a8ee55e872d0648aa2d100e58aeb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Schr=C3=B6ter?= Date: Wed, 30 Oct 2024 16:12:35 +0100 Subject: [PATCH] Sync from SUSE:SLFO:Main hyper-v revision 920028c3cd1dd098469a5e5be0869e26 --- hyper-v.changes | 22 + hyper-v.include.linux.hyperv.h | 917 +------------------------ hyper-v.spec | 194 ++++-- hyper-v.tools.hv.hv_fcopy_uio_daemon.c | 497 ++++++++++++++ hyper-v.tools.hv.hv_kvp_daemon.c | 387 +++++++++-- hyper-v.tools.hv.hv_set_ifconfig.sh | 47 +- hyper-v.tools.hv.vmbus_bufring.c | 318 +++++++++ hyper-v.tools.hv.vmbus_bufring.h | 158 +++++ 8 files changed, 1537 insertions(+), 1003 deletions(-) create mode 100644 hyper-v.tools.hv.hv_fcopy_uio_daemon.c create mode 100644 hyper-v.tools.hv.vmbus_bufring.c create mode 100644 hyper-v.tools.hv.vmbus_bufring.h diff --git a/hyper-v.changes b/hyper-v.changes index f2bee65..19c9e00 100644 --- a/hyper-v.changes +++ b/hyper-v.changes @@ -1,3 +1,25 @@ +------------------------------------------------------------------- +Tue Oct 22 22:22:22 UTC 2024 - ohering@suse.de + +- Add memory allocation check in hv_fcopy_start (94e86b17) +- suppress the invalid warning for packed member alignment (207e03b0) +- Add new fcopy application based on uio driver (82b0945c) +- Add vmbus_bufring (45bab4d7) +- kvp: Handle IPv4 and Ipv6 combination for keyfile format (f971f6dd) +- kvp: Some small fixes for handling NM keyfiles (c3803203) +- kvp: Support for keyfile based connection profile (42999c90) +- kvp: remove unnecessary (void*) conversions (22589542) +- Remove an extraneous "the" (f15f39fa) +- change http to https in hv_kvp_daemon.c (fa52a4b2) +- replace the copy of include/linux/hyperv.h with include/uapi/linux/hyperv.h (6de74d10) +- merge individual udev rules files into a single rules file +- package only files, not directories already owned by filesystem.rpm +- remove braces from rpm spec macros +- remove obsolete Group tag +- replace RPM_BUILD_ROOT with buildroot +- use a meaningful name for the UAPI include file +- use a meaningful variable name for ifcfg in hv_set_ifconfig.sh + ------------------------------------------------------------------- Tue Apr 23 10:01:10 UTC 2024 - pgajdos@suse.com diff --git a/hyper-v.include.linux.hyperv.h b/hyper-v.include.linux.hyperv.h index 54f11ae..d08235b 100644 --- a/hyper-v.include.linux.hyperv.h +++ b/hyper-v.include.linux.hyperv.h @@ -1,3 +1,4 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ /* * * Copyright (c) 2011, Microsoft Corporation. @@ -22,14 +23,21 @@ * */ -#ifndef _HYPERV_H -#define _HYPERV_H +#ifndef _UAPI_HYPERV_H +#define _UAPI_HYPERV_H #include -/* from linux/uuid.h */ -typedef struct { - __u8 b[16]; -} uuid_le; + +/* + * Framework version for util services. + */ +#define UTIL_FW_MINOR 0 + +#define UTIL_WS2K8_FW_MAJOR 1 +#define UTIL_WS2K8_FW_VERSION (UTIL_WS2K8_FW_MAJOR << 16 | UTIL_FW_MINOR) + +#define UTIL_FW_MAJOR 3 +#define UTIL_FW_VERSION (UTIL_FW_MAJOR << 16 | UTIL_FW_MINOR) /* @@ -82,6 +90,17 @@ struct hv_vss_check_dm_info { __u32 flags; } __attribute__((packed)); +/* + * struct hv_vss_msg encodes the fields that the Linux VSS + * driver accesses. However, FREEZE messages from Hyper-V contain + * additional LUN information that Linux doesn't use and are not + * represented in struct hv_vss_msg. A received FREEZE message may + * be as large as 6,260 bytes, so the driver must allocate at least + * that much space, not sizeof(struct hv_vss_msg). Other messages + * such as AUTO_RECOVER may be as large as 12,500 bytes. However, + * because the Linux VSS driver responds that it doesn't support + * auto-recovery, it should not receive such messages. + */ struct hv_vss_msg { union { struct hv_vss_hdr vss_hdr; @@ -93,7 +112,6 @@ struct hv_vss_msg { }; } __attribute__((packed)); - /* * Implementation of a host to guest copy facility. */ @@ -112,8 +130,8 @@ enum hv_fcopy_op { struct hv_fcopy_hdr { __u32 operation; - uuid_le service_id0; /* currently unused */ - uuid_le service_id1; /* currently unused */ + __u8 service_id0[16]; /* currently unused */ + __u8 service_id1[16]; /* currently unused */ } __attribute__((packed)); #define OVER_WRITE 0x1 @@ -140,7 +158,6 @@ struct hv_do_fcopy { __u8 data[DATA_FRAGMENT]; } __attribute__((packed)); - /* * An implementation of HyperV key value pair (KVP) functionality for Linux. * @@ -213,7 +230,7 @@ struct hv_do_fcopy { * kernel and user-level daemon communicate using a connector channel. * * The user mode component first registers with the - * the kernel component. Subsequently, the kernel component requests, data + * kernel component. Subsequently, the kernel component requests, data * for the specified keys. In response to this message the user mode component * fills in the value corresponding to the specified key. We overload the * sequence field in the cn_msg header to define our KVP message types. @@ -391,880 +408,4 @@ struct hv_kvp_ip_msg { struct hv_kvp_ipaddr_value kvp_ip_val; } __attribute__((packed)); -#ifdef __KERNEL__ -#include -#include -#include -#include -#include -#include -#include -#include - - -#define MAX_PAGE_BUFFER_COUNT 19 -#define MAX_MULTIPAGE_BUFFER_COUNT 32 /* 128K */ - -#pragma pack(push, 1) - -/* Single-page buffer */ -struct hv_page_buffer { - u32 len; - u32 offset; - u64 pfn; -}; - -/* Multiple-page buffer */ -struct hv_multipage_buffer { - /* Length and Offset determines the # of pfns in the array */ - u32 len; - u32 offset; - u64 pfn_array[MAX_MULTIPAGE_BUFFER_COUNT]; -}; - -/* 0x18 includes the proprietary packet header */ -#define MAX_PAGE_BUFFER_PACKET (0x18 + \ - (sizeof(struct hv_page_buffer) * \ - MAX_PAGE_BUFFER_COUNT)) -#define MAX_MULTIPAGE_BUFFER_PACKET (0x18 + \ - sizeof(struct hv_multipage_buffer)) - - -#pragma pack(pop) - -struct hv_ring_buffer { - /* Offset in bytes from the start of ring data below */ - u32 write_index; - - /* Offset in bytes from the start of ring data below */ - u32 read_index; - - u32 interrupt_mask; - - /* Pad it to PAGE_SIZE so that data starts on page boundary */ - u8 reserved[4084]; - - /* NOTE: - * The interrupt_mask field is used only for channels but since our - * vmbus connection also uses this data structure and its data starts - * here, we commented out this field. - */ - - /* - * Ring data starts here + RingDataStartOffset - * !!! DO NOT place any fields below this !!! - */ - u8 buffer[0]; -} __packed; - -struct hv_ring_buffer_info { - struct hv_ring_buffer *ring_buffer; - u32 ring_size; /* Include the shared header */ - spinlock_t ring_lock; - - u32 ring_datasize; /* < ring_size */ - u32 ring_data_startoffset; -}; - -struct hv_ring_buffer_debug_info { - u32 current_interrupt_mask; - u32 current_read_index; - u32 current_write_index; - u32 bytes_avail_toread; - u32 bytes_avail_towrite; -}; - - -/* - * - * hv_get_ringbuffer_availbytes() - * - * Get number of bytes available to read and to write to - * for the specified ring buffer - */ -static inline void -hv_get_ringbuffer_availbytes(struct hv_ring_buffer_info *rbi, - u32 *read, u32 *write) -{ - u32 read_loc, write_loc, dsize; - - smp_read_barrier_depends(); - - /* Capture the read/write indices before they changed */ - read_loc = rbi->ring_buffer->read_index; - write_loc = rbi->ring_buffer->write_index; - dsize = rbi->ring_datasize; - - *write = write_loc >= read_loc ? dsize - (write_loc - read_loc) : - read_loc - write_loc; - *read = dsize - *write; -} - - -/* - * We use the same version numbering for all Hyper-V modules. - * - * Definition of versioning is as follows; - * - * Major Number Changes for these scenarios; - * 1. When a new version of Windows Hyper-V - * is released. - * 2. A Major change has occurred in the - * Linux IC's. - * (For example the merge for the first time - * into the kernel) Every time the Major Number - * changes, the Revision number is reset to 0. - * Minor Number Changes when new functionality is added - * to the Linux IC's that is not a bug fix. - * - * 3.1 - Added completed hv_utils driver. Shutdown/Heartbeat/Timesync - */ -#define HV_DRV_VERSION "3.1" - - -/* - * A revision number of vmbus that is used for ensuring both ends on a - * partition are using compatible versions. - */ -#define VMBUS_REVISION_NUMBER 13 - -/* Make maximum size of pipe payload of 16K */ -#define MAX_PIPE_DATA_PAYLOAD (sizeof(u8) * 16384) - -/* Define PipeMode values. */ -#define VMBUS_PIPE_TYPE_BYTE 0x00000000 -#define VMBUS_PIPE_TYPE_MESSAGE 0x00000004 - -/* The size of the user defined data buffer for non-pipe offers. */ -#define MAX_USER_DEFINED_BYTES 120 - -/* The size of the user defined data buffer for pipe offers. */ -#define MAX_PIPE_USER_DEFINED_BYTES 116 - -/* - * At the center of the Channel Management library is the Channel Offer. This - * struct contains the fundamental information about an offer. - */ -struct vmbus_channel_offer { - uuid_le if_type; - uuid_le if_instance; - u64 int_latency; /* in 100ns units */ - u32 if_revision; - u32 server_ctx_size; /* in bytes */ - u16 chn_flags; - u16 mmio_megabytes; /* in bytes * 1024 * 1024 */ - - union { - /* Non-pipes: The user has MAX_USER_DEFINED_BYTES bytes. */ - struct { - unsigned char user_def[MAX_USER_DEFINED_BYTES]; - } std; - - /* - * Pipes: - * The following sructure is an integrated pipe protocol, which - * is implemented on top of standard user-defined data. Pipe - * clients have MAX_PIPE_USER_DEFINED_BYTES left for their own - * use. - */ - struct { - u32 pipe_mode; - unsigned char user_def[MAX_PIPE_USER_DEFINED_BYTES]; - } pipe; - } u; - u32 padding; -} __packed; - -/* Server Flags */ -#define VMBUS_CHANNEL_ENUMERATE_DEVICE_INTERFACE 1 -#define VMBUS_CHANNEL_SERVER_SUPPORTS_TRANSFER_PAGES 2 -#define VMBUS_CHANNEL_SERVER_SUPPORTS_GPADLS 4 -#define VMBUS_CHANNEL_NAMED_PIPE_MODE 0x10 -#define VMBUS_CHANNEL_LOOPBACK_OFFER 0x100 -#define VMBUS_CHANNEL_PARENT_OFFER 0x200 -#define VMBUS_CHANNEL_REQUEST_MONITORED_NOTIFICATION 0x400 - -struct vmpacket_descriptor { - u16 type; - u16 offset8; - u16 len8; - u16 flags; - u64 trans_id; -} __packed; - -struct vmpacket_header { - u32 prev_pkt_start_offset; - struct vmpacket_descriptor descriptor; -} __packed; - -struct vmtransfer_page_range { - u32 byte_count; - u32 byte_offset; -} __packed; - -struct vmtransfer_page_packet_header { - struct vmpacket_descriptor d; - u16 xfer_pageset_id; - u8 sender_owns_set; - u8 reserved; - u32 range_cnt; - struct vmtransfer_page_range ranges[1]; -} __packed; - -struct vmgpadl_packet_header { - struct vmpacket_descriptor d; - u32 gpadl; - u32 reserved; -} __packed; - -struct vmadd_remove_transfer_page_set { - struct vmpacket_descriptor d; - u32 gpadl; - u16 xfer_pageset_id; - u16 reserved; -} __packed; - -/* - * This structure defines a range in guest physical space that can be made to - * look virtually contiguous. - */ -struct gpa_range { - u32 byte_count; - u32 byte_offset; - u64 pfn_array[0]; -}; - -/* - * This is the format for an Establish Gpadl packet, which contains a handle by - * which this GPADL will be known and a set of GPA ranges associated with it. - * This can be converted to a MDL by the guest OS. If there are multiple GPA - * ranges, then the resulting MDL will be "chained," representing multiple VA - * ranges. - */ -struct vmestablish_gpadl { - struct vmpacket_descriptor d; - u32 gpadl; - u32 range_cnt; - struct gpa_range range[1]; -} __packed; - -/* - * This is the format for a Teardown Gpadl packet, which indicates that the - * GPADL handle in the Establish Gpadl packet will never be referenced again. - */ -struct vmteardown_gpadl { - struct vmpacket_descriptor d; - u32 gpadl; - u32 reserved; /* for alignment to a 8-byte boundary */ -} __packed; - -/* - * This is the format for a GPA-Direct packet, which contains a set of GPA - * ranges, in addition to commands and/or data. - */ -struct vmdata_gpa_direct { - struct vmpacket_descriptor d; - u32 reserved; - u32 range_cnt; - struct gpa_range range[1]; -} __packed; - -/* This is the format for a Additional Data Packet. */ -struct vmadditional_data { - struct vmpacket_descriptor d; - u64 total_bytes; - u32 offset; - u32 byte_cnt; - unsigned char data[1]; -} __packed; - -union vmpacket_largest_possible_header { - struct vmpacket_descriptor simple_hdr; - struct vmtransfer_page_packet_header xfer_page_hdr; - struct vmgpadl_packet_header gpadl_hdr; - struct vmadd_remove_transfer_page_set add_rm_xfer_page_hdr; - struct vmestablish_gpadl establish_gpadl_hdr; - struct vmteardown_gpadl teardown_gpadl_hdr; - struct vmdata_gpa_direct data_gpa_direct_hdr; -}; - -#define VMPACKET_DATA_START_ADDRESS(__packet) \ - (void *)(((unsigned char *)__packet) + \ - ((struct vmpacket_descriptor)__packet)->offset8 * 8) - -#define VMPACKET_DATA_LENGTH(__packet) \ - ((((struct vmpacket_descriptor)__packet)->len8 - \ - ((struct vmpacket_descriptor)__packet)->offset8) * 8) - -#define VMPACKET_TRANSFER_MODE(__packet) \ - (((struct IMPACT)__packet)->type) - -enum vmbus_packet_type { - VM_PKT_INVALID = 0x0, - VM_PKT_SYNCH = 0x1, - VM_PKT_ADD_XFER_PAGESET = 0x2, - VM_PKT_RM_XFER_PAGESET = 0x3, - VM_PKT_ESTABLISH_GPADL = 0x4, - VM_PKT_TEARDOWN_GPADL = 0x5, - VM_PKT_DATA_INBAND = 0x6, - VM_PKT_DATA_USING_XFER_PAGES = 0x7, - VM_PKT_DATA_USING_GPADL = 0x8, - VM_PKT_DATA_USING_GPA_DIRECT = 0x9, - VM_PKT_CANCEL_REQUEST = 0xa, - VM_PKT_COMP = 0xb, - VM_PKT_DATA_USING_ADDITIONAL_PKT = 0xc, - VM_PKT_ADDITIONAL_DATA = 0xd -}; - -#define VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED 1 - - -/* Version 1 messages */ -enum vmbus_channel_message_type { - CHANNELMSG_INVALID = 0, - CHANNELMSG_OFFERCHANNEL = 1, - CHANNELMSG_RESCIND_CHANNELOFFER = 2, - CHANNELMSG_REQUESTOFFERS = 3, - CHANNELMSG_ALLOFFERS_DELIVERED = 4, - CHANNELMSG_OPENCHANNEL = 5, - CHANNELMSG_OPENCHANNEL_RESULT = 6, - CHANNELMSG_CLOSECHANNEL = 7, - CHANNELMSG_GPADL_HEADER = 8, - CHANNELMSG_GPADL_BODY = 9, - CHANNELMSG_GPADL_CREATED = 10, - CHANNELMSG_GPADL_TEARDOWN = 11, - CHANNELMSG_GPADL_TORNDOWN = 12, - CHANNELMSG_RELID_RELEASED = 13, - CHANNELMSG_INITIATE_CONTACT = 14, - CHANNELMSG_VERSION_RESPONSE = 15, - CHANNELMSG_UNLOAD = 16, -#ifdef VMBUS_FEATURE_PARENT_OR_PEER_MEMORY_MAPPED_INTO_A_CHILD - CHANNELMSG_VIEWRANGE_ADD = 17, - CHANNELMSG_VIEWRANGE_REMOVE = 18, -#endif - CHANNELMSG_COUNT -}; - -struct vmbus_channel_message_header { - enum vmbus_channel_message_type msgtype; - u32 padding; -} __packed; - -/* Query VMBus Version parameters */ -struct vmbus_channel_query_vmbus_version { - struct vmbus_channel_message_header header; - u32 version; -} __packed; - -/* VMBus Version Supported parameters */ -struct vmbus_channel_version_supported { - struct vmbus_channel_message_header header; - u8 version_supported; -} __packed; - -/* Offer Channel parameters */ -struct vmbus_channel_offer_channel { - struct vmbus_channel_message_header header; - struct vmbus_channel_offer offer; - u32 child_relid; - u8 monitorid; - u8 monitor_allocated; -} __packed; - -/* Rescind Offer parameters */ -struct vmbus_channel_rescind_offer { - struct vmbus_channel_message_header header; - u32 child_relid; -} __packed; - -/* - * Request Offer -- no parameters, SynIC message contains the partition ID - * Set Snoop -- no parameters, SynIC message contains the partition ID - * Clear Snoop -- no parameters, SynIC message contains the partition ID - * All Offers Delivered -- no parameters, SynIC message contains the partition - * ID - * Flush Client -- no parameters, SynIC message contains the partition ID - */ - -/* Open Channel parameters */ -struct vmbus_channel_open_channel { - struct vmbus_channel_message_header header; - - /* Identifies the specific VMBus channel that is being opened. */ - u32 child_relid; - - /* ID making a particular open request at a channel offer unique. */ - u32 openid; - - /* GPADL for the channel's ring buffer. */ - u32 ringbuffer_gpadlhandle; - - /* GPADL for the channel's server context save area. */ - u32 server_contextarea_gpadlhandle; - - /* - * The upstream ring buffer begins at offset zero in the memory - * described by RingBufferGpadlHandle. The downstream ring buffer - * follows it at this offset (in pages). - */ - u32 downstream_ringbuffer_pageoffset; - - /* User-specific data to be passed along to the server endpoint. */ - unsigned char userdata[MAX_USER_DEFINED_BYTES]; -} __packed; - -/* Open Channel Result parameters */ -struct vmbus_channel_open_result { - struct vmbus_channel_message_header header; - u32 child_relid; - u32 openid; - u32 status; -} __packed; - -/* Close channel parameters; */ -struct vmbus_channel_close_channel { - struct vmbus_channel_message_header header; - u32 child_relid; -} __packed; - -/* Channel Message GPADL */ -#define GPADL_TYPE_RING_BUFFER 1 -#define GPADL_TYPE_SERVER_SAVE_AREA 2 -#define GPADL_TYPE_TRANSACTION 8 - -/* - * The number of PFNs in a GPADL message is defined by the number of - * pages that would be spanned by ByteCount and ByteOffset. If the - * implied number of PFNs won't fit in this packet, there will be a - * follow-up packet that contains more. - */ -struct vmbus_channel_gpadl_header { - struct vmbus_channel_message_header header; - u32 child_relid; - u32 gpadl; - u16 range_buflen; - u16 rangecount; - struct gpa_range range[0]; -} __packed; - -/* This is the followup packet that contains more PFNs. */ -struct vmbus_channel_gpadl_body { - struct vmbus_channel_message_header header; - u32 msgnumber; - u32 gpadl; - u64 pfn[0]; -} __packed; - -struct vmbus_channel_gpadl_created { - struct vmbus_channel_message_header header; - u32 child_relid; - u32 gpadl; - u32 creation_status; -} __packed; - -struct vmbus_channel_gpadl_teardown { - struct vmbus_channel_message_header header; - u32 child_relid; - u32 gpadl; -} __packed; - -struct vmbus_channel_gpadl_torndown { - struct vmbus_channel_message_header header; - u32 gpadl; -} __packed; - -#ifdef VMBUS_FEATURE_PARENT_OR_PEER_MEMORY_MAPPED_INTO_A_CHILD -struct vmbus_channel_view_range_add { - struct vmbus_channel_message_header header; - PHYSICAL_ADDRESS viewrange_base; - u64 viewrange_length; - u32 child_relid; -} __packed; - -struct vmbus_channel_view_range_remove { - struct vmbus_channel_message_header header; - PHYSICAL_ADDRESS viewrange_base; - u32 child_relid; -} __packed; -#endif - -struct vmbus_channel_relid_released { - struct vmbus_channel_message_header header; - u32 child_relid; -} __packed; - -struct vmbus_channel_initiate_contact { - struct vmbus_channel_message_header header; - u32 vmbus_version_requested; - u32 padding2; - u64 interrupt_page; - u64 monitor_page1; - u64 monitor_page2; -} __packed; - -struct vmbus_channel_version_response { - struct vmbus_channel_message_header header; - u8 version_supported; -} __packed; - -enum vmbus_channel_state { - CHANNEL_OFFER_STATE, - CHANNEL_OPENING_STATE, - CHANNEL_OPEN_STATE, -}; - -struct vmbus_channel_debug_info { - u32 relid; - enum vmbus_channel_state state; - uuid_le interfacetype; - uuid_le interface_instance; - u32 monitorid; - u32 servermonitor_pending; - u32 servermonitor_latency; - u32 servermonitor_connectionid; - u32 clientmonitor_pending; - u32 clientmonitor_latency; - u32 clientmonitor_connectionid; - - struct hv_ring_buffer_debug_info inbound; - struct hv_ring_buffer_debug_info outbound; -}; - -/* - * Represents each channel msg on the vmbus connection This is a - * variable-size data structure depending on the msg type itself - */ -struct vmbus_channel_msginfo { - /* Bookkeeping stuff */ - struct list_head msglistentry; - - /* So far, this is only used to handle gpadl body message */ - struct list_head submsglist; - - /* Synchronize the request/response if needed */ - struct completion waitevent; - union { - struct vmbus_channel_version_supported version_supported; - struct vmbus_channel_open_result open_result; - struct vmbus_channel_gpadl_torndown gpadl_torndown; - struct vmbus_channel_gpadl_created gpadl_created; - struct vmbus_channel_version_response version_response; - } response; - - u32 msgsize; - /* - * The channel message that goes out on the "wire". - * It will contain at minimum the VMBUS_CHANNEL_MESSAGE_HEADER header - */ - unsigned char msg[0]; -}; - -struct vmbus_close_msg { - struct vmbus_channel_msginfo info; - struct vmbus_channel_close_channel msg; -}; - -struct vmbus_channel { - struct list_head listentry; - - struct hv_device *device_obj; - - struct work_struct work; - - enum vmbus_channel_state state; - - struct vmbus_channel_offer_channel offermsg; - /* - * These are based on the OfferMsg.MonitorId. - * Save it here for easy access. - */ - u8 monitor_grp; - u8 monitor_bit; - - u32 ringbuffer_gpadlhandle; - - /* Allocated memory for ring buffer */ - void *ringbuffer_pages; - u32 ringbuffer_pagecount; - struct hv_ring_buffer_info outbound; /* send to parent */ - struct hv_ring_buffer_info inbound; /* receive from parent */ - spinlock_t inbound_lock; - struct workqueue_struct *controlwq; - - struct vmbus_close_msg close_msg; - - /* Channel callback are invoked in this workqueue context */ - /* HANDLE dataWorkQueue; */ - - void (*onchannel_callback)(void *context); - void *channel_callback_context; -}; - -void vmbus_onmessage(void *context); - -int vmbus_request_offers(void); - -/* The format must be the same as struct vmdata_gpa_direct */ -struct vmbus_channel_packet_page_buffer { - u16 type; - u16 dataoffset8; - u16 length8; - u16 flags; - u64 transactionid; - u32 reserved; - u32 rangecount; - struct hv_page_buffer range[MAX_PAGE_BUFFER_COUNT]; -} __packed; - -/* The format must be the same as struct vmdata_gpa_direct */ -struct vmbus_channel_packet_multipage_buffer { - u16 type; - u16 dataoffset8; - u16 length8; - u16 flags; - u64 transactionid; - u32 reserved; - u32 rangecount; /* Always 1 in this case */ - struct hv_multipage_buffer range; -} __packed; - - -extern int vmbus_open(struct vmbus_channel *channel, - u32 send_ringbuffersize, - u32 recv_ringbuffersize, - void *userdata, - u32 userdatalen, - void(*onchannel_callback)(void *context), - void *context); - -extern void vmbus_close(struct vmbus_channel *channel); - -extern int vmbus_sendpacket(struct vmbus_channel *channel, - const void *buffer, - u32 bufferLen, - u64 requestid, - enum vmbus_packet_type type, - u32 flags); - -extern int vmbus_sendpacket_pagebuffer(struct vmbus_channel *channel, - struct hv_page_buffer pagebuffers[], - u32 pagecount, - void *buffer, - u32 bufferlen, - u64 requestid); - -extern int vmbus_sendpacket_multipagebuffer(struct vmbus_channel *channel, - struct hv_multipage_buffer *mpb, - void *buffer, - u32 bufferlen, - u64 requestid); - -extern int vmbus_establish_gpadl(struct vmbus_channel *channel, - void *kbuffer, - u32 size, - u32 *gpadl_handle); - -extern int vmbus_teardown_gpadl(struct vmbus_channel *channel, - u32 gpadl_handle); - -extern int vmbus_recvpacket(struct vmbus_channel *channel, - void *buffer, - u32 bufferlen, - u32 *buffer_actual_len, - u64 *requestid); - -extern int vmbus_recvpacket_raw(struct vmbus_channel *channel, - void *buffer, - u32 bufferlen, - u32 *buffer_actual_len, - u64 *requestid); - - -extern void vmbus_get_debug_info(struct vmbus_channel *channel, - struct vmbus_channel_debug_info *debug); - -extern void vmbus_ontimer(unsigned long data); - -struct hv_dev_port_info { - u32 int_mask; - u32 read_idx; - u32 write_idx; - u32 bytes_avail_toread; - u32 bytes_avail_towrite; -}; - -/* Base driver object */ -struct hv_driver { - const char *name; - - /* the device type supported by this driver */ - uuid_le dev_type; - const struct hv_vmbus_device_id *id_table; - - struct device_driver driver; - - int (*probe)(struct hv_device *, const struct hv_vmbus_device_id *); - int (*remove)(struct hv_device *); - void (*shutdown)(struct hv_device *); - -}; - -/* Base device object */ -struct hv_device { - /* the device type id of this device */ - uuid_le dev_type; - - /* the device instance id of this device */ - uuid_le dev_instance; - - struct device device; - - struct vmbus_channel *channel; -}; - - -static inline struct hv_device *device_to_hv_device(struct device *d) -{ - return container_of(d, struct hv_device, device); -} - -static inline struct hv_driver *drv_to_hv_drv(struct device_driver *d) -{ - return container_of(d, struct hv_driver, driver); -} - -static inline void hv_set_drvdata(struct hv_device *dev, void *data) -{ - dev_set_drvdata(&dev->device, data); -} - -static inline void *hv_get_drvdata(struct hv_device *dev) -{ - return dev_get_drvdata(&dev->device); -} - -/* Vmbus interface */ -#define vmbus_driver_register(driver) \ - __vmbus_driver_register(driver, THIS_MODULE, KBUILD_MODNAME) -int __must_check __vmbus_driver_register(struct hv_driver *hv_driver, - struct module *owner, - const char *mod_name); -void vmbus_driver_unregister(struct hv_driver *hv_driver); - -/** - * VMBUS_DEVICE - macro used to describe a specific hyperv vmbus device - * - * This macro is used to create a struct hv_vmbus_device_id that matches a - * specific device. - */ -#define VMBUS_DEVICE(g0, g1, g2, g3, g4, g5, g6, g7, \ - g8, g9, ga, gb, gc, gd, ge, gf) \ - .guid = { g0, g1, g2, g3, g4, g5, g6, g7, \ - g8, g9, ga, gb, gc, gd, ge, gf }, - -/* - * Common header for Hyper-V ICs - */ - -#define ICMSGTYPE_NEGOTIATE 0 -#define ICMSGTYPE_HEARTBEAT 1 -#define ICMSGTYPE_KVPEXCHANGE 2 -#define ICMSGTYPE_SHUTDOWN 3 -#define ICMSGTYPE_TIMESYNC 4 -#define ICMSGTYPE_VSS 5 - -#define ICMSGHDRFLAG_TRANSACTION 1 -#define ICMSGHDRFLAG_REQUEST 2 -#define ICMSGHDRFLAG_RESPONSE 4 - - -/* - * While we want to handle util services as regular devices, - * there is only one instance of each of these services; so - * we statically allocate the service specific state. - */ - -struct hv_util_service { - u8 *recv_buffer; - void (*util_cb)(void *); - int (*util_init)(struct hv_util_service *); - void (*util_deinit)(void); -}; - -struct vmbuspipe_hdr { - u32 flags; - u32 msgsize; -} __packed; - -struct ic_version { - u16 major; - u16 minor; -} __packed; - -struct icmsg_hdr { - struct ic_version icverframe; - u16 icmsgtype; - struct ic_version icvermsg; - u16 icmsgsize; - u32 status; - u8 ictransaction_id; - u8 icflags; - u8 reserved[2]; -} __packed; - -struct icmsg_negotiate { - u16 icframe_vercnt; - u16 icmsg_vercnt; - u32 reserved; - struct ic_version icversion_data[1]; /* any size array */ -} __packed; - -struct shutdown_msg_data { - u32 reason_code; - u32 timeout_seconds; - u32 flags; - u8 display_message[2048]; -} __packed; - -struct heartbeat_msg_data { - u64 seq_num; - u32 reserved[8]; -} __packed; - -/* Time Sync IC defs */ -#define ICTIMESYNCFLAG_PROBE 0 -#define ICTIMESYNCFLAG_SYNC 1 -#define ICTIMESYNCFLAG_SAMPLE 2 - -#ifdef __x86_64__ -#define WLTIMEDELTA 116444736000000000L /* in 100ns unit */ -#else -#define WLTIMEDELTA 116444736000000000LL -#endif - -struct ictimesync_data { - u64 parenttime; - u64 childtime; - u64 roundtriptime; - u8 flags; -} __packed; - -struct hyperv_service_callback { - u8 msg_type; - char *log_msg; - uuid_le data; - struct vmbus_channel *channel; - void (*callback) (void *context); -}; - -#define MAX_SRV_VER 0x7ffffff -extern void vmbus_prep_negotiate_resp(struct icmsg_hdr *, - struct icmsg_negotiate *, u8 *, int, - int); - -int hv_kvp_init(struct hv_util_service *); -void hv_kvp_deinit(void); -void hv_kvp_onchannelcallback(void *); - -#endif /* __KERNEL__ */ -#endif /* _HYPERV_H */ +#endif /* _UAPI_HYPERV_H */ diff --git a/hyper-v.spec b/hyper-v.spec index 359558b..aaf93ab 100644 --- a/hyper-v.spec +++ b/hyper-v.spec @@ -19,7 +19,13 @@ %define hv_kvp_daemon hv_kvp_daemon %define hv_vss_daemon hv_vss_daemon %define hv_fcopy_daemon hv_fcopy_daemon -%define helper_dir /usr/lib/%{name} +%define vmbus_bufring vmbus_bufring +%define hv_fcopy_uio_daemon hv_fcopy_uio_daemon +%define include_uapi_linux_hyperv include_uapi_linux_hyperv +%define chardev_kvp vmbus/hv_kvp +%define chardev_vss vmbus/hv_vss +%define chardev_fcopy vmbus/hv_fcopy +%define helper_dir /usr/lib/%name Name: hyper-v ExclusiveArch: %ix86 x86_64 aarch64 @@ -30,19 +36,21 @@ BuildRequires: pkgconfig(systemd) Conflicts: kernel < 4.2 Summary: Microsoft Hyper-V tools License: GPL-2.0-only -Group: System/Kernel Supplements: modalias(dmi:*svnMicrosoftCorporation*pnVirtualMachine*rnVirtualMachine*) Supplements: modalias(pci:v00001414d00005353sv*sd*bc*sc*i*) URL: http://www.kernel.org # Arbitrary version number -Version: 8 +Version: 9 Release: 0 Source0: hyper-v.lsvmbus.py Source5: hyper-v.kvptest.ps1.txt Source7: hyper-v.compare-with-upstream.sh +Source8: hyper-v.tools.hv.vmbus_bufring.h Source9: hyper-v.include.linux.hyperv.h Source10: hyper-v.tools.hv.hv_kvp_daemon.c +Source11: hyper-v.tools.hv.vmbus_bufring.c Source12: hyper-v.tools.hv.hv_vss_daemon.c +Source13: hyper-v.tools.hv.hv_fcopy_uio_daemon.c Source14: hyper-v.tools.hv.hv_fcopy_daemon.c Source20: hyper-v.tools.hv.hv_get_dhcp_info.sh Source21: hyper-v.tools.hv.hv_get_dns_info.sh @@ -56,107 +64,147 @@ This package contains the Microsoft Hyper-V tools. %prep %setup -Tc cp -avL %{S:5} kvptest.ps1.txt -cp -vL %{S:9} %{hv_kvp_daemon}.h +cp -vL %{S:8} %vmbus_bufring.h +cp -vL %{S:9} %include_uapi_linux_hyperv.h cp -vL %{S:10} . -cp -vL %{S:12} %{hv_vss_daemon}.c -cp -vL %{S:14} %{hv_fcopy_daemon}.c +cp -vL %{S:11} %vmbus_bufring.c +cp -vL %{S:12} %hv_vss_daemon.c +cp -vL %{S:13} %hv_fcopy_uio_daemon.c +cp -vL %{S:14} %hv_fcopy_daemon.c %patch -P 0 -p1 -mv `basename %{S:10}` %{hv_kvp_daemon}.c +mv `basename %{S:10}` %hv_kvp_daemon.c %build -sed -i~ '/#include /d' %{hv_kvp_daemon}.c -sed -i~ '/#include /d' %{hv_vss_daemon}.c -sed -i~ '/#include /d' %{hv_fcopy_daemon}.c +sed -i~ '/#include /d' %hv_kvp_daemon.c +sed -i~ '/#include /d' %hv_vss_daemon.c +sed -i~ '/#include /d' %hv_fcopy_uio_daemon.c +sed -i~ '/#include /d' %hv_fcopy_daemon.c gcc \ $RPM_OPT_FLAGS \ -Wno-unused-variable \ -Wno-pointer-sign \ -D_GNU_SOURCE \ -g \ - %{hv_kvp_daemon}.c \ - -include %{hv_kvp_daemon}.h \ + %hv_kvp_daemon.c \ + -include %include_uapi_linux_hyperv.h \ -DCN_KVP_IDX=0x9 \ -DCN_KVP_VAL=0x1 \ -DKVP_SCRIPTS_PATH= \ -pthread \ - -o %{hv_kvp_daemon} + -o %hv_kvp_daemon gcc \ $RPM_OPT_FLAGS \ -Wno-unused-variable \ -Wno-pointer-sign \ -D_GNU_SOURCE \ -g \ - %{hv_vss_daemon}.c \ - -include %{hv_kvp_daemon}.h \ + %hv_vss_daemon.c \ + -include %include_uapi_linux_hyperv.h \ -DCN_VSS_IDX=0xa \ -DCN_VSS_VAL=0x1 \ - -o %{hv_vss_daemon} + -o %hv_vss_daemon gcc \ $RPM_OPT_FLAGS \ -Wno-unused-variable \ -Wno-pointer-sign \ -D_GNU_SOURCE \ -g \ - %{hv_fcopy_daemon}.c \ - -include %{hv_kvp_daemon}.h \ - -o %{hv_fcopy_daemon} + %hv_fcopy_daemon.c \ + -include %include_uapi_linux_hyperv.h \ + -o %hv_fcopy_daemon + +%ifarch %ix86 x86_64 +gcc \ + $RPM_OPT_FLAGS \ + -Wno-unused-variable \ + -Wno-pointer-sign \ + -Wno-address-of-packed-member \ + -D_GNU_SOURCE \ + -g \ + %vmbus_bufring.c \ + %hv_fcopy_uio_daemon.c \ + -include %include_uapi_linux_hyperv.h \ + -o %hv_fcopy_uio_daemon +%endif %install # It is not a callable app anyway, so move it out of the way -bindir=%{helper_dir}/bin -mkdir -p $RPM_BUILD_ROOT${bindir} -mkdir -p $RPM_BUILD_ROOT%{_sbindir} -mkdir -p $RPM_BUILD_ROOT%{helper_dir}/bin -install -m755 %{hv_kvp_daemon} $RPM_BUILD_ROOT${bindir} -install -m755 %{hv_vss_daemon} $RPM_BUILD_ROOT${bindir} -install -m755 %{hv_fcopy_daemon} $RPM_BUILD_ROOT${bindir} -cp -avL %{S:0} $RPM_BUILD_ROOT%{_sbindir}/lsvmbus -chmod 0755 $RPM_BUILD_ROOT%{_sbindir}/lsvmbus -cp -avL %{S:20} $RPM_BUILD_ROOT%{helper_dir}/bin/hv_get_dhcp_info -cp -avL %{S:21} $RPM_BUILD_ROOT%{helper_dir}/bin/hv_get_dns_info -cp -avL %{S:22} $RPM_BUILD_ROOT%{helper_dir}/bin/hv_set_ifconfig -chmod 755 $RPM_BUILD_ROOT%{helper_dir}/bin/* -d=$RPM_BUILD_ROOT%{_unitdir} +bindir=%helper_dir/bin +mkdir -p %buildroot${bindir} +mkdir -p %buildroot%_sbindir +install -m755 %hv_kvp_daemon %buildroot${bindir} +install -m755 %hv_vss_daemon %buildroot${bindir} +install -m755 %hv_fcopy_daemon %buildroot${bindir} +%ifarch %ix86 x86_64 +install -m755 %hv_fcopy_uio_daemon %buildroot${bindir} +%endif +cp -avL %{S:0} %buildroot%_sbindir/lsvmbus +chmod 0755 %buildroot%_sbindir/lsvmbus +cp -avL %{S:20} %buildroot${bindir}/hv_get_dhcp_info +cp -avL %{S:21} %buildroot${bindir}/hv_get_dns_info +cp -avL %{S:22} %buildroot${bindir}/hv_set_ifconfig +chmod 755 %buildroot${bindir}/* +d=%buildroot%_unitdir mkdir -vp ${d} # -tee ${d}/%{hv_kvp_daemon}.service < /sys/bus/vmbus/drivers/uio_hv_generic/new_id' +ExecStart=${bindir}/%hv_fcopy_uio_daemon --no-daemon Restart=on-failure [Install] @@ -165,53 +213,61 @@ EOF # # # -d=$RPM_BUILD_ROOT%{_udevrulesdir} +d=%buildroot%_udevrulesdir mkdir -vp ${d} -tee ${d}/%{hv_kvp_daemon}.rules < /sys/bus/vmbus/drivers/uio_hv_generic/new_id + helpers+=(%hv_fcopy_uio_daemon) + fi +fi if test -d /sys/bus/vmbus/devices then - export PATH=${bindir}:\$PATH + export PATH=${bindir}:$PATH echo -n "Starting hyper-v helpers:" - for i in \ - %{hv_kvp_daemon} \ - %{hv_vss_daemon} \ - %{hv_fcopy_daemon} + for i in "${helpers[@]}" do - if mkdir /run/\$i + if mkdir /run/$i then - echo -n " \$i" - \$i < /dev/null & + echo -n " $i" + $i < /dev/null & fi done echo " ... done" fi -EOF -chmod 755 $RPM_BUILD_ROOT${bindir}/${helper} +_EOF_ +chmod 755 %buildroot${bindir}/${helper} # -%python3_fix_shebang +%?python3_fix_shebang %files %doc kvptest.ps1.txt -%{_unitdir} -%dir /usr/lib/udev -%{_udevrulesdir} -%{_sbindir}/* -%{helper_dir} +%_unitdir/* +%_udevrulesdir/* +%_sbindir/* +%helper_dir %pre # hv_kvp_daemon in SLES11 SP2 stored temporary state files in /var/opt diff --git a/hyper-v.tools.hv.hv_fcopy_uio_daemon.c b/hyper-v.tools.hv.hv_fcopy_uio_daemon.c new file mode 100644 index 0000000..baaba48 --- /dev/null +++ b/hyper-v.tools.hv.hv_fcopy_uio_daemon.c @@ -0,0 +1,497 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * An implementation of host to guest copy functionality for Linux. + * + * Copyright (C) 2023, Microsoft, Inc. + * + * Author : K. Y. Srinivasan + * Author : Saurabh Sengar + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "vmbus_bufring.h" + +#define ICMSGTYPE_NEGOTIATE 0 +#define ICMSGTYPE_FCOPY 7 + +#define WIN8_SRV_MAJOR 1 +#define WIN8_SRV_MINOR 1 +#define WIN8_SRV_VERSION (WIN8_SRV_MAJOR << 16 | WIN8_SRV_MINOR) + +#define MAX_FOLDER_NAME 15 +#define MAX_PATH_LEN 15 +#define FCOPY_UIO "/sys/bus/vmbus/devices/eb765408-105f-49b6-b4aa-c123b64d17d4/uio" + +#define FCOPY_VER_COUNT 1 +static const int fcopy_versions[] = { + WIN8_SRV_VERSION +}; + +#define FW_VER_COUNT 1 +static const int fw_versions[] = { + UTIL_FW_VERSION +}; + +#define HV_RING_SIZE 0x4000 /* 16KB ring buffer size */ + +unsigned char desc[HV_RING_SIZE]; + +static int target_fd; +static char target_fname[PATH_MAX]; +static unsigned long long filesize; + +static int hv_fcopy_create_file(char *file_name, char *path_name, __u32 flags) +{ + int error = HV_E_FAIL; + char *q, *p; + + filesize = 0; + p = path_name; + snprintf(target_fname, sizeof(target_fname), "%s/%s", + path_name, file_name); + + /* + * Check to see if the path is already in place; if not, + * create if required. + */ + while ((q = strchr(p, '/')) != NULL) { + if (q == p) { + p++; + continue; + } + *q = '\0'; + if (access(path_name, F_OK)) { + if (flags & CREATE_PATH) { + if (mkdir(path_name, 0755)) { + syslog(LOG_ERR, "Failed to create %s", + path_name); + goto done; + } + } else { + syslog(LOG_ERR, "Invalid path: %s", path_name); + goto done; + } + } + p = q + 1; + *q = '/'; + } + + if (!access(target_fname, F_OK)) { + syslog(LOG_INFO, "File: %s exists", target_fname); + if (!(flags & OVER_WRITE)) { + error = HV_ERROR_ALREADY_EXISTS; + goto done; + } + } + + target_fd = open(target_fname, + O_RDWR | O_CREAT | O_TRUNC | O_CLOEXEC, 0744); + if (target_fd == -1) { + syslog(LOG_INFO, "Open Failed: %s", strerror(errno)); + goto done; + } + + error = 0; +done: + if (error) + target_fname[0] = '\0'; + return error; +} + +/* copy the data into the file */ +static int hv_copy_data(struct hv_do_fcopy *cpmsg) +{ + ssize_t len; + int ret = 0; + + len = pwrite(target_fd, cpmsg->data, cpmsg->size, cpmsg->offset); + + filesize += cpmsg->size; + if (len != cpmsg->size) { + switch (errno) { + case ENOSPC: + ret = HV_ERROR_DISK_FULL; + break; + default: + ret = HV_E_FAIL; + break; + } + syslog(LOG_ERR, "pwrite failed to write %llu bytes: %ld (%s)", + filesize, (long)len, strerror(errno)); + } + + return ret; +} + +static int hv_copy_finished(void) +{ + close(target_fd); + target_fname[0] = '\0'; + + return 0; +} + +static void print_usage(char *argv[]) +{ + fprintf(stderr, "Usage: %s [options]\n" + "Options are:\n" + " -n, --no-daemon stay in foreground, don't daemonize\n" + " -h, --help print this help\n", argv[0]); +} + +static bool vmbus_prep_negotiate_resp(struct icmsg_hdr *icmsghdrp, unsigned char *buf, + unsigned int buflen, const int *fw_version, int fw_vercnt, + const int *srv_version, int srv_vercnt, + int *nego_fw_version, int *nego_srv_version) +{ + int icframe_major, icframe_minor; + int icmsg_major, icmsg_minor; + int fw_major, fw_minor; + int srv_major, srv_minor; + int i, j; + bool found_match = false; + struct icmsg_negotiate *negop; + + /* Check that there's enough space for icframe_vercnt, icmsg_vercnt */ + if (buflen < ICMSG_HDR + offsetof(struct icmsg_negotiate, reserved)) { + syslog(LOG_ERR, "Invalid icmsg negotiate"); + return false; + } + + icmsghdrp->icmsgsize = 0x10; + negop = (struct icmsg_negotiate *)&buf[ICMSG_HDR]; + + icframe_major = negop->icframe_vercnt; + icframe_minor = 0; + + icmsg_major = negop->icmsg_vercnt; + icmsg_minor = 0; + + /* Validate negop packet */ + if (icframe_major > IC_VERSION_NEGOTIATION_MAX_VER_COUNT || + icmsg_major > IC_VERSION_NEGOTIATION_MAX_VER_COUNT || + ICMSG_NEGOTIATE_PKT_SIZE(icframe_major, icmsg_major) > buflen) { + syslog(LOG_ERR, "Invalid icmsg negotiate - icframe_major: %u, icmsg_major: %u\n", + icframe_major, icmsg_major); + goto fw_error; + } + + /* + * Select the framework version number we will + * support. + */ + + for (i = 0; i < fw_vercnt; i++) { + fw_major = (fw_version[i] >> 16); + fw_minor = (fw_version[i] & 0xFFFF); + + for (j = 0; j < negop->icframe_vercnt; j++) { + if (negop->icversion_data[j].major == fw_major && + negop->icversion_data[j].minor == fw_minor) { + icframe_major = negop->icversion_data[j].major; + icframe_minor = negop->icversion_data[j].minor; + found_match = true; + break; + } + } + + if (found_match) + break; + } + + if (!found_match) + goto fw_error; + + found_match = false; + + for (i = 0; i < srv_vercnt; i++) { + srv_major = (srv_version[i] >> 16); + srv_minor = (srv_version[i] & 0xFFFF); + + for (j = negop->icframe_vercnt; + (j < negop->icframe_vercnt + negop->icmsg_vercnt); + j++) { + if (negop->icversion_data[j].major == srv_major && + negop->icversion_data[j].minor == srv_minor) { + icmsg_major = negop->icversion_data[j].major; + icmsg_minor = negop->icversion_data[j].minor; + found_match = true; + break; + } + } + + if (found_match) + break; + } + + /* + * Respond with the framework and service + * version numbers we can support. + */ +fw_error: + if (!found_match) { + negop->icframe_vercnt = 0; + negop->icmsg_vercnt = 0; + } else { + negop->icframe_vercnt = 1; + negop->icmsg_vercnt = 1; + } + + if (nego_fw_version) + *nego_fw_version = (icframe_major << 16) | icframe_minor; + + if (nego_srv_version) + *nego_srv_version = (icmsg_major << 16) | icmsg_minor; + + negop->icversion_data[0].major = icframe_major; + negop->icversion_data[0].minor = icframe_minor; + negop->icversion_data[1].major = icmsg_major; + negop->icversion_data[1].minor = icmsg_minor; + + return found_match; +} + +static void wcstoutf8(char *dest, const __u16 *src, size_t dest_size) +{ + size_t len = 0; + + while (len < dest_size) { + if (src[len] < 0x80) + dest[len++] = (char)(*src++); + else + dest[len++] = 'X'; + } + + dest[len] = '\0'; +} + +static int hv_fcopy_start(struct hv_start_fcopy *smsg_in) +{ + setlocale(LC_ALL, "en_US.utf8"); + size_t file_size, path_size; + char *file_name, *path_name; + char *in_file_name = (char *)smsg_in->file_name; + char *in_path_name = (char *)smsg_in->path_name; + + file_size = wcstombs(NULL, (const wchar_t *restrict)in_file_name, 0) + 1; + path_size = wcstombs(NULL, (const wchar_t *restrict)in_path_name, 0) + 1; + + file_name = (char *)malloc(file_size * sizeof(char)); + path_name = (char *)malloc(path_size * sizeof(char)); + + if (!file_name || !path_name) { + free(file_name); + free(path_name); + syslog(LOG_ERR, "Can't allocate memory for file name and/or path name"); + return HV_E_FAIL; + } + + wcstoutf8(file_name, (__u16 *)in_file_name, file_size); + wcstoutf8(path_name, (__u16 *)in_path_name, path_size); + + return hv_fcopy_create_file(file_name, path_name, smsg_in->copy_flags); +} + +static int hv_fcopy_send_data(struct hv_fcopy_hdr *fcopy_msg, int recvlen) +{ + int operation = fcopy_msg->operation; + + /* + * The strings sent from the host are encoded in + * utf16; convert it to utf8 strings. + * The host assures us that the utf16 strings will not exceed + * the max lengths specified. We will however, reserve room + * for the string terminating character - in the utf16s_utf8s() + * function we limit the size of the buffer where the converted + * string is placed to W_MAX_PATH -1 to guarantee + * that the strings can be properly terminated! + */ + + switch (operation) { + case START_FILE_COPY: + return hv_fcopy_start((struct hv_start_fcopy *)fcopy_msg); + case WRITE_TO_FILE: + return hv_copy_data((struct hv_do_fcopy *)fcopy_msg); + case COMPLETE_FCOPY: + return hv_copy_finished(); + } + + return HV_E_FAIL; +} + +/* process the packet recv from host */ +static int fcopy_pkt_process(struct vmbus_br *txbr) +{ + int ret, offset, pktlen; + int fcopy_srv_version; + const struct vmbus_chanpkt_hdr *pkt; + struct hv_fcopy_hdr *fcopy_msg; + struct icmsg_hdr *icmsghdr; + + pkt = (const struct vmbus_chanpkt_hdr *)desc; + offset = pkt->hlen << 3; + pktlen = (pkt->tlen << 3) - offset; + icmsghdr = (struct icmsg_hdr *)&desc[offset + sizeof(struct vmbuspipe_hdr)]; + icmsghdr->status = HV_E_FAIL; + + if (icmsghdr->icmsgtype == ICMSGTYPE_NEGOTIATE) { + if (vmbus_prep_negotiate_resp(icmsghdr, desc + offset, pktlen, fw_versions, + FW_VER_COUNT, fcopy_versions, FCOPY_VER_COUNT, + NULL, &fcopy_srv_version)) { + syslog(LOG_INFO, "FCopy IC version %d.%d", + fcopy_srv_version >> 16, fcopy_srv_version & 0xFFFF); + icmsghdr->status = 0; + } + } else if (icmsghdr->icmsgtype == ICMSGTYPE_FCOPY) { + /* Ensure recvlen is big enough to contain hv_fcopy_hdr */ + if (pktlen < ICMSG_HDR + sizeof(struct hv_fcopy_hdr)) { + syslog(LOG_ERR, "Invalid Fcopy hdr. Packet length too small: %u", + pktlen); + return -ENOBUFS; + } + + fcopy_msg = (struct hv_fcopy_hdr *)&desc[offset + ICMSG_HDR]; + icmsghdr->status = hv_fcopy_send_data(fcopy_msg, pktlen); + } + + icmsghdr->icflags = ICMSGHDRFLAG_TRANSACTION | ICMSGHDRFLAG_RESPONSE; + ret = rte_vmbus_chan_send(txbr, 0x6, desc + offset, pktlen, 0); + if (ret) { + syslog(LOG_ERR, "Write to ringbuffer failed err: %d", ret); + return ret; + } + + return 0; +} + +static void fcopy_get_first_folder(char *path, char *chan_no) +{ + DIR *dir = opendir(path); + struct dirent *entry; + + if (!dir) { + syslog(LOG_ERR, "Failed to open directory (errno=%s).\n", strerror(errno)); + return; + } + + while ((entry = readdir(dir)) != NULL) { + if (entry->d_type == DT_DIR && strcmp(entry->d_name, ".") != 0 && + strcmp(entry->d_name, "..") != 0) { + strcpy(chan_no, entry->d_name); + break; + } + } + + closedir(dir); +} + +int main(int argc, char *argv[]) +{ + int fcopy_fd = -1, tmp = 1; + int daemonize = 1, long_index = 0, opt, ret = -EINVAL; + struct vmbus_br txbr, rxbr; + void *ring; + uint32_t len = HV_RING_SIZE; + char uio_name[MAX_FOLDER_NAME] = {0}; + char uio_dev_path[MAX_PATH_LEN] = {0}; + + static struct option long_options[] = { + {"help", no_argument, 0, 'h' }, + {"no-daemon", no_argument, 0, 'n' }, + {0, 0, 0, 0 } + }; + + while ((opt = getopt_long(argc, argv, "hn", long_options, + &long_index)) != -1) { + switch (opt) { + case 'n': + daemonize = 0; + break; + case 'h': + default: + print_usage(argv); + goto exit; + } + } + + if (daemonize && daemon(1, 0)) { + syslog(LOG_ERR, "daemon() failed; error: %s", strerror(errno)); + goto exit; + } + + openlog("HV_UIO_FCOPY", 0, LOG_USER); + syslog(LOG_INFO, "starting; pid is:%d", getpid()); + + fcopy_get_first_folder(FCOPY_UIO, uio_name); + snprintf(uio_dev_path, sizeof(uio_dev_path), "/dev/%s", uio_name); + fcopy_fd = open(uio_dev_path, O_RDWR); + + if (fcopy_fd < 0) { + syslog(LOG_ERR, "open %s failed; error: %d %s", + uio_dev_path, errno, strerror(errno)); + ret = fcopy_fd; + goto exit; + } + + ring = vmbus_uio_map(&fcopy_fd, HV_RING_SIZE); + if (!ring) { + ret = errno; + syslog(LOG_ERR, "mmap ringbuffer failed; error: %d %s", ret, strerror(ret)); + goto close; + } + vmbus_br_setup(&txbr, ring, HV_RING_SIZE); + vmbus_br_setup(&rxbr, (char *)ring + HV_RING_SIZE, HV_RING_SIZE); + + rxbr.vbr->imask = 0; + + while (1) { + /* + * In this loop we process fcopy messages after the + * handshake is complete. + */ + ret = pread(fcopy_fd, &tmp, sizeof(int), 0); + if (ret < 0) { + syslog(LOG_ERR, "pread failed: %s", strerror(errno)); + continue; + } + + len = HV_RING_SIZE; + ret = rte_vmbus_chan_recv_raw(&rxbr, desc, &len); + if (unlikely(ret <= 0)) { + /* This indicates a failure to communicate (or worse) */ + syslog(LOG_ERR, "VMBus channel recv error: %d", ret); + } else { + ret = fcopy_pkt_process(&txbr); + if (ret < 0) + goto close; + + /* Signal host */ + if ((write(fcopy_fd, &tmp, sizeof(int))) != sizeof(int)) { + ret = errno; + syslog(LOG_ERR, "Signal to host failed: %s\n", strerror(ret)); + goto close; + } + } + } +close: + close(fcopy_fd); +exit: + return ret; +} diff --git a/hyper-v.tools.hv.hv_kvp_daemon.c b/hyper-v.tools.hv.hv_kvp_daemon.c index 3e07295..a41b510 100644 --- a/hyper-v.tools.hv.hv_kvp_daemon.c +++ b/hyper-v.tools.hv.hv_kvp_daemon.c @@ -44,7 +44,7 @@ /* * KVP protocol: The user mode component first registers with the - * the kernel component. Subsequently, the kernel component requests, data + * kernel component. Subsequently, the kernel component requests, data * for the specified keys. In response to this message the user mode component * fills in the value corresponding to the specified key. We overload the * sequence field in the cn_msg header to define our KVP message types. @@ -76,6 +76,12 @@ enum { DNS }; +enum { + IPV4 = 1, + IPV6, + IP_TYPE_MAX +}; + static int in_hand_shake; static char *os_name = ""; @@ -102,6 +108,11 @@ static struct utsname uts_buf; #define MAX_FILE_NAME 100 #define ENTRIES_PER_BLOCK 50 +/* + * Change this entry if the number of addresses increases in future + */ +#define MAX_IP_ENTRIES 64 +#define OUTSTR_BUF_SIZE ((INET6_ADDRSTRLEN + 1) * MAX_IP_ENTRIES) struct kvp_record { char key[HV_KVP_EXCHANGE_MAX_KEY_SIZE]; @@ -437,7 +448,7 @@ void kvp_get_os_info(void) /* * Parse the /etc/os-release file if present: - * http://www.freedesktop.org/software/systemd/man/os-release.html + * https://www.freedesktop.org/software/systemd/man/os-release.html */ file = fopen("/etc/os-release", "r"); if (file != NULL) { @@ -772,11 +783,11 @@ static int kvp_process_ip_address(void *addrp, const char *str; if (family == AF_INET) { - addr = (struct sockaddr_in *)addrp; + addr = addrp; str = inet_ntop(family, &addr->sin_addr, tmp, 50); addr_length = INET_ADDRSTRLEN; } else { - addr6 = (struct sockaddr_in6 *)addrp; + addr6 = addrp; str = inet_ntop(family, &addr6->sin6_addr.s6_addr, tmp, 50); addr_length = INET6_ADDRSTRLEN; } @@ -1171,11 +1182,156 @@ static int process_ip_string(FILE *f, char *ip_string, int type) return 0; } +int ip_version_check(const char *input_addr) +{ + struct in6_addr addr; + + if (inet_pton(AF_INET, input_addr, &addr)) + return IPV4; + else if (inet_pton(AF_INET6, input_addr, &addr)) + return IPV6; + + return -EINVAL; +} + +/* + * Only IPv4 subnet strings needs to be converted to plen + * For IPv6 the subnet is already privided in plen format + */ +static int kvp_subnet_to_plen(char *subnet_addr_str) +{ + int plen = 0; + struct in_addr subnet_addr4; + + /* + * Convert subnet address to binary representation + */ + if (inet_pton(AF_INET, subnet_addr_str, &subnet_addr4) == 1) { + uint32_t subnet_mask = ntohl(subnet_addr4.s_addr); + + while (subnet_mask & 0x80000000) { + plen++; + subnet_mask <<= 1; + } + } else { + return -1; + } + + return plen; +} + +static int process_dns_gateway_nm(FILE *f, char *ip_string, int type, + int ip_sec) +{ + char addr[INET6_ADDRSTRLEN], *output_str; + int ip_offset = 0, error = 0, ip_ver; + char *param_name; + + if (type == DNS) + param_name = "dns"; + else if (type == GATEWAY) + param_name = "gateway"; + else + return -EINVAL; + + output_str = (char *)calloc(OUTSTR_BUF_SIZE, sizeof(char)); + if (!output_str) + return -ENOMEM; + + while (1) { + memset(addr, 0, sizeof(addr)); + + if (!parse_ip_val_buffer(ip_string, &ip_offset, addr, + (MAX_IP_ADDR_SIZE * 2))) + break; + + ip_ver = ip_version_check(addr); + if (ip_ver < 0) + continue; + + if ((ip_ver == IPV4 && ip_sec == IPV4) || + (ip_ver == IPV6 && ip_sec == IPV6)) { + /* + * do a bound check to avoid out-of bound writes + */ + if ((OUTSTR_BUF_SIZE - strlen(output_str)) > + (strlen(addr) + 1)) { + strncat(output_str, addr, + OUTSTR_BUF_SIZE - + strlen(output_str) - 1); + strncat(output_str, ",", + OUTSTR_BUF_SIZE - + strlen(output_str) - 1); + } + } else { + continue; + } + } + + if (strlen(output_str)) { + /* + * This is to get rid of that extra comma character + * in the end of the string + */ + output_str[strlen(output_str) - 1] = '\0'; + error = fprintf(f, "%s=%s\n", param_name, output_str); + } + + free(output_str); + return error; +} + +static int process_ip_string_nm(FILE *f, char *ip_string, char *subnet, + int ip_sec) +{ + char addr[INET6_ADDRSTRLEN]; + char subnet_addr[INET6_ADDRSTRLEN]; + int error = 0, i = 0; + int ip_offset = 0, subnet_offset = 0; + int plen, ip_ver; + + memset(addr, 0, sizeof(addr)); + memset(subnet_addr, 0, sizeof(subnet_addr)); + + while (parse_ip_val_buffer(ip_string, &ip_offset, addr, + (MAX_IP_ADDR_SIZE * 2)) && + parse_ip_val_buffer(subnet, + &subnet_offset, + subnet_addr, + (MAX_IP_ADDR_SIZE * + 2))) { + ip_ver = ip_version_check(addr); + if (ip_ver < 0) + continue; + + if (ip_ver == IPV4 && ip_sec == IPV4) + plen = kvp_subnet_to_plen((char *)subnet_addr); + else if (ip_ver == IPV6 && ip_sec == IPV6) + plen = atoi(subnet_addr); + else + continue; + + if (plen < 0) + return plen; + + error = fprintf(f, "address%d=%s/%d\n", ++i, (char *)addr, + plen); + if (error < 0) + return error; + + memset(addr, 0, sizeof(addr)); + memset(subnet_addr, 0, sizeof(subnet_addr)); + } + + return error; +} + static int kvp_set_ip_info(char *if_name, struct hv_kvp_ipaddr_value *new_val) { - int error = 0; - char if_file[PATH_MAX]; - FILE *file; + int error = 0, ip_ver; + char if_filename[PATH_MAX]; + char nm_filename[PATH_MAX]; + FILE *ifcfg_file, *nmfile; char cmd[PATH_MAX]; char *mac_addr; int str_len; @@ -1197,7 +1353,7 @@ static int kvp_set_ip_info(char *if_name, struct hv_kvp_ipaddr_value *new_val) * in a given distro to configure the interface and so are free * ignore information that may not be relevant. * - * Here is the format of the ip configuration file: + * Here is the ifcfg format of the ip configuration file: * * HWADDR=macaddr * DEVICE=interface name @@ -1220,6 +1376,32 @@ static int kvp_set_ip_info(char *if_name, struct hv_kvp_ipaddr_value *new_val) * tagged as IPV6_DEFAULTGW and IPV6 NETMASK will be tagged as * IPV6NETMASK. * + * Here is the keyfile format of the ip configuration file: + * + * [ethernet] + * mac-address=macaddr + * [connection] + * interface-name=interface name + * + * [ipv4] + * method= (where is "auto" if DHCP is configured + * or "manual" if no boot-time protocol should be used) + * + * address1=ipaddr1/plen + * address2=ipaddr2/plen + * + * gateway=gateway1;gateway2 + * + * dns=dns1;dns2 + * + * [ipv6] + * address1=ipaddr1/plen + * address2=ipaddr2/plen + * + * gateway=gateway1;gateway2 + * + * dns=dns1;dns2 + * * The host can specify multiple ipv4 and ipv6 addresses to be * configured for the interface. Furthermore, the configuration * needs to be persistent. A subsequent GET call on the interface @@ -1227,14 +1409,29 @@ static int kvp_set_ip_info(char *if_name, struct hv_kvp_ipaddr_value *new_val) * call. */ - snprintf(if_file, sizeof(if_file), "%s%s%s", KVP_CONFIG_LOC, - "/ifcfg-", if_name); + /* + * We are populating both ifcfg and nmconnection files + */ + snprintf(if_filename, sizeof(if_filename), "%s%s%s", KVP_CONFIG_LOC, + "/ifcfg-", if_name); - file = fopen(if_file, "w"); + ifcfg_file = fopen(if_filename, "w"); - if (file == NULL) { + if (!ifcfg_file) { syslog(LOG_ERR, "Failed to open config file; error: %d %s", - errno, strerror(errno)); + errno, strerror(errno)); + return HV_E_FAIL; + } + + snprintf(nm_filename, sizeof(nm_filename), "%s%s%s%s", KVP_CONFIG_LOC, + "/", if_name, ".nmconnection"); + + nmfile = fopen(nm_filename, "w"); + + if (!nmfile) { + syslog(LOG_ERR, "Failed to open config file; error: %d %s", + errno, strerror(errno)); + fclose(ifcfg_file); return HV_E_FAIL; } @@ -1248,14 +1445,31 @@ static int kvp_set_ip_info(char *if_name, struct hv_kvp_ipaddr_value *new_val) goto setval_error; } - error = kvp_write_file(file, "HWADDR", "", mac_addr); - free(mac_addr); - if (error) - goto setval_error; + error = kvp_write_file(ifcfg_file, "HWADDR", "", mac_addr); + if (error < 0) + goto setmac_error; - error = kvp_write_file(file, "DEVICE", "", if_name); + error = kvp_write_file(ifcfg_file, "DEVICE", "", if_name); + if (error < 0) + goto setmac_error; + + error = fprintf(nmfile, "\n[connection]\n"); + if (error < 0) + goto setmac_error; + + error = kvp_write_file(nmfile, "interface-name", "", if_name); if (error) - goto setval_error; + goto setmac_error; + + error = fprintf(nmfile, "\n[ethernet]\n"); + if (error < 0) + goto setmac_error; + + error = kvp_write_file(nmfile, "mac-address", "", mac_addr); + if (error) + goto setmac_error; + + free(mac_addr); /* * The dhcp_enabled flag is only for IPv4. In the case the host only @@ -1263,47 +1477,137 @@ static int kvp_set_ip_info(char *if_name, struct hv_kvp_ipaddr_value *new_val) * proceed to parse and pass the IPv6 information to the * disto-specific script hv_set_ifconfig. */ + + /* + * First populate the ifcfg file format + */ if (new_val->dhcp_enabled) { - error = kvp_write_file(file, "BOOTPROTO", "", "dhcp"); + error = kvp_write_file(ifcfg_file, "BOOTPROTO", "", "dhcp"); if (error) goto setval_error; - } else { - error = kvp_write_file(file, "BOOTPROTO", "", "none"); + error = kvp_write_file(ifcfg_file, "BOOTPROTO", "", "none"); if (error) goto setval_error; } + error = process_ip_string(ifcfg_file, (char *)new_val->ip_addr, + IPADDR); + if (error) + goto setval_error; + + error = process_ip_string(ifcfg_file, (char *)new_val->sub_net, + NETMASK); + if (error) + goto setval_error; + + error = process_ip_string(ifcfg_file, (char *)new_val->gate_way, + GATEWAY); + if (error) + goto setval_error; + + error = process_ip_string(ifcfg_file, (char *)new_val->dns_addr, DNS); + if (error) + goto setval_error; + /* - * Write the configuration for ipaddress, netmask, gateway and - * name servers. + * Now we populate the keyfile format + * + * The keyfile format expects the IPv6 and IPv4 configuration in + * different sections. Therefore we iterate through the list twice, + * once to populate the IPv4 section and the next time for IPv6 */ + ip_ver = IPV4; + do { + if (ip_ver == IPV4) { + error = fprintf(nmfile, "\n[ipv4]\n"); + if (error < 0) + goto setval_error; + } else { + error = fprintf(nmfile, "\n[ipv6]\n"); + if (error < 0) + goto setval_error; + } - error = process_ip_string(file, (char *)new_val->ip_addr, IPADDR); - if (error) - goto setval_error; + /* + * Write the configuration for ipaddress, netmask, gateway and + * name services + */ + error = process_ip_string_nm(nmfile, (char *)new_val->ip_addr, + (char *)new_val->sub_net, + ip_ver); + if (error < 0) + goto setval_error; - error = process_ip_string(file, (char *)new_val->sub_net, NETMASK); - if (error) - goto setval_error; + /* + * As dhcp_enabled is only valid for ipv4, we do not set dhcp + * methods for ipv6 based on dhcp_enabled flag. + * + * For ipv4, set method to manual only when dhcp_enabled is + * false and specific ipv4 addresses are configured. If neither + * dhcp_enabled is true and no ipv4 addresses are configured, + * set method to 'disabled'. + * + * For ipv6, set method to manual when we configure ipv6 + * addresses. Otherwise set method to 'auto' so that SLAAC from + * RA may be used. + */ + if (ip_ver == IPV4) { + if (new_val->dhcp_enabled) { + error = kvp_write_file(nmfile, "method", "", + "auto"); + if (error < 0) + goto setval_error; + } else if (error) { + error = kvp_write_file(nmfile, "method", "", + "manual"); + if (error < 0) + goto setval_error; + } else { + error = kvp_write_file(nmfile, "method", "", + "disabled"); + if (error < 0) + goto setval_error; + } + } else if (ip_ver == IPV6) { + if (error) { + error = kvp_write_file(nmfile, "method", "", + "manual"); + if (error < 0) + goto setval_error; + } else { + error = kvp_write_file(nmfile, "method", "", + "auto"); + if (error < 0) + goto setval_error; + } + } - error = process_ip_string(file, (char *)new_val->gate_way, GATEWAY); - if (error) - goto setval_error; + error = process_dns_gateway_nm(nmfile, + (char *)new_val->gate_way, + GATEWAY, ip_ver); + if (error < 0) + goto setval_error; - error = process_ip_string(file, (char *)new_val->dns_addr, DNS); - if (error) - goto setval_error; + error = process_dns_gateway_nm(nmfile, + (char *)new_val->dns_addr, DNS, + ip_ver); + if (error < 0) + goto setval_error; - fclose(file); + ip_ver++; + } while (ip_ver < IP_TYPE_MAX); + + fclose(nmfile); + fclose(ifcfg_file); /* * Now that we have populated the configuration file, * invoke the external script to do its magic. */ - str_len = snprintf(cmd, sizeof(cmd), KVP_SCRIPTS_PATH "%s %s", - "hv_set_ifconfig", if_file); + str_len = snprintf(cmd, sizeof(cmd), KVP_SCRIPTS_PATH "%s %s %s", + "hv_set_ifconfig", if_filename, nm_filename); /* * This is a little overcautious, but it's necessary to suppress some * false warnings from gcc 8.0.1. @@ -1321,10 +1625,13 @@ static int kvp_set_ip_info(char *if_name, struct hv_kvp_ipaddr_value *new_val) } return 0; +setmac_error: + free(mac_addr); setval_error: syslog(LOG_ERR, "Failed to write config file. error: %d %s", errno, strerror(errno)); - fclose(file); + fclose(ifcfg_file); + fclose(nmfile); return error; } diff --git a/hyper-v.tools.hv.hv_set_ifconfig.sh b/hyper-v.tools.hv.hv_set_ifconfig.sh index ae861d3..11efe3d 100644 --- a/hyper-v.tools.hv.hv_set_ifconfig.sh +++ b/hyper-v.tools.hv.hv_set_ifconfig.sh @@ -7,12 +7,12 @@ # The only argument to this script is the configuration file that is to # be used to configure the interface. # -# Here is the format of the ip configuration file: +# Here is the ifcfg format of the ip configuration file: # # HWADDR=macaddr # DEVICE=interface name # BOOTPROTO= (where is "dhcp" if DHCP is configured -# or "none" if no boot-time protocol should be used) +# or "none" if no boot-time protocol should be used) # # IPADDR0=ipaddr1 # IPADDR1=ipaddr2 @@ -30,14 +30,41 @@ # tagged as IPV6_DEFAULTGW and IPV6 NETMASK will be tagged as # IPV6NETMASK. # +# Here is the keyfile format of the ip configuration file: +# +# [ethernet] +# mac-address=macaddr +# [connection] +# interface-name=interface name +# +# [ipv4] +# method= (where is "auto" if DHCP is configured +# or "manual" if no boot-time protocol should be used) +# +# address1=ipaddr1/plen +# address2=ipaddr2/plen +# +# gateway=gateway1;gateway2 +# +# dns=dns1; +# +# [ipv6] +# address1=ipaddr1/plen +# address2=ipaddr2/plen +# +# gateway=gateway1;gateway2 +# +# dns=dns1;dns2 +# # The host can specify multiple ipv4 and ipv6 addresses to be # configured for the interface. Furthermore, the configuration # needs to be persistent. A subsequent GET call on the interface # is expected to return the configuration that is set via the SET # call. # -cfg=$1 -if ! test -f "${cfg}" +if_cfg=$1 +nm_cfg=$2 +if ! test -f "${if_cfg}" then : expect configuration datafile as first argument exit 1 @@ -86,11 +113,11 @@ unset ${!IPV6ADDR*} unset ${!IPV6NETMASK*} unset ${!IPV6_DEFAULTGW*} unset ${!DNS*} -. "$1" +. "${if_cfg}" # if test -z "${DEVICE}" then - echo "Missing DEVICE= in ${cfg}" + echo "Missing DEVICE= in ${if_cfg}" exit 1 fi # @@ -200,6 +227,7 @@ do done # echo "$0: working on network interface ifcfg-${DEVICE}" +mkdir -vp "/etc/sysconfig/network" cp -fb ${t_ifcfg} "/etc/sysconfig/network/ifcfg-${DEVICE}" cp -fb ${t_ifroute} "/etc/sysconfig/network/ifroute-${DEVICE}" if test -w /etc/sysconfig/network/config @@ -207,6 +235,13 @@ then sed -i "s@^NETCONFIG_DNS_STATIC_SERVERS=.*@NETCONFIG_DNS_STATIC_SERVERS='$_DNS_'@" /etc/sysconfig/network/config netconfig update -m dns fi + +echo "$0: working on network interface ifcfg-${DEVICE}" +nm_filename="${nm_cfg##*/}" +mkdir -vp "/etc/NetworkManager/system-connections" +umask 0177 +sed '/\[connection\]/a autoconnect=true' "${nm_cfg}" > "/etc/NetworkManager/system-connections/${nm_filename}" + ifdown "${DEVICE}" ifup "${DEVICE}" ) 2>&1 | logger -t "${0##*/}[$PPID / $$]" diff --git a/hyper-v.tools.hv.vmbus_bufring.c b/hyper-v.tools.hv.vmbus_bufring.c new file mode 100644 index 0000000..faf5945 --- /dev/null +++ b/hyper-v.tools.hv.vmbus_bufring.c @@ -0,0 +1,318 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* + * Copyright (c) 2009-2012,2016,2023 Microsoft Corp. + * Copyright (c) 2012 NetApp Inc. + * Copyright (c) 2012 Citrix Inc. + * All rights reserved. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "vmbus_bufring.h" + +/** + * Compiler barrier. + * + * Guarantees that operation reordering does not occur at compile time + * for operations directly before and after the barrier. + */ +#define rte_compiler_barrier() ({ asm volatile ("" : : : "memory"); }) + +#define VMBUS_RQST_ERROR 0xFFFFFFFFFFFFFFFF +#define ALIGN(val, align) ((typeof(val))((val) & (~((typeof(val))((align) - 1))))) + +void *vmbus_uio_map(int *fd, int size) +{ + void *map; + + map = mmap(NULL, 2 * size, PROT_READ | PROT_WRITE, MAP_SHARED, *fd, 0); + if (map == MAP_FAILED) + return NULL; + + return map; +} + +/* Increase bufring index by inc with wraparound */ +static inline uint32_t vmbus_br_idxinc(uint32_t idx, uint32_t inc, uint32_t sz) +{ + idx += inc; + if (idx >= sz) + idx -= sz; + + return idx; +} + +void vmbus_br_setup(struct vmbus_br *br, void *buf, unsigned int blen) +{ + br->vbr = buf; + br->windex = br->vbr->windex; + br->dsize = blen - sizeof(struct vmbus_bufring); +} + +static inline __always_inline void +rte_smp_mb(void) +{ + asm volatile("lock addl $0, -128(%%rsp); " ::: "memory"); +} + +static inline int +rte_atomic32_cmpset(volatile uint32_t *dst, uint32_t exp, uint32_t src) +{ + uint8_t res; + + asm volatile("lock ; " + "cmpxchgl %[src], %[dst];" + "sete %[res];" + : [res] "=a" (res), /* output */ + [dst] "=m" (*dst) + : [src] "r" (src), /* input */ + "a" (exp), + "m" (*dst) + : "memory"); /* no-clobber list */ + return res; +} + +static inline uint32_t +vmbus_txbr_copyto(const struct vmbus_br *tbr, uint32_t windex, + const void *src0, uint32_t cplen) +{ + uint8_t *br_data = tbr->vbr->data; + uint32_t br_dsize = tbr->dsize; + const uint8_t *src = src0; + + /* XXX use double mapping like Linux kernel? */ + if (cplen > br_dsize - windex) { + uint32_t fraglen = br_dsize - windex; + + /* Wrap-around detected */ + memcpy(br_data + windex, src, fraglen); + memcpy(br_data, src + fraglen, cplen - fraglen); + } else { + memcpy(br_data + windex, src, cplen); + } + + return vmbus_br_idxinc(windex, cplen, br_dsize); +} + +/* + * Write scattered channel packet to TX bufring. + * + * The offset of this channel packet is written as a 64bits value + * immediately after this channel packet. + * + * The write goes through three stages: + * 1. Reserve space in ring buffer for the new data. + * Writer atomically moves priv_write_index. + * 2. Copy the new data into the ring. + * 3. Update the tail of the ring (visible to host) that indicates + * next read location. Writer updates write_index + */ +static int +vmbus_txbr_write(struct vmbus_br *tbr, const struct iovec iov[], int iovlen) +{ + struct vmbus_bufring *vbr = tbr->vbr; + uint32_t ring_size = tbr->dsize; + uint32_t old_windex, next_windex, windex, total; + uint64_t save_windex; + int i; + + total = 0; + for (i = 0; i < iovlen; i++) + total += iov[i].iov_len; + total += sizeof(save_windex); + + /* Reserve space in ring */ + do { + uint32_t avail; + + /* Get current free location */ + old_windex = tbr->windex; + + /* Prevent compiler reordering this with calculation */ + rte_compiler_barrier(); + + avail = vmbus_br_availwrite(tbr, old_windex); + + /* If not enough space in ring, then tell caller. */ + if (avail <= total) + return -EAGAIN; + + next_windex = vmbus_br_idxinc(old_windex, total, ring_size); + + /* Atomic update of next write_index for other threads */ + } while (!rte_atomic32_cmpset(&tbr->windex, old_windex, next_windex)); + + /* Space from old..new is now reserved */ + windex = old_windex; + for (i = 0; i < iovlen; i++) + windex = vmbus_txbr_copyto(tbr, windex, iov[i].iov_base, iov[i].iov_len); + + /* Set the offset of the current channel packet. */ + save_windex = ((uint64_t)old_windex) << 32; + windex = vmbus_txbr_copyto(tbr, windex, &save_windex, + sizeof(save_windex)); + + /* The region reserved should match region used */ + if (windex != next_windex) + return -EINVAL; + + /* Ensure that data is available before updating host index */ + rte_compiler_barrier(); + + /* Checkin for our reservation. wait for our turn to update host */ + while (!rte_atomic32_cmpset(&vbr->windex, old_windex, next_windex)) + _mm_pause(); + + return 0; +} + +int rte_vmbus_chan_send(struct vmbus_br *txbr, uint16_t type, void *data, + uint32_t dlen, uint32_t flags) +{ + struct vmbus_chanpkt pkt; + unsigned int pktlen, pad_pktlen; + const uint32_t hlen = sizeof(pkt); + uint64_t pad = 0; + struct iovec iov[3]; + int error; + + pktlen = hlen + dlen; + pad_pktlen = ALIGN(pktlen, sizeof(uint64_t)); + + pkt.hdr.type = type; + pkt.hdr.flags = flags; + pkt.hdr.hlen = hlen >> VMBUS_CHANPKT_SIZE_SHIFT; + pkt.hdr.tlen = pad_pktlen >> VMBUS_CHANPKT_SIZE_SHIFT; + pkt.hdr.xactid = VMBUS_RQST_ERROR; + + iov[0].iov_base = &pkt; + iov[0].iov_len = hlen; + iov[1].iov_base = data; + iov[1].iov_len = dlen; + iov[2].iov_base = &pad; + iov[2].iov_len = pad_pktlen - pktlen; + + error = vmbus_txbr_write(txbr, iov, 3); + + return error; +} + +static inline uint32_t +vmbus_rxbr_copyfrom(const struct vmbus_br *rbr, uint32_t rindex, + void *dst0, size_t cplen) +{ + const uint8_t *br_data = rbr->vbr->data; + uint32_t br_dsize = rbr->dsize; + uint8_t *dst = dst0; + + if (cplen > br_dsize - rindex) { + uint32_t fraglen = br_dsize - rindex; + + /* Wrap-around detected. */ + memcpy(dst, br_data + rindex, fraglen); + memcpy(dst + fraglen, br_data, cplen - fraglen); + } else { + memcpy(dst, br_data + rindex, cplen); + } + + return vmbus_br_idxinc(rindex, cplen, br_dsize); +} + +/* Copy data from receive ring but don't change index */ +static int +vmbus_rxbr_peek(const struct vmbus_br *rbr, void *data, size_t dlen) +{ + uint32_t avail; + + /* + * The requested data and the 64bits channel packet + * offset should be there at least. + */ + avail = vmbus_br_availread(rbr); + if (avail < dlen + sizeof(uint64_t)) + return -EAGAIN; + + vmbus_rxbr_copyfrom(rbr, rbr->vbr->rindex, data, dlen); + return 0; +} + +/* + * Copy data from receive ring and change index + * NOTE: + * We assume (dlen + skip) == sizeof(channel packet). + */ +static int +vmbus_rxbr_read(struct vmbus_br *rbr, void *data, size_t dlen, size_t skip) +{ + struct vmbus_bufring *vbr = rbr->vbr; + uint32_t br_dsize = rbr->dsize; + uint32_t rindex; + + if (vmbus_br_availread(rbr) < dlen + skip + sizeof(uint64_t)) + return -EAGAIN; + + /* Record where host was when we started read (for debug) */ + rbr->windex = rbr->vbr->windex; + + /* + * Copy channel packet from RX bufring. + */ + rindex = vmbus_br_idxinc(rbr->vbr->rindex, skip, br_dsize); + rindex = vmbus_rxbr_copyfrom(rbr, rindex, data, dlen); + + /* + * Discard this channel packet's 64bits offset, which is useless to us. + */ + rindex = vmbus_br_idxinc(rindex, sizeof(uint64_t), br_dsize); + + /* Update the read index _after_ the channel packet is fetched. */ + rte_compiler_barrier(); + + vbr->rindex = rindex; + + return 0; +} + +int rte_vmbus_chan_recv_raw(struct vmbus_br *rxbr, + void *data, uint32_t *len) +{ + struct vmbus_chanpkt_hdr pkt; + uint32_t dlen, bufferlen = *len; + int error; + + error = vmbus_rxbr_peek(rxbr, &pkt, sizeof(pkt)); + if (error) + return error; + + if (unlikely(pkt.hlen < VMBUS_CHANPKT_HLEN_MIN)) + /* XXX this channel is dead actually. */ + return -EIO; + + if (unlikely(pkt.hlen > pkt.tlen)) + return -EIO; + + /* Length are in quad words */ + dlen = pkt.tlen << VMBUS_CHANPKT_SIZE_SHIFT; + *len = dlen; + + /* If caller buffer is not large enough */ + if (unlikely(dlen > bufferlen)) + return -ENOBUFS; + + /* Read data and skip packet header */ + error = vmbus_rxbr_read(rxbr, data, dlen, 0); + if (error) + return error; + + /* Return the number of bytes read */ + return dlen + sizeof(uint64_t); +} diff --git a/hyper-v.tools.hv.vmbus_bufring.h b/hyper-v.tools.hv.vmbus_bufring.h new file mode 100644 index 0000000..ff6673b --- /dev/null +++ b/hyper-v.tools.hv.vmbus_bufring.h @@ -0,0 +1,158 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ + +#ifndef _VMBUS_BUF_H_ +#define _VMBUS_BUF_H_ + +#include +#include + +#define __packed __attribute__((__packed__)) +#define unlikely(x) __builtin_expect(!!(x), 0) + +#define ICMSGHDRFLAG_TRANSACTION 1 +#define ICMSGHDRFLAG_REQUEST 2 +#define ICMSGHDRFLAG_RESPONSE 4 + +#define IC_VERSION_NEGOTIATION_MAX_VER_COUNT 100 +#define ICMSG_HDR (sizeof(struct vmbuspipe_hdr) + sizeof(struct icmsg_hdr)) +#define ICMSG_NEGOTIATE_PKT_SIZE(icframe_vercnt, icmsg_vercnt) \ + (ICMSG_HDR + sizeof(struct icmsg_negotiate) + \ + (((icframe_vercnt) + (icmsg_vercnt)) * sizeof(struct ic_version))) + +/* + * Channel packets + */ + +/* Channel packet flags */ +#define VMBUS_CHANPKT_TYPE_INBAND 0x0006 +#define VMBUS_CHANPKT_TYPE_RXBUF 0x0007 +#define VMBUS_CHANPKT_TYPE_GPA 0x0009 +#define VMBUS_CHANPKT_TYPE_COMP 0x000b + +#define VMBUS_CHANPKT_FLAG_NONE 0 +#define VMBUS_CHANPKT_FLAG_RC 0x0001 /* report completion */ + +#define VMBUS_CHANPKT_SIZE_SHIFT 3 +#define VMBUS_CHANPKT_SIZE_ALIGN BIT(VMBUS_CHANPKT_SIZE_SHIFT) +#define VMBUS_CHANPKT_HLEN_MIN \ + (sizeof(struct vmbus_chanpkt_hdr) >> VMBUS_CHANPKT_SIZE_SHIFT) + +/* + * Buffer ring + */ +struct vmbus_bufring { + volatile uint32_t windex; + volatile uint32_t rindex; + + /* + * Interrupt mask {0,1} + * + * For TX bufring, host set this to 1, when it is processing + * the TX bufring, so that we can safely skip the TX event + * notification to host. + * + * For RX bufring, once this is set to 1 by us, host will not + * further dispatch interrupts to us, even if there are data + * pending on the RX bufring. This effectively disables the + * interrupt of the channel to which this RX bufring is attached. + */ + volatile uint32_t imask; + + /* + * Win8 uses some of the reserved bits to implement + * interrupt driven flow management. On the send side + * we can request that the receiver interrupt the sender + * when the ring transitions from being full to being able + * to handle a message of size "pending_send_sz". + * + * Add necessary state for this enhancement. + */ + volatile uint32_t pending_send; + uint32_t reserved1[12]; + + union { + struct { + uint32_t feat_pending_send_sz:1; + }; + uint32_t value; + } feature_bits; + + /* Pad it to rte_mem_page_size() so that data starts on page boundary */ + uint8_t reserved2[4028]; + + /* + * Ring data starts here + RingDataStartOffset + * !!! DO NOT place any fields below this !!! + */ + uint8_t data[]; +} __packed; + +struct vmbus_br { + struct vmbus_bufring *vbr; + uint32_t dsize; + uint32_t windex; /* next available location */ +}; + +struct vmbus_chanpkt_hdr { + uint16_t type; /* VMBUS_CHANPKT_TYPE_ */ + uint16_t hlen; /* header len, in 8 bytes */ + uint16_t tlen; /* total len, in 8 bytes */ + uint16_t flags; /* VMBUS_CHANPKT_FLAG_ */ + uint64_t xactid; +} __packed; + +struct vmbus_chanpkt { + struct vmbus_chanpkt_hdr hdr; +} __packed; + +struct vmbuspipe_hdr { + unsigned int flags; + unsigned int msgsize; +} __packed; + +struct ic_version { + unsigned short major; + unsigned short minor; +} __packed; + +struct icmsg_negotiate { + unsigned short icframe_vercnt; + unsigned short icmsg_vercnt; + unsigned int reserved; + struct ic_version icversion_data[]; /* any size array */ +} __packed; + +struct icmsg_hdr { + struct ic_version icverframe; + unsigned short icmsgtype; + struct ic_version icvermsg; + unsigned short icmsgsize; + unsigned int status; + unsigned char ictransaction_id; + unsigned char icflags; + unsigned char reserved[2]; +} __packed; + +int rte_vmbus_chan_recv_raw(struct vmbus_br *rxbr, void *data, uint32_t *len); +int rte_vmbus_chan_send(struct vmbus_br *txbr, uint16_t type, void *data, + uint32_t dlen, uint32_t flags); +void vmbus_br_setup(struct vmbus_br *br, void *buf, unsigned int blen); +void *vmbus_uio_map(int *fd, int size); + +/* Amount of space available for write */ +static inline uint32_t vmbus_br_availwrite(const struct vmbus_br *br, uint32_t windex) +{ + uint32_t rindex = br->vbr->rindex; + + if (windex >= rindex) + return br->dsize - (windex - rindex); + else + return rindex - windex; +} + +static inline uint32_t vmbus_br_availread(const struct vmbus_br *br) +{ + return br->dsize - vmbus_br_availwrite(br, br->vbr->windex); +} + +#endif /* !_VMBUS_BUF_H_ */