/* Hardware start transmission. * Send a packet to media from the upper layer. */ static int emac_start_xmit(struct sk_buff *skb, struct net_device *dev) { struct emac_board_info *db = netdev_priv(dev); unsigned long channel; unsigned long flags; channel = db->tx_fifo_stat & 3; if (channel == 3) return 1; channel = (channel == 1 ? 1 : 0); spin_lock_irqsave(&db->lock, flags); writel(channel, db->membase + EMAC_TX_INS_REG); emac_outblk_32bit(db->membase + EMAC_TX_IO_DATA_REG, skb_data(skb), skb_len(skb)); dev->stats.tx_bytes += skb_len(skb); db->tx_fifo_stat |= 1 << channel; /* TX control: First packet immediately send, second packet queue */ if (channel == 0) { /* set TX len */ writel(skb_len(skb), db->membase + EMAC_TX_PL0_REG); /* start translate from fifo to phy */ writel(readl(db->membase + EMAC_TX_CTL0_REG) | 1, db->membase + EMAC_TX_CTL0_REG); /* save the time stamp */ dev->trans_start = jiffies; } else if (channel == 1) { /* set TX len */ writel(skb_len(skb), db->membase + EMAC_TX_PL1_REG); /* start translate from fifo to phy */ writel(readl(db->membase + EMAC_TX_CTL1_REG) | 1, db->membase + EMAC_TX_CTL1_REG); /* save the time stamp */ dev->trans_start = jiffies; } if ((db->tx_fifo_stat & 3) == 3) { /* Second packet */ netif_stop_queue(dev); } spin_unlock_irqrestore(&db->lock, flags); /* free this SKB */ dev_kfree_skb(skb); return NETDEV_TX_OK; }
static int tcp_dispatch(struct sk_buff *skb, const struct pal_dip *dip) { struct tcp_hdr *tcph = skb_tcp_header(skb); pal_cur_thread_conf()->stats.ip.tcp.rx_pkts++; pal_cur_thread_conf()->stats.ip.tcp.rx_bytes += skb_l2_len(skb); if (!skb_l4_csum_ok(skb)) { pal_cur_thread_conf()->stats.ip.tcp.csum_err++; PAL_DEBUG("TCP check sum error\n"); return -1; } if (skb_len(skb) < (unsigned)tcph->doff * 4) { pal_cur_thread_conf()->stats.ip.tcp.trunc_pkts++; PAL_DEBUG("error: skb len < doff * 4\n"); return -1; } if (dispatch_pkt(skb, dip) == 0) /* DO NOT free. Already sent to worker */ return 0; return -1; }
static int icmp_dispatch(struct sk_buff *skb, const struct pal_dip *dip) { struct ip_hdr *iph = (struct ip_hdr *)skb_ip_header(skb); struct icmp_hdr *icmph = skb_icmp_header(skb); unsigned icmp_len = pal_ntohs(iph->tot_len) - (iph->ihl * 4); pal_cur_thread_conf()->stats.ip.icmp.rx_pkts++; pal_cur_thread_conf()->stats.ip.icmp.rx_bytes += skb_l2_len(skb); /* icmp content must be more then 8 bytes */ if (icmp_len >= 8 && icmp_len <= skb_len(skb)) { if (icmp_check_sum_correct((uint16_t *)icmph, icmp_len)) { return dispatch_pkt(skb, dip); } else { pal_cur_thread_conf()->stats.ip.icmp.csum_err++; PAL_DEBUG("Bad icmp checksum\n"); } } else { pal_cur_thread_conf()->stats.ip.icmp.trunc_pkts++; PAL_DEBUG("icmp len < 8, invalid\n"); } return -1; }
static int udp_dispatch(struct sk_buff *skb, const struct pal_dip *dip) { pal_cur_thread_conf()->stats.ip.udp.rx_pkts++; pal_cur_thread_conf()->stats.ip.udp.rx_bytes += skb_l2_len(skb); if (!skb_l4_csum_ok(skb)) { pal_cur_thread_conf()->stats.ip.udp.csum_err++; PAL_DEBUG("UDP check sum error\n"); return -1; } if (skb_len(skb) < 8) { pal_cur_thread_conf()->stats.ip.udp.trunc_pkts++; PAL_DEBUG("error: skb len < udp header length\n"); return -1; } if (dispatch_pkt(skb, dip) == 0) { /* DO NOT free. Already sent to worker */ return 0; } return -1; }
static int icmp_handler(struct sk_buff *skb) { struct ip_hdr *iph = (struct ip_hdr *)skb_ip_header(skb); struct icmp_hdr *icmph = skb_icmp_header(skb); unsigned icmp_len = pal_htons(iph->tot_len) - (iph->ihl * 4); uint8_t *l2_dest_mac_p = skb_eth_header(skb)->dst; uint32_t tmp_addr; pal_cur_thread_conf()->stats.ip.icmp.rx_pkts++; pal_cur_thread_conf()->stats.ip.icmp.rx_bytes += skb_l2_len(skb); PAL_DEBUG("in icmp handler\n"); /* icmp content must be more then 8 bytes */ if (icmp_len >= 8 && icmp_len <= skb_len(skb)) { /* we only handle icmp echo request */ if (icmph->type == ICMP_ECHO) { if (icmp_check_sum_correct((uint16_t *)icmph, icmp_len)) { icmph->type = ICMP_ECHOREPLY; /* update icmp check sum*/ icmph->checksum = icmph->checksum + pal_htons_constant(0x0800); /* Exchange ip addresses */ tmp_addr = iph->saddr; iph->saddr = iph->daddr; iph->daddr = tmp_addr; /**update ip ttl*/ iph->ttl = 64; skb_ip_csum_offload(skb, iph->ihl * 4); swap_mac(l2_dest_mac_p); skb_push(skb, (unsigned long)skb_l4_header(skb) - (unsigned long)skb_l2_header(skb)); pal_cur_thread_conf()->stats.ip.icmp.reply_pkts++; if (pal_send_raw_pkt(skb, skb->recv_if) == 0) { PAL_DEBUG("sent icmp reply\n"); return 0; } pal_cur_thread_conf()->stats.ip.icmp.reply_failure++; PAL_DEBUG("send icmp reply failed\n"); } else { pal_cur_thread_conf()->stats.ip.icmp.csum_err++; PAL_DEBUG("Bad icmp checksum\n"); } } else { pal_cur_thread_conf()->stats.ip.icmp.not_echo_pkts++; pal_cur_thread_conf()->stats.ip.icmp.not_echo_bytes += skb_l2_len(skb); PAL_DEBUG("icmp not echo request!\n"); } } else { pal_cur_thread_conf()->stats.ip.icmp.trunc_pkts++; PAL_DEBUG("icmp len < 8, invalid\n"); } return -1; }
/* * @brief Handles ip packets * @param skb Pointer to the skb, whose data pointer must be set to ip header. * @return 0 on success. -1 on failure. The packet is freed in both cases */ int ip_handler(struct sk_buff *skb) { int ret = -1; struct ip_hdr *iph; struct pal_dip *dip; if (!skb_ip_csum_ok(skb)) { PAL_DEBUG("skb ip csum error\n"); pal_cur_thread_conf()->stats.ip.csum_err++; goto free_out; } iph = skb_ip_header(skb); if ((unsigned)iph->ihl < 5 || skb_len(skb) < (unsigned)iph->ihl * 4) { pal_cur_thread_conf()->stats.ip.trunc_pkts++; goto free_out; } dip = pal_ipg_find_ip(iph->daddr); if (dip == NULL) { pal_cur_thread_conf()->stats.ip.unknown_dst++; /* TODO: use default ip group. */ PAL_DEBUG("dip "NIPQUAD_FMT" not found\n", NIPQUAD(iph->daddr)); goto free_out; } switch (dip->type) { case PAL_DIP_USER: skb_pull(skb, iph->ihl * 4); skb_reset_l4_header(skb); switch (iph->protocol) { case PAL_IPPROTO_TCP: ret = tcp_dispatch(skb, dip); break; case PAL_IPPROTO_UDP: ret = udp_dispatch(skb, dip); break; case PAL_IPPROTO_ICMP: if (dip->ipg->flags & PAL_IPG_F_HANDLEICMP) { ret = icmp_dispatch(skb, dip); } else { ret = icmp_handler(skb); } break; default: pal_cur_thread_conf()->stats.ip.unknown_proto_pkts++; pal_cur_thread_conf()->stats.ip.unknown_proto_bytes += skb_l2_len(skb); PAL_DEBUG("UNKNOWN IP PKTS\n"); if (dip->ipg->flags & PAL_IPG_F_HANDLEUNKNOWIPPROTO) { ret = dispatch_pkt(skb, dip); } else { ret = -1; } break; } if (ret >= 0) { pal_ipg_put_ip(dip->ip); return 0; } goto putdip_out; case PAL_DIP_VNIC: if (dip->port != skb->recv_if) { pal_cur_thread_conf()->stats.tap.port_err++; PAL_DEBUG("got a packet for wrong vnic\n"); goto putdip_out; } /* push the data pointer to include the ethernet header */ skb_push(skb, sizeof(struct eth_hdr)); if (pal_send_to_vnic(dip->port, skb) == 0) { pal_skb_free(skb); pal_ipg_put_ip(dip->ip); return 0; } goto putdip_out; default: pal_cur_thread_conf()->stats.ip.unknown_dst++; PAL_DEBUG("unknown\n"); goto putdip_out; } putdip_out: pal_ipg_put_ip(dip->ip); free_out: pal_skb_free(skb); return ret; }