/* * i2400m_msg_ack_hook - process cmd/set/get ack for internal status * * @i2400m: device descriptor * @l3l4_hdr: pointer to message; it has been already validated for * consistent size. * @size: size of the message * * Extract information we might need from acks to commands and act on * it. This is akin to i2400m_report_hook(). Note most of this * processing should be done in the function that calls the * command. This is here for some cases where it can't happen... */ static void i2400m_msg_ack_hook(struct i2400m *i2400m, const struct i2400m_l3l4_hdr *l3l4_hdr, size_t size) { int result; struct device *dev = i2400m_dev(i2400m); unsigned ack_type, ack_status; char strerr[32]; /* Chew on the message, we might need some information from * here */ ack_type = le16_to_cpu(l3l4_hdr->type); ack_status = le16_to_cpu(l3l4_hdr->status); switch (ack_type) { case I2400M_MT_CMD_ENTER_POWERSAVE: /* This is just left here for the sake of example, as * the processing is done somewhere else. */ if (0) { result = i2400m_msg_check_status( l3l4_hdr, strerr, sizeof(strerr)); if (result >= 0) d_printf(1, dev, "ready for power save: %zd\n", size); } break; } }
/* * Send an DoExitIdle command to the device to ask it to go out of * basestation-idle mode. * * @i2400m: device descriptor * * This starts a renegotiation with the basestation that might involve * another crypto handshake with user space. * * Returns: 0 if ok, < 0 errno code on error. */ int i2400m_cmd_exit_idle(struct i2400m *i2400m) { int result; struct device *dev = i2400m_dev(i2400m); struct sk_buff *ack_skb; struct i2400m_l3l4_hdr *cmd; char strerr[32]; result = -ENOMEM; cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); if (cmd == NULL) goto error_alloc; cmd->type = cpu_to_le16(I2400M_MT_CMD_EXIT_IDLE); cmd->length = 0; cmd->version = cpu_to_le16(I2400M_L3L4_VERSION); ack_skb = i2400m_msg_to_dev(i2400m, cmd, sizeof(*cmd)); result = PTR_ERR(ack_skb); if (IS_ERR(ack_skb)) { dev_err(dev, "Failed to issue 'exit idle' command: %d\n", result); goto error_msg_to_dev; } result = i2400m_msg_check_status(wimax_msg_data(ack_skb), strerr, sizeof(strerr)); kfree_skb(ack_skb); error_msg_to_dev: kfree(cmd); error_alloc: return result; }
/** * i2400m_get_device_info - Query the device for detailed device information * * @i2400m: device descriptor * * Returns: an skb whose skb->data points to a 'struct * i2400m_tlv_detailed_device_info'. When done, kfree_skb() it. The * skb is *guaranteed* to contain the whole TLV data structure. * * On error, IS_ERR(skb) is true and ERR_PTR(skb) is the error * code. */ struct sk_buff *i2400m_get_device_info(struct i2400m *i2400m) { int result; struct device *dev = i2400m_dev(i2400m); struct sk_buff *ack_skb; struct i2400m_l3l4_hdr *cmd; const struct i2400m_l3l4_hdr *ack; size_t ack_len; const struct i2400m_tlv_hdr *tlv; const struct i2400m_tlv_detailed_device_info *ddi; char strerr[32]; ack_skb = ERR_PTR(-ENOMEM); cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); if (cmd == NULL) goto error_alloc; cmd->type = cpu_to_le16(I2400M_MT_GET_DEVICE_INFO); cmd->length = 0; cmd->version = cpu_to_le16(I2400M_L3L4_VERSION); ack_skb = i2400m_msg_to_dev(i2400m, cmd, sizeof(*cmd)); if (IS_ERR(ack_skb)) { dev_err(dev, "Failed to issue 'get device info' command: %ld\n", PTR_ERR(ack_skb)); goto error_msg_to_dev; } ack = wimax_msg_data_len(ack_skb, &ack_len); result = i2400m_msg_check_status(ack, strerr, sizeof(strerr)); if (result < 0) { dev_err(dev, "'get device info' (0x%04x) command failed: " "%d - %s\n", I2400M_MT_GET_DEVICE_INFO, result, strerr); goto error_cmd_failed; } tlv = i2400m_tlv_find(i2400m, ack->pl, ack_len - sizeof(*ack), I2400M_TLV_DETAILED_DEVICE_INFO, sizeof(*ddi)); if (tlv == NULL) { dev_err(dev, "GET DEVICE INFO: " "detailed device info TLV not found (0x%04x)\n", I2400M_TLV_DETAILED_DEVICE_INFO); result = -EIO; goto error_no_tlv; } skb_pull(ack_skb, (void *) tlv - (void *) ack_skb->data); error_msg_to_dev: kfree(cmd); error_alloc: return ack_skb; error_no_tlv: error_cmd_failed: kfree_skb(ack_skb); kfree(cmd); return ERR_PTR(result); }
/** * i2400m_set_idle_timeout - Set the device's idle mode timeout * * @i2400m: i2400m device descriptor * * @msecs: milliseconds for the timeout to enter idle mode. Between * 100 to 300000 (5m); 0 to disable. In increments of 100. * * After this @msecs of the link being idle (no data being sent or * received), the device will negotiate with the basestation entering * idle mode for saving power. The connection is maintained, but * getting out of it (done in tx.c) will require some negotiation, * possible crypto re-handshake and a possible DHCP re-lease. * * Only available if fw_version >= 0x00090002. * * Returns: 0 if ok, < 0 errno code on error. */ int i2400m_set_idle_timeout(struct i2400m *i2400m, unsigned msecs) { int result; struct device *dev = i2400m_dev(i2400m); struct sk_buff *ack_skb; struct { struct i2400m_l3l4_hdr hdr; struct i2400m_tlv_config_idle_timeout cit; } *cmd; const struct i2400m_l3l4_hdr *ack; size_t ack_len; char strerr[32]; result = -ENOSYS; if (i2400m_le_v1_3(i2400m)) goto error_alloc; result = -ENOMEM; cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); if (cmd == NULL) goto error_alloc; cmd->hdr.type = cpu_to_le16(I2400M_MT_GET_STATE); cmd->hdr.length = cpu_to_le16(sizeof(*cmd) - sizeof(cmd->hdr)); cmd->hdr.version = cpu_to_le16(I2400M_L3L4_VERSION); cmd->cit.hdr.type = cpu_to_le16(I2400M_TLV_CONFIG_IDLE_TIMEOUT); cmd->cit.hdr.length = cpu_to_le16(sizeof(cmd->cit.timeout)); cmd->cit.timeout = cpu_to_le32(msecs); ack_skb = i2400m_msg_to_dev(i2400m, cmd, sizeof(*cmd)); if (IS_ERR(ack_skb)) { dev_err(dev, "Failed to issue 'set idle timeout' command: " "%ld\n", PTR_ERR(ack_skb)); result = PTR_ERR(ack_skb); goto error_msg_to_dev; } ack = wimax_msg_data_len(ack_skb, &ack_len); result = i2400m_msg_check_status(ack, strerr, sizeof(strerr)); if (result < 0) { dev_err(dev, "'set idle timeout' (0x%04x) command failed: " "%d - %s\n", I2400M_MT_GET_STATE, result, strerr); goto error_cmd_failed; } result = 0; kfree_skb(ack_skb); error_cmd_failed: error_msg_to_dev: kfree(cmd); error_alloc: return result; }
/* * Request entering power save * * This command is (mainly) executed when the device indicates that it * is ready to go into powersave mode via a REPORT_POWERSAVE_READY. */ int i2400m_cmd_enter_powersave(struct i2400m *i2400m) { int result; struct device *dev = i2400m_dev(i2400m); struct sk_buff *ack_skb; struct i2400m_cmd_enter_power_save *cmd; char strerr[32]; result = -ENOMEM; cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); if (cmd == NULL) goto error_alloc; cmd->hdr.type = cpu_to_le16(I2400M_MT_CMD_ENTER_POWERSAVE); cmd->hdr.length = cpu_to_le16(sizeof(*cmd) - sizeof(cmd->hdr)); cmd->hdr.version = cpu_to_le16(I2400M_L3L4_VERSION); cmd->tlv.type = cpu_to_le16(I2400M_TLV_TYPE_WAKEUP_MODE); cmd->tlv.length = cpu_to_le16(sizeof(cmd->val)); cmd->val = cpu_to_le32(I2400M_WAKEUP_ENABLED); ack_skb = i2400m_msg_to_dev(i2400m, cmd, sizeof(*cmd)); result = PTR_ERR(ack_skb); if (IS_ERR(ack_skb)) { dev_err(dev, "Failed to issue 'Enter power save' command: %d\n", result); goto error_msg_to_dev; } result = i2400m_msg_check_status(wimax_msg_data(ack_skb), strerr, sizeof(strerr)); if (result == -EACCES) d_printf(1, dev, "Cannot enter power save mode\n"); else if (result < 0) dev_err(dev, "'Enter power save' (0x%04x) command failed: " "%d - %s\n", I2400M_MT_CMD_ENTER_POWERSAVE, result, strerr); else d_printf(1, dev, "device ready to power save\n"); kfree_skb(ack_skb); error_msg_to_dev: kfree(cmd); error_alloc: return result; }
/* * Query the device for its state, update the WiMAX stack's idea of it * * @i2400m: device descriptor * * Returns: 0 if ok, < 0 errno code on error. * * Executes a 'Get State' command and parses the returned * TLVs. * * Because this is almost identical to a 'Report State', we use * i2400m_report_state_hook() to parse the answer. This will set the * carrier state, as well as the RF Kill switches state. */ static int i2400m_cmd_get_state(struct i2400m *i2400m) { int result; struct device *dev = i2400m_dev(i2400m); struct sk_buff *ack_skb; struct i2400m_l3l4_hdr *cmd; const struct i2400m_l3l4_hdr *ack; size_t ack_len; char strerr[32]; result = -ENOMEM; cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); if (cmd == NULL) goto error_alloc; cmd->type = cpu_to_le16(I2400M_MT_GET_STATE); cmd->length = 0; cmd->version = cpu_to_le16(I2400M_L3L4_VERSION); ack_skb = i2400m_msg_to_dev(i2400m, cmd, sizeof(*cmd)); if (IS_ERR(ack_skb)) { dev_err(dev, "Failed to issue 'get state' command: %ld\n", PTR_ERR(ack_skb)); result = PTR_ERR(ack_skb); goto error_msg_to_dev; } ack = wimax_msg_data_len(ack_skb, &ack_len); result = i2400m_msg_check_status(ack, strerr, sizeof(strerr)); if (result < 0) { dev_err(dev, "'get state' (0x%04x) command failed: " "%d - %s\n", I2400M_MT_GET_STATE, result, strerr); goto error_cmd_failed; } i2400m_report_state_hook(i2400m, ack, ack_len - sizeof(*ack), "GET STATE"); result = 0; kfree_skb(ack_skb); error_cmd_failed: error_msg_to_dev: kfree(cmd); error_alloc: return result; }
static void i2400m_msg_ack_hook(struct i2400m *i2400m, const struct i2400m_l3l4_hdr *l3l4_hdr, size_t size) { int result; struct device *dev = i2400m_dev(i2400m); unsigned ack_type, ack_status; char strerr[32]; ack_type = le16_to_cpu(l3l4_hdr->type); ack_status = le16_to_cpu(l3l4_hdr->status); switch (ack_type) { case I2400M_MT_CMD_ENTER_POWERSAVE: if (0) { result = i2400m_msg_check_status( l3l4_hdr, strerr, sizeof(strerr)); if (result >= 0) d_printf(1, dev, "ready for power save: %zd\n", size); } break; } }
/** * Set basic configuration settings * * @i2400m: device descriptor * @args: array of pointers to the TLV headers to send for * configuration (each followed by its payload). * TLV headers and payloads must be properly initialized, with the * right endianess (LE). * @arg_size: number of pointers in the @args array */ static int i2400m_set_init_config(struct i2400m *i2400m, const struct i2400m_tlv_hdr **arg, size_t args) { int result; struct device *dev = i2400m_dev(i2400m); struct sk_buff *ack_skb; struct i2400m_l3l4_hdr *cmd; char strerr[32]; unsigned argc, argsize, tlv_size; const struct i2400m_tlv_hdr *tlv_hdr; void *buf, *itr; d_fnstart(3, dev, "(i2400m %p arg %p args %zu)\n", i2400m, arg, args); result = 0; if (args == 0) goto none; /* Compute the size of all the TLVs, so we can alloc a * contiguous command block to copy them. */ argsize = 0; for (argc = 0; argc < args; argc++) { tlv_hdr = arg[argc]; argsize += sizeof(*tlv_hdr) + le16_to_cpu(tlv_hdr->length); } WARN_ON(argc >= 9); /* As per hw spec */ /* Alloc the space for the command and TLVs*/ result = -ENOMEM; buf = kzalloc(sizeof(*cmd) + argsize, GFP_KERNEL); if (buf == NULL) goto error_alloc; cmd = buf; cmd->type = cpu_to_le16(I2400M_MT_SET_INIT_CONFIG); cmd->length = cpu_to_le16(argsize); cmd->version = cpu_to_le16(I2400M_L3L4_VERSION); /* Copy the TLVs */ itr = buf + sizeof(*cmd); for (argc = 0; argc < args; argc++) { tlv_hdr = arg[argc]; tlv_size = sizeof(*tlv_hdr) + le16_to_cpu(tlv_hdr->length); memcpy(itr, tlv_hdr, tlv_size); itr += tlv_size; } /* Send the message! */ ack_skb = i2400m_msg_to_dev(i2400m, buf, sizeof(*cmd) + argsize); result = PTR_ERR(ack_skb); if (IS_ERR(ack_skb)) { dev_err(dev, "Failed to issue 'init config' command: %d\n", result); goto error_msg_to_dev; } result = i2400m_msg_check_status(wimax_msg_data(ack_skb), strerr, sizeof(strerr)); if (result < 0) dev_err(dev, "'init config' (0x%04x) command failed: %d - %s\n", I2400M_MT_SET_INIT_CONFIG, result, strerr); kfree_skb(ack_skb); error_msg_to_dev: kfree(buf); error_alloc: none: d_fnend(3, dev, "(i2400m %p arg %p args %zu) = %d\n", i2400m, arg, args, result); return result; }
/** * i2400m_firmware_check - check firmware versions are compatible with * the driver * * @i2400m: device descriptor * * Returns: 0 if ok, < 0 errno code an error and a message in the * kernel log. * * Long function, but quite simple; first chunk launches the command * and double checks the reply for the right TLV. Then we process the * TLV (where the meat is). * * Once we process the TLV that gives us the firmware's interface * version, we encode it and save it in i2400m->fw_version for future * reference. */ int i2400m_firmware_check(struct i2400m *i2400m) { int result; struct device *dev = i2400m_dev(i2400m); struct sk_buff *ack_skb; struct i2400m_l3l4_hdr *cmd; const struct i2400m_l3l4_hdr *ack; size_t ack_len; const struct i2400m_tlv_hdr *tlv; const struct i2400m_tlv_l4_message_versions *l4mv; char strerr[32]; unsigned major, minor, branch; result = -ENOMEM; cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); if (cmd == NULL) goto error_alloc; cmd->type = cpu_to_le16(I2400M_MT_GET_LM_VERSION); cmd->length = 0; cmd->version = cpu_to_le16(I2400M_L3L4_VERSION); ack_skb = i2400m_msg_to_dev(i2400m, cmd, sizeof(*cmd)); if (IS_ERR(ack_skb)) { result = PTR_ERR(ack_skb); dev_err(dev, "Failed to issue 'get lm version' command: %-d\n", result); goto error_msg_to_dev; } ack = wimax_msg_data_len(ack_skb, &ack_len); result = i2400m_msg_check_status(ack, strerr, sizeof(strerr)); if (result < 0) { dev_err(dev, "'get lm version' (0x%04x) command failed: " "%d - %s\n", I2400M_MT_GET_LM_VERSION, result, strerr); goto error_cmd_failed; } tlv = i2400m_tlv_find(i2400m, ack->pl, ack_len - sizeof(*ack), I2400M_TLV_L4_MESSAGE_VERSIONS, sizeof(*l4mv)); if (tlv == NULL) { dev_err(dev, "get lm version: TLV not found (0x%04x)\n", I2400M_TLV_L4_MESSAGE_VERSIONS); result = -EIO; goto error_no_tlv; } l4mv = container_of(tlv, typeof(*l4mv), hdr); major = le16_to_cpu(l4mv->major); minor = le16_to_cpu(l4mv->minor); branch = le16_to_cpu(l4mv->branch); result = -EINVAL; if (major != I2400M_HDIv_MAJOR) { dev_err(dev, "unsupported major fw version " "%u.%u.%u\n", major, minor, branch); goto error_bad_major; } result = 0; if (minor < I2400M_HDIv_MINOR_2 && minor > I2400M_HDIv_MINOR) dev_warn(dev, "untested minor fw version %u.%u.%u\n", major, minor, branch); /* Yes, we ignore the branch -- we don't have to track it */ i2400m->fw_version = major << 16 | minor; dev_info(dev, "firmware interface version %u.%u.%u\n", major, minor, branch); error_bad_major: error_no_tlv: error_cmd_failed: kfree_skb(ack_skb); error_msg_to_dev: kfree(cmd); error_alloc: return result; }
int i2400m_op_rfkill_sw_toggle(struct wimax_dev *wimax_dev, enum wimax_rf_state state) { int result; struct i2400m *i2400m = wimax_dev_to_i2400m(wimax_dev); struct device *dev = i2400m_dev(i2400m); struct sk_buff *ack_skb; struct { struct i2400m_l3l4_hdr hdr; struct i2400m_tlv_rf_operation sw_rf; } __attribute__((packed)) *cmd; char strerr[32]; d_fnstart(4, dev, "(wimax_dev %p state %d)\n", wimax_dev, state); result = -ENOMEM; cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); if (cmd == NULL) goto error_alloc; cmd->hdr.type = cpu_to_le16(I2400M_MT_CMD_RF_CONTROL); cmd->hdr.length = sizeof(cmd->sw_rf); cmd->hdr.version = cpu_to_le16(I2400M_L3L4_VERSION); cmd->sw_rf.hdr.type = cpu_to_le16(I2400M_TLV_RF_OPERATION); cmd->sw_rf.hdr.length = cpu_to_le16(sizeof(cmd->sw_rf.status)); switch (state) { case WIMAX_RF_OFF: cmd->sw_rf.status = cpu_to_le32(2); break; case WIMAX_RF_ON: cmd->sw_rf.status = cpu_to_le32(1); break; default: BUG(); } ack_skb = i2400m_msg_to_dev(i2400m, cmd, sizeof(*cmd)); result = PTR_ERR(ack_skb); if (IS_ERR(ack_skb)) { dev_err(dev, "Failed to issue 'RF Control' command: %d\n", result); goto error_msg_to_dev; } result = i2400m_msg_check_status(wimax_msg_data(ack_skb), strerr, sizeof(strerr)); if (result < 0) { dev_err(dev, "'RF Control' (0x%04x) command failed: %d - %s\n", I2400M_MT_CMD_RF_CONTROL, result, strerr); goto error_cmd; } result = wait_event_timeout( i2400m->state_wq, i2400m_radio_is(i2400m, state), 5 * HZ); if (result == 0) result = -ETIMEDOUT; if (result < 0) dev_err(dev, "Error waiting for device to toggle RF state: " "%d\n", result); result = 0; error_cmd: kfree_skb(ack_skb); error_msg_to_dev: error_alloc: d_fnend(4, dev, "(wimax_dev %p state %d) = %d\n", wimax_dev, state, result); return result; }