diff options
Diffstat (limited to 'drivers/net/wimax/cmc7xx/receive.c')
-rwxr-xr-x | drivers/net/wimax/cmc7xx/receive.c | 464 |
1 files changed, 464 insertions, 0 deletions
diff --git a/drivers/net/wimax/cmc7xx/receive.c b/drivers/net/wimax/cmc7xx/receive.c new file mode 100755 index 0000000..7126a62 --- /dev/null +++ b/drivers/net/wimax/cmc7xx/receive.c @@ -0,0 +1,464 @@ +/* + * Copyright (C) 2011 Samsung Electronics. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ +#include "headers.h" +#include "download.h" + +static void process_indicate_packet(struct net_adapter *adapter, u8 *buffer) +{ + struct wimax_msg_header *packet; + struct wimax_cfg *g_cfg = adapter->pdata->g_cfg; + char *tmp_byte; + + packet = (struct wimax_msg_header *)buffer; + + if (packet->type != be16_to_cpu(ETHERTYPE_DL)) { + pr_warn("%s: not a download packet\n", __func__); + return; + } + + switch (be16_to_cpu(packet->id)) { + case MSG_DRIVER_OK_RESP: + adapter->modem_resp = true; + wake_up_interruptible(&adapter->modem_resp_event); + pr_debug("%s: MSG_DRIVER_OK_RESP\n", __func__); + send_image_info_packet(adapter, MSG_IMAGE_INFO_REQ); + break; + case MSG_IMAGE_INFO_RESP: + pr_debug("%s: MSG_IMAGE_INFO_RESP\n", __func__); + send_image_data_packet(adapter, MSG_IMAGE_DATA_REQ); + break; + case MSG_IMAGE_DATA_RESP: + if (adapter->wimax_image.offset == adapter->wimax_image.size) { + pr_debug("%s: Image Download Complete\n", __func__); + send_cmd_packet(adapter, MSG_RUN_REQ); + } else { + send_image_data_packet(adapter, MSG_IMAGE_DATA_REQ); + } + break; + case MSG_RUN_RESP: + tmp_byte = (char *)(buffer + sizeof(struct wimax_msg_header)); + + if (*tmp_byte != 0x01) + break; + + adapter->download_complete = true; + wake_up_interruptible(&adapter->download_event); + + pr_debug("%s: MSG_RUN_RESP\n", __func__); + if (g_cfg->wimax_mode == SDIO_MODE + || g_cfg->wimax_mode == DM_MODE + || g_cfg->wimax_mode == USB_MODE + || g_cfg->wimax_mode == USIM_RELAY_MODE) { + + adapter->mac_task = kthread_create( + wimax_hw_get_mac_address, adapter, + "%s", "mac_request_thread"); + if (adapter->mac_task) + wake_up_process(adapter->mac_task); + + } else if (g_cfg->wimax_mode == WTM_MODE + || g_cfg->wimax_mode == AUTH_MODE) { + adapter->download_complete = true; + wake_up_interruptible(&adapter->download_event); + } + break; + default: + pr_err("%s: Unknown packet type\n", __func__); + break; + } +} + +static u32 process_private_cmd(struct net_adapter *adapter, void *buffer) +{ + struct hw_private_packet *cmd; + struct wimax_cfg *g_cfg = adapter->pdata->g_cfg; + u8 *bufp; + int ret; + + cmd = (struct hw_private_packet *)buffer; + + switch (cmd->code) { + case HWCODEMACRESPONSE: { + u8 mac_addr[ETHERNET_ADDRESS_LENGTH]; + bufp = (u8 *)buffer; + + /* processing for mac_req request */ + pr_debug("MAC address = %02x:%02x:%02x:%02x:%02x:%02x", + bufp[3], bufp[4], bufp[5], + bufp[6], bufp[7], bufp[8]); + memcpy(mac_addr, bufp + 3, + ETHERNET_ADDRESS_LENGTH); + + /* create ethernet header */ + memcpy(adapter->hw.eth_header, + mac_addr, ETHERNET_ADDRESS_LENGTH); + memcpy(adapter->hw.eth_header + ETHERNET_ADDRESS_LENGTH, + mac_addr, ETHERNET_ADDRESS_LENGTH); + adapter->hw.eth_header[(ETHERNET_ADDRESS_LENGTH * 2) - 1] += 1; + + memcpy(adapter->net->dev_addr, bufp + 3, + ETHERNET_ADDRESS_LENGTH); + adapter->mac_ready = true; + + ret = sizeof(struct hw_private_packet) + + ETHERNET_ADDRESS_LENGTH - sizeof(u8); + return ret; + } + case HWCODELINKINDICATION: { + if ((cmd->value == HW_PROT_VALUE_LINK_DOWN) + && (adapter->media_state != MEDIA_DISCONNECTED)) { + pr_debug("LINK_DOWN_INDICATION"); + + /* set values */ + adapter->media_state = MEDIA_DISCONNECTED; + g_cfg->wimax_status = WIMAX_STATE_READY; + + /* indicate link down */ + netif_stop_queue(adapter->net); + netif_carrier_off(adapter->net); + } else if ((cmd->value == HW_PROT_VALUE_LINK_UP) + && (adapter->media_state != MEDIA_CONNECTED)) { + pr_debug("LINK_UP_INDICATION"); + + /* set values */ + adapter->media_state = MEDIA_CONNECTED; + g_cfg->wimax_status = WIMAX_STATE_NORMAL; + adapter->net->mtu = WIMAX_MTU_SIZE; + + /* indicate link up */ + netif_start_queue(adapter->net); + netif_carrier_on(adapter->net); + } + break; + } + case HWCODEHALTEDINDICATION: { + pr_debug("Device is about to reset, stop driver"); + adapter->halted = true; + break; + } + case HWCODERXREADYINDICATION: { + pr_debug("Device RxReady"); + /* + * to start the data packet send + * queue again after stopping in xmit + */ + if (adapter->media_state == MEDIA_CONNECTED) + netif_wake_queue(adapter->net); + break; + } + case HWCODEIDLENTFY: { + /* set idle / vi */ + if (g_cfg->wimax_status == WIMAX_STATE_NORMAL + || g_cfg->wimax_status == WIMAX_STATE_IDLE) { + pr_debug("process_private_cmd: IDLE"); + g_cfg->wimax_status = WIMAX_STATE_IDLE; + } else { + pr_debug("process_private_cmd: VIRTUAL IDLE"); + g_cfg->wimax_status = WIMAX_STATE_VIRTUAL_IDLE; + } + break; + } + case HWCODEWAKEUPNTFY: { + /* + * IMPORTANT!! at least 4 sec + * is required after modem waked up + */ + wake_lock_timeout(&g_cfg->wimax_wake_lock, 4 * HZ); + + if (g_cfg->wimax_status == + WIMAX_STATE_AWAKE_REQUESTED) { + complete(&adapter->wakeup_event); + break; + } + + if (g_cfg->wimax_status == WIMAX_STATE_IDLE + || g_cfg->wimax_status == WIMAX_STATE_NORMAL) { + pr_debug("process_private_cmd: IDLE -> NORMAL"); + g_cfg->wimax_status = WIMAX_STATE_NORMAL; + } else { + pr_debug("process_private_cmd: VI -> READY"); + g_cfg->wimax_status = WIMAX_STATE_READY; + } + break; + } + default: + pr_debug("Command = %04x", cmd->code); + break; + } + + return sizeof(struct hw_private_packet); +} + + +static void adapter_sdio_rx_packet(struct net_adapter *adapter, int len) +{ + struct hw_packet_header *hdr; + int rlen; + u32 type; + u8 *ofs; + struct sk_buff *rx_skb; + + rlen = len; + ofs = (u8 *)adapter->hw.receive_buffer; + + while (rlen > 0) { + hdr = (struct hw_packet_header *)ofs; + type = HWPKTTYPENONE; + + /* "WD", "WC", "WP" or "WE" */ + if (unlikely(hdr->id0 != 'W')) { + if (rlen > 4) { + pr_debug("Wrong packet \ + ID (%02x %02x)", hdr->id0, hdr->id1); + } + /* skip rest of packets */ + break; + } + + /* check packet type */ + switch (hdr->id1) { + case 'P': { + u32 l = 0; + type = HWPKTTYPEPRIVATE; + + /* process packet */ + l = process_private_cmd(adapter, ofs); + + /* shift */ + ofs += l; + rlen -= l; + + /* process next packet */ + continue; + } + case 'C': + type = HWPKTTYPECONTROL; + break; + case 'D': + type = HWPKTTYPEDATA; + break; + case 'E': + /* skip rest of buffer */ + break; + default: + pr_debug("hwParseReceivedData : \ + Wrong packet ID [%02x %02x]", + hdr->id0, hdr->id1); + /* skip rest of buffer */ + break; + } + + if (type == HWPKTTYPENONE) + break; + + if (likely(!adapter->downloading)) { + if (unlikely(hdr->length > WIMAX_MAX_TOTAL_SIZE + || ((hdr->length + + sizeof(struct hw_packet_header)) > rlen))) { + pr_debug("Packet length is \ + too big (%d)", hdr->length); + /* skip rest of packets */ + break; + } + } + + /* change offset */ + ofs += sizeof(struct hw_packet_header); + rlen -= sizeof(struct hw_packet_header); + + /* process download packet, data and control packet */ + if (likely(!adapter->downloading)) { + ofs += 2; + rlen -= 2; + + if (unlikely(type == HWPKTTYPECONTROL)) + control_recv(adapter, (u8 *)ofs, + hdr->length); + else { + if (hdr->length > BUFFER_DATA_SIZE) { + pr_debug("Data packet too large"); + adapter->netstats.rx_dropped++; + break; + } + + rx_skb = dev_alloc_skb(hdr->length + + (ETHERNET_ADDRESS_LENGTH * 2)); + if (!rx_skb) { + pr_debug("MEMORY PINCH: \ + unable to allocate skb"); + break; + } + skb_reserve(rx_skb, + (ETHERNET_ADDRESS_LENGTH * 2)); + + memcpy(skb_push(rx_skb, + (ETHERNET_ADDRESS_LENGTH * 2)), + adapter->hw.eth_header, + (ETHERNET_ADDRESS_LENGTH * 2)); + + memcpy(skb_put(rx_skb, hdr->length), + (u8 *)ofs, + hdr->length); + + rx_skb->dev = adapter->net; + rx_skb->protocol = + eth_type_trans(rx_skb, adapter->net); + rx_skb->ip_summed = CHECKSUM_UNNECESSARY; + + if (netif_rx_ni(rx_skb) == NET_RX_DROP) { + pr_debug("packet dropped!"); + adapter->netstats.rx_dropped++; + } + adapter->netstats.rx_packets++; + adapter->netstats.rx_bytes += + (hdr->length + + (ETHERNET_ADDRESS_LENGTH * 2)); + + } + } else { + hdr->length -= sizeof(struct hw_packet_header); + process_indicate_packet(adapter, ofs); + } + /* + * If the packet is unreasonably long, + * quietly drop it rather than + * kernel panicing by calling skb_put. + * + * shift + */ + ofs += hdr->length; + rlen -= hdr->length; + } + + return; +} + +static int hw_sdio_read_bank_index(struct net_adapter *adapter, int *read_idx) +{ + int ret = 0; + + *read_idx = sdio_readb(adapter->func, SDIO_C2H_RP_REG, &ret); + if (ret) + return ret; + + if (*read_idx == sdio_readb(adapter->func, SDIO_C2H_WP_REG, &ret)) + *read_idx = -1; + + return ret; +} + +static int hw_sdio_read_counter(struct net_adapter *adapter, u32 *len, + int read_idx) +{ + int ret = 0; + + *len = sdio_readw(adapter->func, (SDIO_RX_BANK_ADDR + + (read_idx * SDIO_BANK_SIZE)), &ret); + if (ret) + return ret; + + *len |= sdio_readw(adapter->func, (SDIO_RX_BANK_ADDR + + (read_idx * SDIO_BANK_SIZE) + 2), &ret) << 16; + + if (*len > SDIO_BUFFER_SIZE) + *len = 0; + + return ret; +} + +int cmc732_receive_thread(void *data) +{ + struct net_adapter *adapter = (struct net_adapter *)data; + int err = 1; + int nReadIdx; + u32 len = 0; + u32 t_len; + u32 t_index; + u32 t_size; + u8 *t_buff; + + do { + + wait_event_interruptible(adapter->receive_event, + adapter->rx_data_available + || (!adapter) || adapter->halted); + adapter->rx_data_available = false; + if ((!adapter) || adapter->halted) + break; + sdio_claim_host(adapter->func); + err = hw_sdio_read_bank_index(adapter, &nReadIdx); + + if (err) { + pr_debug("adapter_sdio_rx_packet : \ + error occurred during fetch bank \ + index!! err = %d, nReadIdx = %d", \ + err, nReadIdx); + sdio_release_host(adapter->func); + continue; + } + if (nReadIdx < 0) { + sdio_release_host(adapter->func); + continue; + } + err = hw_sdio_read_counter(adapter, &len, nReadIdx); + if (unlikely(err || !len)) { + pr_debug("adapter_sdio_rx_packet : \ + error in reading bank length!! \ + err = %d, len = %d", err, len); + sdio_release_host(adapter->func); + continue; + } + + if (unlikely(len > SDIO_BUFFER_SIZE)) { + pr_debug("ERROR RECV length (%d) > \ + SDIO_BUFFER_SIZE", len); + len = SDIO_BUFFER_SIZE; + } + + sdio_writeb(adapter->func, (nReadIdx + 1) % 16, + SDIO_C2H_RP_REG, NULL); + + t_len = len; + t_index = (SDIO_RX_BANK_ADDR + (SDIO_BANK_SIZE * nReadIdx) + 4); + t_buff = (u8 *)adapter->hw.receive_buffer; + + while (t_len) { + t_size = (t_len > CMC_BLOCK_SIZE) ? + (CMC_BLOCK_SIZE) : t_len; + err = sdio_memcpy_fromio(adapter->func, (void *)t_buff, + t_index, t_size); + t_len -= t_size; + t_buff += t_size; + t_index += t_size; + } + + if (unlikely(err || !len)) { + pr_debug("adapter_sdio_rx_packet : \ + error in receiving packet!!drop the \ + packet errt = %d, len = %d", err, len); + sdio_release_host(adapter->func); + continue; + } + sdio_release_host(adapter->func); + adapter_sdio_rx_packet(adapter, len); + } while (adapter); + + adapter->halted = true; + + pr_debug("cmc732_receive_thread exiting"); + + do_exit(0); + +return 0; +} |