blob: 3552b9e303599bdf1bdb0d35398312ce50db8f68 [file] [log] [blame]
// Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "global.h"
#include <linux/netlink.h>
#include <linux/rtnetlink.h>
#include "error.h"
#include "wimax.h"
#include "wm_ioctl.h"
#include "io.h"
#include "device.h"
#include "sdk.h"
#include "log.h"
#define dm_dev_ptr dev_mng.devices
dev_mng_t dev_mng;
static void dm_cleanup_dev(int dev_idx)
{
xfunc_in("dev=%d", dev_idx);
dm_device_lock(dev_idx);
if (dm_dev_ptr[dev_idx]) {
#if defined(FREE_DEVICE)
dm_dev_ptr[dev_idx]->ref_cnt = -1;
#endif
wm_cleanup_device(dev_idx);
sdk_free(dm_dev_ptr[dev_idx]);
dm_dev_ptr[dev_idx] = NULL;
}
dm_device_unlock(dev_idx);
xfunc_out();
}
static int dm_insert_device(int dev_idx, const char *dev_name, bool ind)
{
dev_mng_t *dm = &dev_mng;
device_t *dev;
xfunc_in("dev=%d, ind=%d", dev_idx, ind);
pthread_mutex_lock(&dm->detect_lock);
dev = dm_dev_ptr[dev_idx];
if (dev && dev->inserted) {
xprintf(SDK_ERR, "%s was already inserted\n", dev_name);
pthread_mutex_unlock(&dm->detect_lock);
return -1;
}
if (dm_dev_ptr[dev_idx] == NULL)
dm_dev_ptr[dev_idx] = (device_t *) sdk_malloc(sizeof(device_t));
dev = dm_dev_ptr[dev_idx];
memset(dev, 0, sizeof(device_t));
dev->inserted = TRUE;
strcpy(dev->name, dev_name);
dev->ind_thr = (pthread_t) NULL;
dev->net_fd = 0;
dev->io_nl.fd = 0;
dm->dev_cnt++;
pthread_mutex_unlock(&dm->detect_lock);
pthread_mutex_init(&dev->hci_wait_signal, NULL);
INIT_LIST_HEAD(&dev->hci_wait_list);
pthread_mutex_init(&dev->load_lock, NULL);
pthread_sem_init(&dev->sync_lock, 0);
if (ind)
sdk_ind_dev_insert_remove(dev_idx, TRUE);
xfunc_out("dev_cnt=%d", dm->dev_cnt);
return 0;
}
static int dm_remove_device(int dev_idx, const char *dev_name)
{
dev_mng_t *dm = &dev_mng;
device_t *dev = dm_dev_ptr[dev_idx];
sdk_internal_t *sdk;
if (!dev) {
xprintf(SDK_ERR, "%s was not inserted!\n", dev->name);
return sdk_set_errno(ERR_INVALID);
}
xfunc_in("dev=%d", dev_idx);
dev->inserted = FALSE;
if ((sdk = sdk_get_rw_handle()))
sdk_internal_device_close(sdk, dev_idx);
sdk_ind_dev_insert_remove(dev_idx, FALSE);
hci_destroy_resp(dev_idx);
pthread_mutex_destroy(&dev->hci_wait_signal);
io_receiver_delete(dev_idx);
pthread_sem_destroy(&dev->sync_lock);
pthread_mutex_lock(&dm->detect_lock);
dm_device_lock(dev_idx);
#if defined(FREE_DEVICE)
if (!dev->ref_cnt)
dm_cleanup_dev(dev_idx);
#endif
dm_device_unlock(dev_idx);
dm->dev_cnt--;
pthread_mutex_unlock(&dm->detect_lock);
xfunc_out("dev_cnt=%d", dm->dev_cnt);
return 0;
}
static int dm_device_listup(void)
{
FILE *fp;
char *dev_file = "/proc/net/dev";
char buf[512], *p_dev;
int index, ret = 0;
int dev_delimiter_pos = 6;
xfunc_in();
fp = fopen(dev_file, "rt");
if (fp == NULL) {
xprintf(SDK_STD_ERR, "%s fopen NULL\n", dev_file);
ret = sdk_set_errno(ERR_STD);
goto out;
}
while (fgets(buf, sizeof(buf), fp)) {
buf[dev_delimiter_pos] = '\0';
p_dev = strstr(buf, WM_DEV);
if (p_dev) {
if (sscanf(p_dev, WM_DEV"%d", &index) == 1) {
dm_insert_device(DEV_BASE_IDX+index, p_dev, FALSE);
ret++;
}
}
}
fclose(fp);
out:
xfunc_out();
return ret;
}
static int dm_detector_open(dev_mng_t *dm)
{
int fd;
struct sockaddr_nl sa;
fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
if (fd < 0) {
xprintf(SDK_STD_ERR, "detector open\n");
return sdk_set_errno(ERR_STD);
}
memset(&sa, 0, sizeof(sa));
sa.nl_family = AF_NETLINK;
sa.nl_groups = RTMGRP_LINK;
sa.nl_pid = 0;
if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
xprintf(SDK_STD_ERR, "detector binding\n", fd);
close(fd);
return sdk_set_errno(ERR_STD);
}
dm->det_fd = fd;
xprintf(SDK_DBG, "detector open, fd=%d\n", fd);
return fd;
}
static void dm_detector_close(dev_mng_t *dm)
{
if (dm->det_fd > 0) {
close(dm->det_fd);
xprintf(SDK_DBG, "detector close(%d)\n", dm->det_fd);
dm->det_fd = 0;
}
}
static int dm_detect_device(int fd)
{
#define NETDEV_REG_UNREG (~0U)
char buf[1024], *devname;
struct iovec iov = {buf, sizeof(buf) };
struct sockaddr_nl sa;
struct msghdr msg = {(void *)&sa, sizeof(sa), &iov, 1, NULL, 0, 0};
struct nlmsghdr *nlh;
int len, index;
struct ifinfomsg *ifi;
struct rtattr *rta;
int ret = 0;
assert(fd > 0);
len = recvmsg(fd, &msg, 0);
if (len <= (sizeof(struct nlmsghdr)+sizeof(struct ifinfomsg))) {
xprintf(SDK_STD_ERR, "recvmsg(%d), len=%d\n", fd, len);
ret = sdk_set_errno(ERR_STD);
goto out;
}
nlh = (struct nlmsghdr *)buf;
ifi = NLMSG_DATA(nlh);
if (ifi->ifi_change == NETDEV_REG_UNREG &&
(nlh->nlmsg_type == RTM_NEWLINK || nlh->nlmsg_type == RTM_DELLINK)) {
rta = IFLA_RTA(ifi);
len = nlh->nlmsg_len;
len -= NLMSG_LENGTH(sizeof(*ifi));
while (RTA_OK(rta, len)) {
if (rta->rta_type == IFLA_IFNAME) {
devname = RTA_DATA(rta);
if (sscanf(devname, WM_DEV "%d", &index) == 1) {
if (nlh->nlmsg_type == RTM_NEWLINK)
ret = dm_insert_device(DEV_BASE_IDX+index, devname, TRUE);
else { /*RTM_DELLINK*/
ret = dm_remove_device(DEV_BASE_IDX+index, devname);
/*
If Suspend or Hibernate is issued,
device is inserted immediately after removing.
In that case, we need a time closing device(i.e. sdk_device_close).
*/
sleep(2);
}
if (ret < 0)
goto out;
}
else
xprintf(SDK_DBG, "Other device=%s\n", devname);
break;
}
rta = RTA_NEXT(rta, len);
}
}
out:
return ret;
}
static void *dm_detector_thread(void *data)
{
dev_mng_t *dm = (dev_mng_t *) data;
int fd, ret;
pthread_mutex_init(&dm->detect_lock, NULL);
fd = dm_detector_open(dm);
if (fd < 0)
goto out;
while (1) {
ret = dm_detect_device(fd);
if (ret < 0)
break;
}
out:
dm_detector_close(dm);
return NULL;
}
int dm_init(void)
{
dev_mng_t *dm = &dev_mng;
int ret = 0;
xfunc_in();
memset(dm, 0, sizeof(dev_mng_t));
ret = dm_device_listup();
if (ret >= 0)
pthread_create(&dm->detect_thr, NULL, dm_detector_thread, (void *)dm);
xfunc_out();
return ret;
}
int dm_deinit(void)
{
dev_mng_t *dm = &dev_mng;
pthread_t thread;
int i;
xfunc_in();
pthread_mutex_lock(&dm->detect_lock);
for (i = 0; i < MAX_DEVICE; i++) {
if (dm_dev_ptr[i])
dm_cleanup_dev(i);
}
pthread_mutex_unlock(&dm->detect_lock);
if ((thread = dm->detect_thr)) {
dm->detect_thr = (pthread_t) NULL;
pthread_cancel(thread);
pthread_join(thread, NULL);
}
xfunc_out();
return 0;
}
device_t *dm_get_dev(int dev_idx)
{
device_t *dev;
dm_device_lock(dev_idx);
if (!(dev = dm_dev_ptr[dev_idx])) {
xprintf(SDK_ERR, "device(%d) is invalid\n", dev_idx);
sdk_set_errno(ERR_INVALID_DEV);
}
#if defined(FREE_DEVICE)
else
dev->ref_cnt++;
#endif
dm_device_unlock(dev_idx);
return dev;
}
#if defined(FREE_DEVICE)
void dm_put_dev(int dev_idx)
{
device_t *dev;
dm_device_lock(dev_idx);
dev = dm_dev_ptr[dev_idx];
assert(dev);
if (--dev->ref_cnt == 0 && !dev->inserted)
dm_cleanup_dev(dev_idx);
dm_device_unlock(dev_idx);
}
#endif
int dm_open_device(int dev_idx)
{
device_t *dev;
int ret = 0;
xfunc_in("dev=%d", dev_idx);
if (!(dev = dm_get_dev(dev_idx)))
return -1;
if (dev->open_cnt == 0) {
wm_open_device(dev_idx);
assert(dev->wimax->subs_list.head.prev && dev->wimax->subs_list.head.next);
io_receiver_create(dev_idx);
}
dev->open_cnt++;
xprintf(SDK_INFO, "[%d] open device, cnt=%d\n", dev_idx, dev->open_cnt);
dm_put_dev(dev_idx);
xfunc_out();
return ret;
}
int dm_close_device(int dev_idx)
{
device_t *dev;
int ret = 0;
xfunc_in("dev=%d", dev_idx);
if (!(dev = dm_get_dev(dev_idx)))
return 0;
if (dev->open_cnt == 0) {
xprintf(SDK_ERR, "dev(%d) was not opened!\n", dev_idx);
ret = sdk_set_errno(ERR_INVALID);
goto out;
}
dev->open_cnt--;
if (dev->open_cnt == 0) {
if (dev->inserted) {
if (dev->wimax->scan.sf_mode && sdk_get_rw_handle())
wm_disable_sf_mode(dev_idx);
ret = io_receiver_delete(dev_idx);
}
wm_close_device(dev_idx);
}
out:
xprintf(SDK_INFO, "[%d] close device, cnt=%d\n", dev_idx, dev->open_cnt);
dm_put_dev(dev_idx);
xfunc_out();
return ret;
}
int dm_get_status(int dev_idx, int *m_status, int *c_status)
{
fsm_t fsm;
int ret = 0;
xfunc_in("m_s=%d, c_s=%d", *m_status, *c_status);
if (net_ioctl_get_data(dev_idx, SIOC_DATA_FSM, &fsm, sizeof(fsm)) > 0) {
*m_status = fsm.m_status;
*c_status = fsm.c_status;
}
xfunc_out("m_s=%d, c_s=%d", *m_status, *c_status);
dm_put_dev(dev_idx);
return ret;
}
int dm_set_status(int dev_idx, int m_status, int c_status)
{
fsm_t fsm;
int ret = 0;
xfunc_in("m_s=%d, c_s=%d", m_status, c_status);
fsm.m_status = m_status;
fsm.c_status = c_status;
ret = net_ioctl_set_data(dev_idx, SIOC_DATA_FSM, &fsm, sizeof(fsm));
if (ret > 0) ret = 0;
xfunc_out("m_s=%d, c_s=%d", fsm.m_status, fsm.c_status);
dm_put_dev(dev_idx);
return ret;
}