/* Copyright (c) 2013-2016, 2019 The Linux Foundation. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 and * only version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that 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. * * RMNET Data MAP protocol * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "rmnet_data_config.h" #include "rmnet_map.h" #include "rmnet_data_private.h" #include "rmnet_data_stats.h" #include "rmnet_data_trace.h" RMNET_LOG_MODULE(RMNET_DATA_LOGMASK_MAPD); /* ***************** Local Definitions ************************************** */ long agg_time_limit __read_mostly = 1000000L; module_param(agg_time_limit, long, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(agg_time_limit, "Maximum time packets sit in the agg buf"); long agg_bypass_time __read_mostly = 10000000L; module_param(agg_bypass_time, long, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(agg_bypass_time, "Skip agg when apart spaced more than this"); struct agg_work { struct delayed_work work; struct rmnet_phys_ep_config *config; }; #define RMNET_MAP_DEAGGR_HEADROOM (192) #define RMNET_MAP_ETHERNET_HEADER (32) #define RMNET_MAP_DEAGGR_SPACING (RMNET_MAP_ETHERNET_HEADER + RMNET_MAP_DEAGGR_HEADROOM) /******************************************************************************/ /** * rmnet_data_map_add_map_header() - Adds MAP header to front of skb->data * @skb: Socket buffer ("packet") to modify * @hdrlen: Number of bytes of header data which should not be included in * MAP length field * @pad: Specify if padding the MAP packet to make it 4 byte aligned is * necessary * * Padding is calculated and set appropriately in MAP header. Mux ID is * initialized to 0. * * Return: * - Pointer to MAP structure * - 0 (null) if insufficient headroom * - 0 (null) if insufficient tailroom for padding bytes * * todo: Parameterize skb alignment */ struct rmnet_map_header_s *rmnet_data_map_add_map_header(struct sk_buff *skb, int hdrlen, int pad) { uint32_t padding, map_datalen; uint8_t *padbytes; struct rmnet_map_header_s *map_header; if (skb_headroom(skb) < sizeof(struct rmnet_map_header_s)) return 0; map_datalen = skb->len - hdrlen; map_header = (struct rmnet_map_header_s *) skb_push(skb, sizeof(struct rmnet_map_header_s)); memset(map_header, 0, sizeof(struct rmnet_map_header_s)); if (pad == RMNET_MAP_NO_PAD_BYTES) { map_header->pkt_len = htons(map_datalen); return map_header; } padding = ALIGN(map_datalen, 4) - map_datalen; if (padding == 0) goto done; if (skb_tailroom(skb) < padding) return 0; padbytes = (uint8_t *) skb_put(skb, padding); LOGD("pad: %d", padding); memset(padbytes, 0, padding); done: map_header->pkt_len = htons(map_datalen + padding); map_header->pad_len = padding&0x3F; return map_header; } /** * rmnet_data_map_deaggregate() - Deaggregates a single packet * @skb: Source socket buffer containing multiple MAP frames * @config: Physical endpoint configuration of the ingress device * * A whole new buffer is allocated for each portion of an aggregated frame. * Caller should keep calling deaggregate() on the source skb until 0 is * returned, indicating that there are no more packets to deaggregate. Caller * is responsible for freeing the original skb. * * Return: * - Pointer to new skb * - 0 (null) if no more aggregated packets */ struct sk_buff *rmnet_data_map_deaggregate(struct sk_buff *skb, struct rmnet_phys_ep_config *config) { struct sk_buff *skbn; struct rmnet_map_header_s *maph; uint32_t packet_len; if (skb->len == 0) return 0; maph = (struct rmnet_map_header_s *) skb->data; packet_len = ntohs(maph->pkt_len) + sizeof(struct rmnet_map_header_s); if ((config->ingress_data_format & RMNET_INGRESS_FORMAT_MAP_CKSUMV3) || (config->ingress_data_format & RMNET_INGRESS_FORMAT_MAP_CKSUMV4)) packet_len += sizeof(struct rmnet_map_dl_checksum_trailer_s); if ((((int)skb->len) - ((int)packet_len)) < 0) { LOGM("%s", "Got malformed packet. Dropping"); return 0; } skbn = alloc_skb(packet_len + RMNET_MAP_DEAGGR_SPACING, GFP_ATOMIC); if (!skbn) return 0; skbn->dev = skb->dev; skb_reserve(skbn, RMNET_MAP_DEAGGR_HEADROOM); skb_put(skbn, packet_len); memcpy(skbn->data, skb->data, packet_len); skb_pull(skb, packet_len); /* Some hardware can send us empty frames. Catch them */ if (ntohs(maph->pkt_len) == 0) { LOGD("Dropping empty MAP frame"); rmnet_kfree_skb(skbn, RMNET_STATS_SKBFREE_DEAGG_DATA_LEN_0); return 0; } return skbn; } /** * rmnet_map_flush_packet_queue() - Transmits aggregeted frame on timeout * @work: struct agg_work containing delayed work and skb to flush * * This function is scheduled to run in a specified number of jiffies after * the last frame transmitted by the network stack. When run, the buffer * containing aggregated packets is finally transmitted on the underlying link. * */ static void rmnet_map_flush_packet_queue(struct work_struct *work) { struct agg_work *real_work; struct rmnet_phys_ep_config *config; unsigned long flags; struct sk_buff *skb; int rc, agg_count = 0; skb = 0; real_work = (struct agg_work *)work; config = real_work->config; LOGD("%s", "Entering flush thread"); spin_lock_irqsave(&config->agg_lock, flags); if (likely(config->agg_state == RMNET_MAP_TXFER_SCHEDULED)) { /* Buffer may have already been shipped out */ if (likely(config->agg_skb)) { rmnet_stats_agg_pkts(config->agg_count); if (config->agg_count > 1) LOGL("Agg count: %d", config->agg_count); skb = config->agg_skb; agg_count = config->agg_count; config->agg_skb = 0; config->agg_count = 0; memset(&(config->agg_time), 0, sizeof(struct timespec)); } config->agg_state = RMNET_MAP_AGG_IDLE; } else { /* How did we get here? */ LOGE("Ran queued command when state %s", "is idle. State machine likely broken"); } spin_unlock_irqrestore(&config->agg_lock, flags); if (skb) { trace_rmnet_map_flush_packet_queue(skb, agg_count); rc = dev_queue_xmit(skb); rmnet_stats_queue_xmit(rc, RMNET_STATS_QUEUE_XMIT_AGG_TIMEOUT); } kfree(work); } /** * rmnet_map_aggregate() - Software aggregates multiple packets. * @skb: current packet being transmitted * @config: Physical endpoint configuration of the ingress device * * Aggregates multiple SKBs into a single large SKB for transmission. MAP * protocol is used to separate the packets in the buffer. This funcion consumes * the argument SKB and should not be further processed by any other function. */ void rmnet_map_aggregate(struct sk_buff *skb, struct rmnet_phys_ep_config *config) { uint8_t *dest_buff; struct agg_work *work; unsigned long flags; struct sk_buff *agg_skb; struct timespec diff, last; int size, rc, agg_count = 0; if (!skb || !config) BUG(); size = config->egress_agg_size-skb->len; if (size < 2000) { LOGL("Invalid length %d", size); return; } new_packet: spin_lock_irqsave(&config->agg_lock, flags); memcpy(&last, &(config->agg_last), sizeof(struct timespec)); getnstimeofday(&(config->agg_last)); if (!config->agg_skb) { /* Check to see if we should agg first. If the traffic is very * sparse, don't aggregate. We will need to tune this later */ diff = timespec_sub(config->agg_last, last); if ((diff.tv_sec > 0) || (diff.tv_nsec > agg_bypass_time)) { spin_unlock_irqrestore(&config->agg_lock, flags); LOGL("delta t: %ld.%09lu\tcount: bypass", diff.tv_sec, diff.tv_nsec); rmnet_stats_agg_pkts(1); trace_rmnet_map_aggregate(skb, 0); rc = dev_queue_xmit(skb); rmnet_stats_queue_xmit(rc, RMNET_STATS_QUEUE_XMIT_AGG_SKIP); return; } config->agg_skb = skb_copy_expand(skb, 0, size, GFP_ATOMIC); if (!config->agg_skb) { config->agg_skb = 0; config->agg_count = 0; memset(&(config->agg_time), 0, sizeof(struct timespec)); spin_unlock_irqrestore(&config->agg_lock, flags); rmnet_stats_agg_pkts(1); trace_rmnet_map_aggregate(skb, 0); rc = dev_queue_xmit(skb); rmnet_stats_queue_xmit(rc, RMNET_STATS_QUEUE_XMIT_AGG_CPY_EXP_FAIL); return; } config->agg_count = 1; getnstimeofday(&(config->agg_time)); trace_rmnet_start_aggregation(skb); rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_AGG_CPY_EXPAND); goto schedule; } diff = timespec_sub(config->agg_last, config->agg_time); if (skb->len > (config->egress_agg_size - config->agg_skb->len) || (config->agg_count >= config->egress_agg_count) || (diff.tv_sec > 0) || (diff.tv_nsec > agg_time_limit)) { rmnet_stats_agg_pkts(config->agg_count); agg_skb = config->agg_skb; agg_count = config->agg_count; config->agg_skb = 0; config->agg_count = 0; memset(&(config->agg_time), 0, sizeof(struct timespec)); spin_unlock_irqrestore(&config->agg_lock, flags); LOGL("delta t: %ld.%09lu\tcount: %d", diff.tv_sec, diff.tv_nsec, agg_count); trace_rmnet_map_aggregate(skb, agg_count); rc = dev_queue_xmit(agg_skb); rmnet_stats_queue_xmit(rc, RMNET_STATS_QUEUE_XMIT_AGG_FILL_BUFFER); goto new_packet; } dest_buff = skb_put(config->agg_skb, skb->len); memcpy(dest_buff, skb->data, skb->len); config->agg_count++; rmnet_kfree_skb(skb, RMNET_STATS_SKBFREE_AGG_INTO_BUFF); schedule: if (config->agg_state != RMNET_MAP_TXFER_SCHEDULED) { work = kmalloc(sizeof(*work), GFP_ATOMIC); if (!work) { LOGE("Failed to allocate work item for packet %s", "transfer. DATA PATH LIKELY BROKEN!"); config->agg_state = RMNET_MAP_AGG_IDLE; spin_unlock_irqrestore(&config->agg_lock, flags); return; } INIT_DELAYED_WORK((struct delayed_work *)work, rmnet_map_flush_packet_queue); work->config = config; config->agg_state = RMNET_MAP_TXFER_SCHEDULED; schedule_delayed_work((struct delayed_work *)work, 1); } spin_unlock_irqrestore(&config->agg_lock, flags); return; } /* ***************** Checksum Offload ************************************** */ static inline uint16_t *rmnet_map_get_checksum_field(unsigned char protocol, const void *txporthdr) { uint16_t *check = 0; switch (protocol) { case IPPROTO_TCP: check = &(((struct tcphdr *)txporthdr)->check); break; case IPPROTO_UDP: check = &(((struct udphdr *)txporthdr)->check); break; default: check = 0; break; } return check; } static inline uint16_t rmnet_map_add_checksums(uint16_t val1, uint16_t val2) { int sum = val1+val2; sum = (((sum&0xFFFF0000)>>16) + sum) & 0x0000FFFF; return (uint16_t) (sum&0x0000FFFF); } static inline uint16_t rmnet_map_subtract_checksums(uint16_t val1, uint16_t val2) { return rmnet_map_add_checksums(val1, ~val2); } /** * rmnet_map_validate_ipv4_packet_checksum() - Validates TCP/UDP checksum * value for IPv4 packet * @map_payload: Pointer to the beginning of the map payload * @cksum_trailer: Pointer to the checksum trailer * * Validates the TCP/UDP checksum for the packet using the checksum value * from the checksum trailer added to the packet. * The validation formula is the following: * 1. Performs 1's complement over the checksum value from the trailer * 2. Computes 1's complement checksum over IPv4 header and subtracts it from * the value from step 1 * 3. Computes 1's complement checksum over IPv4 pseudo header and adds it to * the value from step 2 * 4. Subtracts the checksum value from the TCP/UDP header from the value from * step 3 * 5. Compares the value from step 4 to the checksum value from the TCP/UDP * header * * Fragmentation and tunneling are not supported. * * Return: 0 is validation succeeded. */ static int rmnet_map_validate_ipv4_packet_checksum(unsigned char *map_payload, struct rmnet_map_dl_checksum_trailer_s *cksum_trailer) { struct iphdr *ip4h; uint16_t *checksum_field; void *txporthdr; uint16_t pseudo_checksum; uint16_t ip_hdr_checksum; uint16_t checksum_value; uint16_t ip_payload_checksum; uint16_t ip_pseudo_payload_checksum; uint16_t checksum_value_final; ip4h = (struct iphdr *) map_payload; if ((ntohs(ip4h->frag_off) & IP_MF) || ((ntohs(ip4h->frag_off) & IP_OFFSET) > 0)) return RMNET_MAP_CHECKSUM_FRAGMENTED_PACKET; txporthdr = map_payload + ip4h->ihl*4; checksum_field = rmnet_map_get_checksum_field(ip4h->protocol, txporthdr); if (unlikely(!checksum_field)) return RMNET_MAP_CHECKSUM_ERR_UNKNOWN_TRANSPORT; /* RFC 768 - Skip IPv4 UDP packets where sender checksum field is 0 */ if ((*checksum_field == 0) && (ip4h->protocol == IPPROTO_UDP)) return RMNET_MAP_CHECKSUM_SKIPPED; checksum_value = ~ntohs(cksum_trailer->checksum_value); ip_hdr_checksum = ~ip_fast_csum(ip4h, (int)ip4h->ihl); ip_payload_checksum = rmnet_map_subtract_checksums(checksum_value, ip_hdr_checksum); pseudo_checksum = ~ntohs(csum_tcpudp_magic(ip4h->saddr, ip4h->daddr, (uint16_t)(ntohs(ip4h->tot_len) - ip4h->ihl*4), (uint16_t)ip4h->protocol, 0)); ip_pseudo_payload_checksum = rmnet_map_add_checksums( ip_payload_checksum, pseudo_checksum); checksum_value_final = ~rmnet_map_subtract_checksums( ip_pseudo_payload_checksum, ntohs(*checksum_field)); if (unlikely(checksum_value_final == 0)) { switch (ip4h->protocol) { case IPPROTO_UDP: /* RFC 768 */ LOGD("DL4 1's complement rule for UDP checksum 0"); checksum_value_final = ~checksum_value_final; break; case IPPROTO_TCP: if (*checksum_field == 0xFFFF) { LOGD( "DL4 Non-RFC compliant TCP checksum found"); checksum_value_final = ~checksum_value_final; } break; } } LOGD( "DL4 cksum: ~HW: %04X, field: %04X, pseudo header: %04X, final: %04X", ~ntohs(cksum_trailer->checksum_value), ntohs(*checksum_field), pseudo_checksum, checksum_value_final); if (checksum_value_final == ntohs(*checksum_field)) return RMNET_MAP_CHECKSUM_OK; else return RMNET_MAP_CHECKSUM_VALIDATION_FAILED; } /** * rmnet_map_validate_ipv6_packet_checksum() - Validates TCP/UDP checksum * value for IPv6 packet * @map_payload: Pointer to the beginning of the map payload * @cksum_trailer: Pointer to the checksum trailer * * Validates the TCP/UDP checksum for the packet using the checksum value * from the checksum trailer added to the packet. * The validation formula is the following: * 1. Performs 1's complement over the checksum value from the trailer * 2. Computes 1's complement checksum over IPv6 header and subtracts it from * the value from step 1 * 3. Computes 1's complement checksum over IPv6 pseudo header and adds it to * the value from step 2 * 4. Subtracts the checksum value from the TCP/UDP header from the value from * step 3 * 5. Compares the value from step 4 to the checksum value from the TCP/UDP * header * * Fragmentation, extension headers and tunneling are not supported. * * Return: 0 is validation succeeded. */ static int rmnet_map_validate_ipv6_packet_checksum(unsigned char *map_payload, struct rmnet_map_dl_checksum_trailer_s *cksum_trailer) { struct ipv6hdr *ip6h; uint16_t *checksum_field; void *txporthdr; uint16_t pseudo_checksum; uint16_t ip_hdr_checksum; uint16_t checksum_value; uint16_t ip_payload_checksum; uint16_t ip_pseudo_payload_checksum; uint16_t checksum_value_final; uint32_t length; ip6h = (struct ipv6hdr *) map_payload; txporthdr = map_payload + sizeof(struct ipv6hdr); checksum_field = rmnet_map_get_checksum_field(ip6h->nexthdr, txporthdr); if (unlikely(!checksum_field)) return RMNET_MAP_CHECKSUM_ERR_UNKNOWN_TRANSPORT; checksum_value = ~ntohs(cksum_trailer->checksum_value); ip_hdr_checksum = ~ntohs(ip_compute_csum(ip6h, (int)(txporthdr - (void *)map_payload))); ip_payload_checksum = rmnet_map_subtract_checksums(checksum_value, ip_hdr_checksum); length = (ip6h->nexthdr == IPPROTO_UDP) ? ntohs(((struct udphdr *)txporthdr)->len) : ntohs(ip6h->payload_len); pseudo_checksum = ~ntohs(csum_ipv6_magic(&ip6h->saddr, &ip6h->daddr, length, ip6h->nexthdr, 0)); ip_pseudo_payload_checksum = rmnet_map_add_checksums( ip_payload_checksum, pseudo_checksum); checksum_value_final = ~rmnet_map_subtract_checksums( ip_pseudo_payload_checksum, ntohs(*checksum_field)); if (unlikely(checksum_value_final == 0)) { switch (ip6h->nexthdr) { case IPPROTO_UDP: /* RFC 2460 section 8.1 */ LOGD("DL6 One's complement rule for UDP checksum 0"); checksum_value_final = ~checksum_value_final; break; case IPPROTO_TCP: if (*checksum_field == 0xFFFF) { LOGD( "DL6 Non-RFC compliant TCP checksum found"); checksum_value_final = ~checksum_value_final; } break; } } LOGD( "DL6 cksum: ~HW: %04X, field: %04X, pseudo header: %04X, final: %04X", ~ntohs(cksum_trailer->checksum_value), ntohs(*checksum_field), pseudo_checksum, checksum_value_final); if (checksum_value_final == ntohs(*checksum_field)) return RMNET_MAP_CHECKSUM_OK; else return RMNET_MAP_CHECKSUM_VALIDATION_FAILED; } /** * rmnet_map_data_checksum_downlink_packet() - Validates checksum on * a downlink packet * @skb: Pointer to the packet's skb. * * Validates packet checksums. Function takes a pointer to * the beginning of a buffer which contains the entire MAP * frame: MAP header + IP payload + padding + checksum trailer. * Currently, only IPv4 and IPv6 are supported along with * TCP & UDP. Fragmented or tunneled packets are not supported. * * Return: * - RMNET_MAP_CHECKSUM_OK: Validation of checksum succeeded. * - RMNET_MAP_CHECKSUM_ERR_BAD_BUFFER: Skb buffer given is corrupted. * - RMNET_MAP_CHECKSUM_VALID_FLAG_NOT_SET: Valid flag is not set in the * checksum trailer. * - RMNET_MAP_CHECKSUM_FRAGMENTED_PACKET: The packet is a fragment. * - RMNET_MAP_CHECKSUM_ERR_UNKNOWN_TRANSPORT: The transport header is * not TCP/UDP. * - RMNET_MAP_CHECKSUM_ERR_UNKNOWN_IP_VERSION: Unrecognized IP header. * - RMNET_MAP_CHECKSUM_VALIDATION_FAILED: In case the validation failed. */ int rmnet_map_data_checksum_downlink_packet(struct sk_buff *skb) { struct rmnet_map_dl_checksum_trailer_s *cksum_trailer; unsigned int data_len; unsigned char *map_payload; unsigned char ip_version; data_len = RMNET_MAP_GET_LENGTH(skb); if (unlikely(skb->len < (sizeof(struct rmnet_map_header_s) + data_len + sizeof(struct rmnet_map_dl_checksum_trailer_s)))) return RMNET_MAP_CHECKSUM_ERR_BAD_BUFFER; cksum_trailer = (struct rmnet_map_dl_checksum_trailer_s *) (skb->data + data_len + sizeof(struct rmnet_map_header_s)); if (unlikely(!ntohs(cksum_trailer->valid))) return RMNET_MAP_CHECKSUM_VALID_FLAG_NOT_SET; map_payload = (unsigned char *)(skb->data + sizeof(struct rmnet_map_header_s)); ip_version = (*map_payload & 0xF0) >> 4; if (ip_version == 0x04) return rmnet_map_validate_ipv4_packet_checksum(map_payload, cksum_trailer); else if (ip_version == 0x06) return rmnet_map_validate_ipv6_packet_checksum(map_payload, cksum_trailer); return RMNET_MAP_CHECKSUM_ERR_UNKNOWN_IP_VERSION; } static void rmnet_map_fill_ipv4_packet_ul_checksum_header(void *iphdr, struct rmnet_map_ul_checksum_header_s *ul_header, struct sk_buff *skb) { struct iphdr *ip4h = (struct iphdr *)iphdr; unsigned short *hdr = (unsigned short *)ul_header; ul_header->checksum_start_offset = htons((unsigned short) (skb_transport_header(skb) - (unsigned char *)iphdr)); ul_header->checksum_insert_offset = skb->csum_offset; ul_header->cks_en = 1; if (ip4h->protocol == IPPROTO_UDP) ul_header->udp_ip4_ind = 1; else ul_header->udp_ip4_ind = 0; /* Changing checksum_insert_offset to network order */ hdr++; *hdr = htons(*hdr); skb->ip_summed = CHECKSUM_NONE; } static void rmnet_map_fill_ipv6_packet_ul_checksum_header(void *iphdr, struct rmnet_map_ul_checksum_header_s *ul_header, struct sk_buff *skb) { unsigned short *hdr = (unsigned short *)ul_header; ul_header->checksum_start_offset = htons((unsigned short) (skb_transport_header(skb) - (unsigned char *)iphdr)); ul_header->checksum_insert_offset = skb->csum_offset; ul_header->cks_en = 1; ul_header->udp_ip4_ind = 0; /* Changing checksum_insert_offset to network order */ hdr++; *hdr = htons(*hdr); skb->ip_summed = CHECKSUM_NONE; } static void rmnet_map_complement_ipv4_txporthdr_csum_field(void *iphdr) { struct iphdr *ip4h = (struct iphdr *)iphdr; void *txporthdr; uint16_t *csum; txporthdr = iphdr + ip4h->ihl*4; if ((ip4h->protocol == IPPROTO_TCP) || (ip4h->protocol == IPPROTO_UDP)) { csum = (uint16_t *)rmnet_map_get_checksum_field(ip4h->protocol, txporthdr); *csum = ~(*csum); } } static void rmnet_map_complement_ipv6_txporthdr_csum_field(void *ip6hdr) { struct ipv6hdr *ip6h = (struct ipv6hdr *)ip6hdr; void *txporthdr; uint16_t *csum; txporthdr = ip6hdr + sizeof(struct ipv6hdr); if ((ip6h->nexthdr == IPPROTO_TCP) || (ip6h->nexthdr == IPPROTO_UDP)) { csum = (uint16_t *)rmnet_map_get_checksum_field(ip6h->nexthdr, txporthdr); *csum = ~(*csum); } } /** * rmnet_map_checksum_uplink_packet() - Generates UL checksum * meta info header * @skb: Pointer to the packet's skb. * * Generates UL checksum meta info header for IPv4 and IPv6 over TCP and UDP * packets that are supported for UL checksum offload. * * Return: * - RMNET_MAP_CHECKSUM_OK: Validation of checksum succeeded. * - RMNET_MAP_CHECKSUM_ERR_UNKNOWN_IP_VERSION: Unrecognized IP header. * - RMNET_MAP_CHECKSUM_SW: Unsupported packet for UL checksum offload. */ int rmnet_map_data_checksum_uplink_packet(struct sk_buff *skb, struct net_device *orig_dev, uint32_t egress_data_format) { unsigned char ip_version; struct rmnet_map_ul_checksum_header_s *ul_header; void *iphdr; int ret; ul_header = (struct rmnet_map_ul_checksum_header_s *) skb_push(skb, sizeof(struct rmnet_map_ul_checksum_header_s)); if (unlikely(!(orig_dev->features & (NETIF_F_V4_CSUM | NETIF_F_V6_CSUM)))) { ret = RMNET_MAP_CHECKSUM_SW; goto sw_checksum; } if (skb->ip_summed == CHECKSUM_PARTIAL) { iphdr = (char *)ul_header + sizeof(struct rmnet_map_ul_checksum_header_s); ip_version = (*(char *)iphdr & 0xF0) >> 4; if (ip_version == 0x04) { rmnet_map_fill_ipv4_packet_ul_checksum_header(iphdr, ul_header, skb); if (egress_data_format & RMNET_EGRESS_FORMAT_MAP_CKSUMV4) rmnet_map_complement_ipv4_txporthdr_csum_field( iphdr); return RMNET_MAP_CHECKSUM_OK; } else if (ip_version == 0x06) { rmnet_map_fill_ipv6_packet_ul_checksum_header(iphdr, ul_header, skb); if (egress_data_format & RMNET_EGRESS_FORMAT_MAP_CKSUMV4) rmnet_map_complement_ipv6_txporthdr_csum_field( iphdr); return RMNET_MAP_CHECKSUM_OK; } else { ret = RMNET_MAP_CHECKSUM_ERR_UNKNOWN_IP_VERSION; goto sw_checksum; } } else { ret = RMNET_MAP_CHECKSUM_SW; goto sw_checksum; } sw_checksum: ul_header->checksum_start_offset = 0; ul_header->checksum_insert_offset = 0; ul_header->cks_en = 0; ul_header->udp_ip4_ind = 0; return ret; }