#include <sys/policy.h>
#include <sys/conf.h>
#include <sys/modctl.h>
#include <sys/priv_names.h>
#include <sys/dlpi.h>
#include <net/simnet.h>
#include <sys/ethernet.h>
#include <sys/mac.h>
#include <sys/dls.h>
#include <sys/mac_ether.h>
#include <sys/mac_provider.h>
#include <sys/mac_client_priv.h>
#include <sys/vlan.h>
#include <sys/random.h>
#include <sys/sysmacros.h>
#include <sys/list.h>
#include <sys/strsubr.h>
#include <sys/strsun.h>
#include <sys/atomic.h>
#include <sys/mac_wifi.h>
#include <sys/mac_impl.h>
#include <sys/pattr.h>
#include <inet/wifi_ioctl.h>
#include <sys/thread.h>
#include <sys/synch.h>
#include <sys/sunddi.h>
#include "simnet_impl.h"
#define SIMNETINFO "Simulated Network Driver"
static dev_info_t *simnet_dip;
static ddi_taskq_t *simnet_rxq;
static int simnet_getinfo(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int simnet_attach(dev_info_t *, ddi_attach_cmd_t);
static int simnet_detach(dev_info_t *, ddi_detach_cmd_t);
static int simnet_ioc_create(void *, intptr_t, int, cred_t *, int *);
static int simnet_ioc_delete(void *, intptr_t, int, cred_t *, int *);
static int simnet_ioc_info(void *, intptr_t, int, cred_t *, int *);
static int simnet_ioc_modify(void *, intptr_t, int, cred_t *, int *);
static const struct ether_addr *mcastaddr_lookup(const simnet_dev_t *,
const uint8_t *);
static dld_ioc_info_t simnet_ioc_list[] = {
{SIMNET_IOC_CREATE, DLDCOPYINOUT, sizeof (simnet_ioc_create_t),
simnet_ioc_create, secpolicy_dl_config},
{SIMNET_IOC_DELETE, DLDCOPYIN, sizeof (simnet_ioc_delete_t),
simnet_ioc_delete, secpolicy_dl_config},
{SIMNET_IOC_INFO, DLDCOPYINOUT, sizeof (simnet_ioc_info_t),
simnet_ioc_info, NULL},
{SIMNET_IOC_MODIFY, DLDCOPYIN, sizeof (simnet_ioc_modify_t),
simnet_ioc_modify, secpolicy_dl_config}
};
DDI_DEFINE_STREAM_OPS(simnet_dev_ops, nulldev, nulldev, simnet_attach,
simnet_detach, nodev, simnet_getinfo, D_MP, NULL,
ddi_quiesce_not_supported);
static struct modldrv simnet_modldrv = {
&mod_driverops,
SIMNETINFO,
&simnet_dev_ops
};
static struct modlinkage modlinkage = {
MODREV_1, &simnet_modldrv, NULL
};
static int simnet_m_start(void *);
static void simnet_m_stop(void *);
static int simnet_m_promisc(void *, boolean_t);
static int simnet_m_multicst(void *, boolean_t, const uint8_t *);
static int simnet_m_unicst(void *, const uint8_t *);
static int simnet_m_stat(void *, uint_t, uint64_t *);
static void simnet_m_ioctl(void *, queue_t *, mblk_t *);
static mblk_t *simnet_m_tx(void *, mblk_t *);
static int simnet_m_setprop(void *, const char *, mac_prop_id_t,
const uint_t, const void *);
static int simnet_m_getprop(void *, const char *, mac_prop_id_t,
uint_t, void *);
static void simnet_m_propinfo(void *, const char *, mac_prop_id_t,
mac_prop_info_handle_t);
static boolean_t simnet_m_getcapab(void *, mac_capab_t, void *);
static mac_callbacks_t simnet_m_callbacks = {
(MC_IOCTL | MC_GETCAPAB | MC_SETPROP | MC_GETPROP | MC_PROPINFO),
simnet_m_stat,
simnet_m_start,
simnet_m_stop,
simnet_m_promisc,
simnet_m_multicst,
simnet_m_unicst,
simnet_m_tx,
NULL,
simnet_m_ioctl,
simnet_m_getcapab,
NULL,
NULL,
simnet_m_setprop,
simnet_m_getprop,
simnet_m_propinfo
};
static krwlock_t simnet_dev_lock;
static list_t simnet_dev_list;
static int simnet_count;
int
_init(void)
{
int status;
mac_init_ops(&simnet_dev_ops, "simnet");
status = mod_install(&modlinkage);
if (status != DDI_SUCCESS)
mac_fini_ops(&simnet_dev_ops);
return (status);
}
int
_fini(void)
{
int status;
status = mod_remove(&modlinkage);
if (status == DDI_SUCCESS)
mac_fini_ops(&simnet_dev_ops);
return (status);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
static boolean_t
simnet_init(void)
{
if ((simnet_rxq = ddi_taskq_create(simnet_dip, "simnet", 1,
TASKQ_DEFAULTPRI, 0)) == NULL)
return (B_FALSE);
rw_init(&simnet_dev_lock, NULL, RW_DEFAULT, NULL);
list_create(&simnet_dev_list, sizeof (simnet_dev_t),
offsetof(simnet_dev_t, sd_listnode));
return (B_TRUE);
}
static void
simnet_fini(void)
{
ASSERT(simnet_count == 0);
rw_destroy(&simnet_dev_lock);
list_destroy(&simnet_dev_list);
ddi_taskq_destroy(simnet_rxq);
}
static int
simnet_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
void **result)
{
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
*result = simnet_dip;
return (DDI_SUCCESS);
case DDI_INFO_DEVT2INSTANCE:
*result = NULL;
return (DDI_SUCCESS);
}
return (DDI_FAILURE);
}
static int
simnet_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
switch (cmd) {
case DDI_ATTACH:
if (ddi_get_instance(dip) != 0) {
return (DDI_FAILURE);
}
if (dld_ioc_register(SIMNET_IOC, simnet_ioc_list,
DLDIOCCNT(simnet_ioc_list)) != 0)
return (DDI_FAILURE);
simnet_dip = dip;
if (!simnet_init())
return (DDI_FAILURE);
return (DDI_SUCCESS);
case DDI_RESUME:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
static int
simnet_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
switch (cmd) {
case DDI_DETACH:
if (simnet_count > 0)
return (DDI_FAILURE);
dld_ioc_unregister(SIMNET_IOC);
simnet_fini();
simnet_dip = NULL;
return (DDI_SUCCESS);
case DDI_SUSPEND:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
static simnet_dev_t *
simnet_dev_lookup(datalink_id_t link_id)
{
simnet_dev_t *sdev;
ASSERT(RW_LOCK_HELD(&simnet_dev_lock));
for (sdev = list_head(&simnet_dev_list); sdev != NULL;
sdev = list_next(&simnet_dev_list, sdev)) {
if (!(sdev->sd_flags & SDF_SHUTDOWN) &&
(sdev->sd_link_id == link_id)) {
atomic_inc_32(&sdev->sd_refcount);
return (sdev);
}
}
return (NULL);
}
static void
simnet_wifidev_free(simnet_dev_t *sdev)
{
simnet_wifidev_t *wdev = sdev->sd_wifidev;
int i;
for (i = 0; i < wdev->swd_esslist_num; i++) {
kmem_free(wdev->swd_esslist[i],
sizeof (wl_ess_conf_t));
}
kmem_free(wdev, sizeof (simnet_wifidev_t));
}
static void
simnet_dev_unref(simnet_dev_t *sdev)
{
ASSERT(sdev->sd_refcount > 0);
if (atomic_dec_32_nv(&sdev->sd_refcount) != 0)
return;
if (sdev->sd_mh != NULL)
(void) mac_unregister(sdev->sd_mh);
if (sdev->sd_wifidev != NULL) {
ASSERT(sdev->sd_type == DL_WIFI);
simnet_wifidev_free(sdev);
}
mutex_destroy(&sdev->sd_instlock);
cv_destroy(&sdev->sd_threadwait);
kmem_free(sdev, sizeof (*sdev));
simnet_count--;
}
static int
simnet_init_wifi(simnet_dev_t *sdev, mac_register_t *mac)
{
wifi_data_t wd = { 0 };
int err;
sdev->sd_wifidev = kmem_zalloc(sizeof (simnet_wifidev_t), KM_NOSLEEP);
if (sdev->sd_wifidev == NULL)
return (ENOMEM);
sdev->sd_wifidev->swd_sdev = sdev;
sdev->sd_wifidev->swd_linkstatus = WL_NOTCONNECTED;
wd.wd_secalloc = WIFI_SEC_NONE;
wd.wd_opmode = IEEE80211_M_STA;
mac->m_type_ident = MAC_PLUGIN_IDENT_WIFI;
mac->m_max_sdu = IEEE80211_MTU;
mac->m_pdata = &wd;
mac->m_pdata_size = sizeof (wd);
err = mac_register(mac, &sdev->sd_mh);
return (err);
}
static int
simnet_init_ether(simnet_dev_t *sdev, mac_register_t *mac)
{
int err;
mac->m_type_ident = MAC_PLUGIN_IDENT_ETHER;
mac->m_max_sdu = SIMNET_MAX_MTU;
mac->m_margin = VLAN_TAGSZ;
err = mac_register(mac, &sdev->sd_mh);
return (err);
}
static int
simnet_init_mac(simnet_dev_t *sdev)
{
mac_register_t *mac;
int err;
if ((mac = mac_alloc(MAC_VERSION)) == NULL)
return (ENOMEM);
mac->m_driver = sdev;
mac->m_dip = simnet_dip;
mac->m_instance = (uint_t)-1;
mac->m_src_addr = sdev->sd_mac_addr;
mac->m_callbacks = &simnet_m_callbacks;
mac->m_min_sdu = 0;
if (sdev->sd_type == DL_ETHER)
err = simnet_init_ether(sdev, mac);
else if (sdev->sd_type == DL_WIFI)
err = simnet_init_wifi(sdev, mac);
else
err = EINVAL;
mac_free(mac);
return (err);
}
static int
simnet_ioc_create(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
{
simnet_ioc_create_t *create_arg = karg;
simnet_dev_t *sdev;
simnet_dev_t *sdev_tmp;
int err = 0;
sdev = kmem_zalloc(sizeof (*sdev), KM_NOSLEEP);
if (sdev == NULL)
return (ENOMEM);
rw_enter(&simnet_dev_lock, RW_WRITER);
if ((sdev_tmp = simnet_dev_lookup(create_arg->sic_link_id)) != NULL) {
simnet_dev_unref(sdev_tmp);
rw_exit(&simnet_dev_lock);
kmem_free(sdev, sizeof (*sdev));
return (EEXIST);
}
sdev->sd_ls = LINK_STATE_UNKNOWN;
sdev->sd_type = create_arg->sic_type;
sdev->sd_link_id = create_arg->sic_link_id;
sdev->sd_zoneid = crgetzoneid(cred);
sdev->sd_refcount++;
mutex_init(&sdev->sd_instlock, NULL, MUTEX_DRIVER, NULL);
cv_init(&sdev->sd_threadwait, NULL, CV_DRIVER, NULL);
simnet_count++;
if (create_arg->sic_mac_len == 0) {
(void) random_get_pseudo_bytes(sdev->sd_mac_addr, ETHERADDRL);
sdev->sd_mac_addr[0] = (sdev->sd_mac_addr[0] & ~1) | 2;
sdev->sd_mac_len = ETHERADDRL;
} else {
(void) memcpy(sdev->sd_mac_addr, create_arg->sic_mac_addr,
create_arg->sic_mac_len);
sdev->sd_mac_len = create_arg->sic_mac_len;
}
if ((err = simnet_init_mac(sdev)) != 0) {
simnet_dev_unref(sdev);
goto exit;
}
if ((err = dls_devnet_create(sdev->sd_mh, sdev->sd_link_id,
crgetzoneid(cred))) != 0) {
simnet_dev_unref(sdev);
goto exit;
}
sdev->sd_ls = LINK_STATE_UP;
mac_link_update(sdev->sd_mh, LINK_STATE_UP);
mac_tx_update(sdev->sd_mh);
list_insert_tail(&simnet_dev_list, sdev);
(void) memcpy(create_arg->sic_mac_addr, sdev->sd_mac_addr,
sdev->sd_mac_len);
create_arg->sic_mac_len = sdev->sd_mac_len;
exit:
rw_exit(&simnet_dev_lock);
return (err);
}
static datalink_id_t
simnet_remove_peer(simnet_dev_t *sdev)
{
simnet_dev_t *sdev_peer;
datalink_id_t peer_link_id = DATALINK_INVALID_LINKID;
ASSERT(RW_WRITE_HELD(&simnet_dev_lock));
if ((sdev_peer = sdev->sd_peer_dev) != NULL) {
ASSERT(sdev == sdev_peer->sd_peer_dev);
sdev_peer->sd_peer_dev = NULL;
sdev->sd_peer_dev = NULL;
peer_link_id = sdev_peer->sd_link_id;
simnet_dev_unref(sdev_peer);
simnet_dev_unref(sdev);
}
return (peer_link_id);
}
static int
simnet_ioc_modify(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
{
simnet_ioc_modify_t *modify_arg = karg;
simnet_dev_t *sdev;
simnet_dev_t *sdev_peer = NULL;
rw_enter(&simnet_dev_lock, RW_WRITER);
if ((sdev = simnet_dev_lookup(modify_arg->sim_link_id)) == NULL) {
rw_exit(&simnet_dev_lock);
return (ENOENT);
}
if (sdev->sd_zoneid != crgetzoneid(cred)) {
rw_exit(&simnet_dev_lock);
simnet_dev_unref(sdev);
return (ENOENT);
}
if (sdev->sd_link_id == modify_arg->sim_peer_link_id) {
rw_exit(&simnet_dev_lock);
simnet_dev_unref(sdev);
return (EINVAL);
}
if (sdev->sd_peer_dev != NULL && sdev->sd_peer_dev->sd_link_id ==
modify_arg->sim_peer_link_id) {
rw_exit(&simnet_dev_lock);
simnet_dev_unref(sdev);
return (0);
}
if (modify_arg->sim_peer_link_id != DATALINK_INVALID_LINKID) {
sdev_peer = simnet_dev_lookup(modify_arg->sim_peer_link_id);
if (sdev_peer == NULL) {
rw_exit(&simnet_dev_lock);
simnet_dev_unref(sdev);
return (ENOENT);
}
if (sdev_peer->sd_zoneid != sdev->sd_zoneid) {
rw_exit(&simnet_dev_lock);
simnet_dev_unref(sdev);
simnet_dev_unref(sdev_peer);
return (EACCES);
}
}
(void) simnet_remove_peer(sdev);
if (sdev_peer != NULL) {
(void) simnet_remove_peer(sdev_peer);
sdev_peer->sd_peer_dev = sdev;
sdev->sd_peer_dev = sdev_peer;
} else {
simnet_dev_unref(sdev);
}
rw_exit(&simnet_dev_lock);
return (0);
}
static int
simnet_ioc_delete(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
{
int err;
simnet_dev_t *sdev;
simnet_dev_t *sdev_peer;
simnet_ioc_delete_t *delete_arg = karg;
datalink_id_t tmpid;
datalink_id_t peerid;
rw_enter(&simnet_dev_lock, RW_WRITER);
if ((sdev = simnet_dev_lookup(delete_arg->sid_link_id)) == NULL) {
rw_exit(&simnet_dev_lock);
return (ENOENT);
}
if (sdev->sd_zoneid != crgetzoneid(cred)) {
rw_exit(&simnet_dev_lock);
simnet_dev_unref(sdev);
return (ENOENT);
}
if ((err = dls_devnet_destroy(sdev->sd_mh, &tmpid, B_TRUE)) != 0) {
rw_exit(&simnet_dev_lock);
simnet_dev_unref(sdev);
return (err);
}
ASSERT(sdev->sd_link_id == tmpid);
peerid = simnet_remove_peer(sdev);
mutex_enter(&sdev->sd_instlock);
sdev->sd_flags |= SDF_SHUTDOWN;
while (sdev->sd_threadcount > 0) {
if (cv_wait_sig(&sdev->sd_threadwait,
&sdev->sd_instlock) == 0) {
mutex_exit(&sdev->sd_instlock);
err = EINTR;
goto fail;
}
}
mutex_exit(&sdev->sd_instlock);
if ((err = mac_disable(sdev->sd_mh)) != 0)
goto fail;
list_remove(&simnet_dev_list, sdev);
rw_exit(&simnet_dev_lock);
simnet_dev_unref(sdev);
simnet_dev_unref(sdev);
return (err);
fail:
(void) dls_devnet_create(sdev->sd_mh, sdev->sd_link_id,
crgetzoneid(cred));
sdev->sd_flags &= ~SDF_SHUTDOWN;
ASSERT(sdev->sd_peer_dev == NULL);
if (peerid != DATALINK_INVALID_LINKID &&
((sdev_peer = simnet_dev_lookup(peerid)) != NULL)) {
ASSERT(sdev_peer->sd_peer_dev == NULL);
sdev_peer->sd_peer_dev = sdev;
sdev->sd_peer_dev = sdev_peer;
} else {
simnet_dev_unref(sdev);
}
rw_exit(&simnet_dev_lock);
return (err);
}
static int
simnet_ioc_info(void *karg, intptr_t arg, int mode, cred_t *cred, int *rvalp)
{
simnet_ioc_info_t *info_arg = karg;
simnet_dev_t *sdev;
if (!dls_devnet_islinkvisible(info_arg->sii_link_id, crgetzoneid(cred)))
return (ENOENT);
rw_enter(&simnet_dev_lock, RW_READER);
if ((sdev = simnet_dev_lookup(info_arg->sii_link_id)) == NULL) {
rw_exit(&simnet_dev_lock);
return (ENOENT);
}
(void) memcpy(info_arg->sii_mac_addr, sdev->sd_mac_addr,
sdev->sd_mac_len);
info_arg->sii_mac_len = sdev->sd_mac_len;
info_arg->sii_type = sdev->sd_type;
if (sdev->sd_peer_dev != NULL)
info_arg->sii_peer_link_id = sdev->sd_peer_dev->sd_link_id;
rw_exit(&simnet_dev_lock);
simnet_dev_unref(sdev);
return (0);
}
static boolean_t
simnet_thread_ref(simnet_dev_t *sdev)
{
mutex_enter(&sdev->sd_instlock);
if (sdev->sd_flags & SDF_SHUTDOWN ||
!(sdev->sd_flags & SDF_STARTED)) {
mutex_exit(&sdev->sd_instlock);
return (B_FALSE);
}
sdev->sd_threadcount++;
mutex_exit(&sdev->sd_instlock);
return (B_TRUE);
}
static void
simnet_thread_unref(simnet_dev_t *sdev)
{
mutex_enter(&sdev->sd_instlock);
if (--sdev->sd_threadcount == 0)
cv_broadcast(&sdev->sd_threadwait);
mutex_exit(&sdev->sd_instlock);
}
static void
simnet_rx(void *arg)
{
mblk_t *mp = arg;
mac_header_info_t hdr_info;
simnet_dev_t *sdev;
sdev = (simnet_dev_t *)mp->b_next;
mp->b_next = NULL;
if (mac_header_info(sdev->sd_mh, mp, &hdr_info) != 0) {
mac_drop_pkt(mp, "invalid L2 header");
sdev->sd_stats.recv_errors++;
goto rx_done;
}
if (!sdev->sd_promisc &&
hdr_info.mhi_dsttype != MAC_ADDRTYPE_BROADCAST) {
if (hdr_info.mhi_dsttype == MAC_ADDRTYPE_UNICAST &&
bcmp(hdr_info.mhi_daddr, sdev->sd_mac_addr,
ETHERADDRL) != 0) {
freemsg(mp);
goto rx_done;
} else if (hdr_info.mhi_dsttype == MAC_ADDRTYPE_MULTICAST) {
mutex_enter(&sdev->sd_instlock);
if (mcastaddr_lookup(sdev, hdr_info.mhi_daddr) ==
NULL) {
mutex_exit(&sdev->sd_instlock);
freemsg(mp);
goto rx_done;
}
mutex_exit(&sdev->sd_instlock);
}
}
if ((sdev->sd_rx_cksum & HCKSUM_IPHDRCKSUM) != 0) {
mac_hcksum_set(mp, 0, 0, 0, 0, HCK_IPV4_HDRCKSUM_OK);
}
sdev->sd_stats.recv_count++;
sdev->sd_stats.rbytes += msgdsize(mp);
mac_rx(sdev->sd_mh, NULL, mp);
rx_done:
simnet_thread_unref(sdev);
}
#define SIMNET_ULP_CKSUM (HCKSUM_INET_FULL_V4 | HCKSUM_INET_PARTIAL)
static mblk_t *
simnet_m_tx(void *arg, mblk_t *mp_chain)
{
simnet_dev_t *sdev = arg;
simnet_dev_t *sdev_rx;
mblk_t *mpnext = mp_chain;
mblk_t *mp, *nmp;
mac_emul_t emul = 0;
rw_enter(&simnet_dev_lock, RW_READER);
if ((sdev_rx = sdev->sd_peer_dev) == NULL) {
rw_exit(&simnet_dev_lock);
mac_drop_chain(mp_chain, "no peer");
return (NULL);
}
if (!simnet_thread_ref(sdev_rx)) {
rw_exit(&simnet_dev_lock);
mac_drop_chain(mp_chain, "simnet peer dev not ready");
return (NULL);
}
rw_exit(&simnet_dev_lock);
if (!simnet_thread_ref(sdev)) {
simnet_thread_unref(sdev_rx);
mac_drop_chain(mp_chain, "simnet dev not ready");
return (NULL);
}
while ((mp = mpnext) != NULL) {
size_t len;
size_t size;
mblk_t *mp_new;
mblk_t *mp_tmp;
mpnext = mp->b_next;
mp->b_next = NULL;
len = msgdsize(mp);
if (len < ETHERMIN) {
size = ETHERMIN - len;
mp_new = allocb(size, BPRI_HI);
if (mp_new == NULL) {
sdev->sd_stats.xmit_errors++;
mac_drop_pkt(mp, "allocb failed");
continue;
}
bzero(mp_new->b_wptr, size);
mp_new->b_wptr += size;
mp_tmp = mp;
while (mp_tmp->b_cont != NULL)
mp_tmp = mp_tmp->b_cont;
mp_tmp->b_cont = mp_new;
len += size;
}
if ((nmp = msgpullup(mp, -1)) == NULL) {
sdev->sd_stats.xmit_errors++;
mac_drop_pkt(mp, "msgpullup failed");
continue;
} else {
mac_hcksum_clone(mp, nmp);
freemsg(mp);
mp = nmp;
}
if (!simnet_thread_ref(sdev_rx)) {
mac_drop_pkt(mp, "failed to get thread ref");
mac_drop_chain(mpnext, "failed to get thread ref");
break;
}
if ((sdev->sd_tx_cksum & HCKSUM_IPHDRCKSUM) != 0)
emul |= MAC_IPCKSUM_EMUL;
if ((sdev->sd_tx_cksum & SIMNET_ULP_CKSUM) != 0)
emul |= MAC_HWCKSUM_EMUL;
if (sdev->sd_lso)
emul |= MAC_LSO_EMUL;
if (emul != 0)
mac_hw_emul(&mp, NULL, NULL, emul);
if (mp == NULL) {
sdev->sd_stats.xmit_errors++;
continue;
}
DB_CKSUMFLAGS(mp) = 0;
mp->b_next = (mblk_t *)sdev_rx;
if (ddi_taskq_dispatch(simnet_rxq, simnet_rx, mp,
DDI_NOSLEEP) == DDI_SUCCESS) {
sdev->sd_stats.xmit_count++;
sdev->sd_stats.obytes += len;
} else {
simnet_thread_unref(sdev_rx);
mp->b_next = NULL;
freemsg(mp);
sdev_rx->sd_stats.recv_errors++;
}
}
simnet_thread_unref(sdev);
simnet_thread_unref(sdev_rx);
return (NULL);
}
static int
simnet_wifi_ioctl(simnet_dev_t *sdev, mblk_t *mp)
{
int rc = WL_SUCCESS;
simnet_wifidev_t *wdev = sdev->sd_wifidev;
switch (((wldp_t *)mp->b_rptr)->wldp_id) {
case WL_DISASSOCIATE:
wdev->swd_linkstatus = WL_NOTCONNECTED;
break;
default:
break;
}
return (rc);
}
static void
simnet_m_ioctl(void *arg, queue_t *q, mblk_t *mp)
{
simnet_dev_t *sdev = arg;
struct iocblk *iocp;
mblk_t *mp1;
uint32_t cmd;
int rc;
if (sdev->sd_type != DL_WIFI) {
miocnak(q, mp, 0, ENOTSUP);
return;
}
iocp = (struct iocblk *)mp->b_rptr;
if (iocp->ioc_count == 0) {
miocnak(q, mp, 0, EINVAL);
return;
}
cmd = iocp->ioc_cmd;
switch (cmd) {
default:
miocnak(q, mp, 0, EINVAL);
return;
case WLAN_GET_PARAM:
case WLAN_SET_PARAM:
case WLAN_COMMAND:
break;
}
mp1 = mp->b_cont;
freemsg(mp1->b_cont);
mp1->b_cont = NULL;
mp1->b_wptr = mp1->b_rptr;
rc = simnet_wifi_ioctl(sdev, mp1);
miocack(q, mp, msgdsize(mp1), rc);
}
static boolean_t
simnet_m_getcapab(void *arg, mac_capab_t cap, void *cap_data)
{
simnet_dev_t *sdev = arg;
const uint_t tcp_cksums = HCKSUM_INET_FULL_V4 | HCKSUM_INET_PARTIAL;
switch (cap) {
case MAC_CAPAB_HCKSUM: {
uint32_t *tx_cksum_flags = cap_data;
*tx_cksum_flags = sdev->sd_tx_cksum;
break;
}
case MAC_CAPAB_LSO: {
mac_capab_lso_t *cap_lso = cap_data;
if (sdev->sd_lso &&
(sdev->sd_tx_cksum & HCKSUM_IPHDRCKSUM) != 0 &&
(sdev->sd_tx_cksum & tcp_cksums) != 0) {
cap_lso->lso_flags = LSO_TX_BASIC_TCP_IPV4;
cap_lso->lso_basic_tcp_ipv4.lso_max = SD_LSO_MAXLEN;
break;
} else {
return (B_FALSE);
}
}
default:
return (B_FALSE);
}
return (B_TRUE);
}
static int
simnet_m_stat(void *arg, uint_t stat, uint64_t *val)
{
int rval = 0;
simnet_dev_t *sdev = arg;
ASSERT(sdev->sd_mh != NULL);
switch (stat) {
case MAC_STAT_IFSPEED:
*val = 100 * 1000000ull;
break;
case MAC_STAT_LINK_STATE:
*val = LINK_DUPLEX_FULL;
break;
case MAC_STAT_LINK_UP:
if (sdev->sd_flags & SDF_STARTED)
*val = LINK_STATE_UP;
else
*val = LINK_STATE_DOWN;
break;
case MAC_STAT_PROMISC:
case MAC_STAT_MULTIRCV:
case MAC_STAT_MULTIXMT:
case MAC_STAT_BRDCSTRCV:
case MAC_STAT_BRDCSTXMT:
rval = ENOTSUP;
break;
case MAC_STAT_OPACKETS:
*val = sdev->sd_stats.xmit_count;
break;
case MAC_STAT_OBYTES:
*val = sdev->sd_stats.obytes;
break;
case MAC_STAT_IERRORS:
*val = sdev->sd_stats.recv_errors;
break;
case MAC_STAT_OERRORS:
*val = sdev->sd_stats.xmit_errors;
break;
case MAC_STAT_RBYTES:
*val = sdev->sd_stats.rbytes;
break;
case MAC_STAT_IPACKETS:
*val = sdev->sd_stats.recv_count;
break;
case WIFI_STAT_FCS_ERRORS:
case WIFI_STAT_WEP_ERRORS:
case WIFI_STAT_TX_FRAGS:
case WIFI_STAT_MCAST_TX:
case WIFI_STAT_RTS_SUCCESS:
case WIFI_STAT_RTS_FAILURE:
case WIFI_STAT_ACK_FAILURE:
case WIFI_STAT_RX_FRAGS:
case WIFI_STAT_MCAST_RX:
case WIFI_STAT_RX_DUPS:
rval = ENOTSUP;
break;
default:
rval = ENOTSUP;
break;
}
return (rval);
}
static int
simnet_m_start(void *arg)
{
simnet_dev_t *sdev = arg;
sdev->sd_flags |= SDF_STARTED;
return (0);
}
static void
simnet_m_stop(void *arg)
{
simnet_dev_t *sdev = arg;
sdev->sd_flags &= ~SDF_STARTED;
}
static int
simnet_m_promisc(void *arg, boolean_t on)
{
simnet_dev_t *sdev = arg;
sdev->sd_promisc = on;
return (0);
}
static const struct ether_addr *
mcastaddr_lookup(const simnet_dev_t *sdev, const uint8_t *addrp)
{
ASSERT(MUTEX_HELD(&sdev->sd_instlock));
for (uint_t i = 0; i < sdev->sd_mcastaddr_count; i++) {
const struct ether_addr *maddrp = &sdev->sd_mcastaddrs[i];
if (bcmp(maddrp->ether_addr_octet, addrp,
sizeof (maddrp->ether_addr_octet)) == 0) {
return (maddrp);
}
}
return (NULL);
}
static int
simnet_multicst_add(simnet_dev_t *sdev, const struct ether_addr *eap)
{
ASSERT(MUTEX_HELD(&sdev->sd_instlock));
if ((eap->ether_addr_octet[0] & 01) == 0) {
return (EINVAL);
}
if (sdev->sd_mcastaddr_count == SM_MAX_NUM_MCAST_ADDRS) {
return (ENOSPC);
}
bcopy(eap, &sdev->sd_mcastaddrs[sdev->sd_mcastaddr_count],
sizeof (*eap));
sdev->sd_mcastaddr_count++;
return (0);
}
static int
simnet_multicst_rm(simnet_dev_t *sdev, const struct ether_addr *eap)
{
ASSERT(MUTEX_HELD(&sdev->sd_instlock));
for (uint_t i = 0; i < sdev->sd_mcastaddr_count; i++) {
if (bcmp(eap, &sdev->sd_mcastaddrs[i], sizeof (*eap)) == 0) {
for (i++; i < sdev->sd_mcastaddr_count; i++) {
sdev->sd_mcastaddrs[i - 1] =
sdev->sd_mcastaddrs[i];
}
bzero(&sdev->sd_mcastaddrs[i - 1],
sizeof (sdev->sd_mcastaddrs[0]));
sdev->sd_mcastaddr_count--;
return (0);
}
}
return (EINVAL);
}
static int
simnet_m_multicst(void *arg, boolean_t add, const uint8_t *addrp)
{
simnet_dev_t *sdev = arg;
struct ether_addr ea;
int ret;
bcopy(addrp, ea.ether_addr_octet, sizeof (ea.ether_addr_octet));
mutex_enter(&sdev->sd_instlock);
if (add) {
ret = simnet_multicst_add(sdev, &ea);
} else {
ret = simnet_multicst_rm(sdev, &ea);
}
ASSERT3U(sdev->sd_mcastaddr_count, <=, SM_MAX_NUM_MCAST_ADDRS);
mutex_exit(&sdev->sd_instlock);
return (ret);
}
static int
simnet_m_unicst(void *arg, const uint8_t *macaddr)
{
simnet_dev_t *sdev = arg;
(void) memcpy(sdev->sd_mac_addr, macaddr, ETHERADDRL);
return (0);
}
static int
parse_esslist_args(const void *pr_val, uint_t pr_valsize,
char args[][MAX_ESSLIST_ARGLEN])
{
char *sep;
ptrdiff_t len = pr_valsize;
const char *piece = pr_val;
const char *end = (const char *)pr_val + pr_valsize - 1;
int arg = 0;
while (piece < end && (arg < MAX_ESSLIST_ARGS)) {
sep = strchr(piece, ',');
if (sep == NULL)
sep = (char *)end;
len = sep - piece;
if (arg == 0 && strnlen(piece, len) == 1 && piece[0] == '0')
return (0);
if (len > MAX_ESSLIST_ARGLEN)
len = MAX_ESSLIST_ARGLEN - 1;
(void) memcpy(&args[arg][0], piece, len);
args[arg][len] = '\0';
piece = sep + 1;
arg++;
}
return (arg);
}
static int
set_wl_esslist_priv_prop(simnet_wifidev_t *wdev, uint_t pr_valsize,
const void *pr_val)
{
char essargs[MAX_ESSLIST_ARGS][MAX_ESSLIST_ARGLEN];
wl_ess_conf_t *wls;
long result;
int i;
bzero(essargs, sizeof (essargs));
if (parse_esslist_args(pr_val, pr_valsize, essargs) == 0) {
for (i = 0; i < wdev->swd_esslist_num; i++) {
kmem_free(wdev->swd_esslist[i], sizeof (wl_ess_conf_t));
wdev->swd_esslist[i] = NULL;
}
wdev->swd_esslist_num = 0;
return (0);
}
for (i = 0; i < wdev->swd_esslist_num; i++) {
wls = wdev->swd_esslist[i];
if (strcasecmp(wls->wl_ess_conf_essid.wl_essid_essid,
essargs[0]) == 0)
return (EEXIST);
}
if (wdev->swd_esslist_num >= MAX_SIMNET_ESSCONF)
return (EINVAL);
wls = kmem_zalloc(sizeof (wl_ess_conf_t), KM_SLEEP);
(void) strlcpy(wls->wl_ess_conf_essid.wl_essid_essid,
essargs[0], sizeof (wls->wl_ess_conf_essid.wl_essid_essid));
wls->wl_ess_conf_essid.wl_essid_length =
strlen(wls->wl_ess_conf_essid.wl_essid_essid);
(void) random_get_pseudo_bytes((uint8_t *)
&wls->wl_ess_conf_bssid, sizeof (wl_bssid_t));
(void) ddi_strtol(essargs[1], (char **)NULL, 0, &result);
wls->wl_ess_conf_sl = (wl_rssi_t)
((result > MAX_RSSI || result < 0) ? 0:result);
wdev->swd_esslist[wdev->swd_esslist_num] = wls;
wdev->swd_esslist_num++;
return (0);
}
static int
simnet_set_priv_prop_wifi(simnet_dev_t *sdev, const char *name,
const uint_t len, const void *val)
{
simnet_wifidev_t *wdev = sdev->sd_wifidev;
long result;
if (strcmp(name, "_wl_esslist") == 0) {
if (val == NULL)
return (EINVAL);
return (set_wl_esslist_priv_prop(wdev, len, val));
} else if (strcmp(name, "_wl_connected") == 0) {
if (val == NULL)
return (EINVAL);
(void) ddi_strtol(val, (char **)NULL, 0, &result);
wdev->swd_linkstatus = ((result == 1) ?
WL_CONNECTED:WL_NOTCONNECTED);
return (0);
}
return (EINVAL);
}
static int
simnet_set_priv_prop_ether(simnet_dev_t *sdev, const char *name,
const uint_t len, const void *val)
{
if (strcmp(name, SD_PROP_RX_IP_CKSUM) == 0) {
if (val == NULL)
return (EINVAL);
if (strcmp(val, "off") == 0) {
sdev->sd_rx_cksum &= ~HCKSUM_IPHDRCKSUM;
} else if (strcmp(val, "on") == 0) {
sdev->sd_rx_cksum |= HCKSUM_IPHDRCKSUM;
} else {
return (EINVAL);
}
return (0);
} else if (strcmp(name, SD_PROP_TX_ULP_CKSUM) == 0) {
if (val == NULL)
return (EINVAL);
if (strcmp(val, "none") == 0) {
sdev->sd_tx_cksum &= ~HCKSUM_INET_FULL_V4;
} else if (strcmp(val, "fullv4") == 0) {
sdev->sd_tx_cksum &= ~HCKSUM_INET_PARTIAL;
sdev->sd_tx_cksum |= HCKSUM_INET_FULL_V4;
} else if (strcmp(val, "partial") == 0) {
sdev->sd_tx_cksum &= HCKSUM_INET_FULL_V4;
sdev->sd_tx_cksum |= HCKSUM_INET_PARTIAL;
} else {
return (EINVAL);
}
return (0);
} else if (strcmp(name, SD_PROP_TX_IP_CKSUM) == 0) {
if (val == NULL)
return (EINVAL);
if (strcmp(val, "off") == 0) {
sdev->sd_tx_cksum &= ~HCKSUM_IPHDRCKSUM;
} else if (strcmp(val, "on") == 0) {
sdev->sd_tx_cksum |= HCKSUM_IPHDRCKSUM;
} else {
return (EINVAL);
}
return (0);
} else if (strcmp(name, SD_PROP_LSO) == 0) {
if (val == NULL)
return (EINVAL);
if (strcmp(val, "off") == 0) {
sdev->sd_lso = B_FALSE;
} else if (strcmp(val, "on") == 0) {
sdev->sd_lso = B_TRUE;
} else {
return (EINVAL);
}
return (0);
} else if (strcmp(name, SD_PROP_LINKSTATE) == 0) {
if (val == NULL)
return (EINVAL);
if (strcmp(val, "up") == 0) {
sdev->sd_ls = LINK_STATE_UP;
} else if (strcmp(val, "down") == 0) {
sdev->sd_ls = LINK_STATE_DOWN;
} else if (strcmp(val, "unknown") == 0) {
sdev->sd_ls = LINK_STATE_UNKNOWN;
} else {
return (EINVAL);
}
mac_link_update(sdev->sd_mh, sdev->sd_ls);
return (0);
}
return (ENOTSUP);
}
static int
simnet_setprop_wifi(simnet_dev_t *sdev, const char *name,
const mac_prop_id_t num, const uint_t len, const void *val)
{
int err = 0;
simnet_wifidev_t *wdev = sdev->sd_wifidev;
switch (num) {
case MAC_PROP_WL_ESSID: {
int i;
wl_ess_conf_t *wls;
(void) memcpy(&wdev->swd_essid, val, sizeof (wl_essid_t));
wdev->swd_linkstatus = WL_CONNECTED;
for (i = 0; i < wdev->swd_esslist_num; i++) {
wls = wdev->swd_esslist[i];
if (strcasecmp(wls->wl_ess_conf_essid.wl_essid_essid,
wdev->swd_essid.wl_essid_essid) == 0) {
wdev->swd_rssi = wls->wl_ess_conf_sl;
break;
}
}
break;
}
case MAC_PROP_WL_BSSID: {
(void) memcpy(&wdev->swd_bssid, val, sizeof (wl_bssid_t));
break;
}
case MAC_PROP_WL_PHY_CONFIG:
case MAC_PROP_WL_KEY_TAB:
case MAC_PROP_WL_AUTH_MODE:
case MAC_PROP_WL_ENCRYPTION:
case MAC_PROP_WL_BSSTYPE:
case MAC_PROP_WL_DESIRED_RATES:
break;
case MAC_PROP_PRIVATE:
err = simnet_set_priv_prop_wifi(sdev, name, len, val);
break;
default:
err = EINVAL;
break;
}
return (err);
}
static int
simnet_setprop_ether(simnet_dev_t *sdev, const char *name,
const mac_prop_id_t num, const uint_t len, const void *val)
{
int err = 0;
switch (num) {
case MAC_PROP_PRIVATE:
err = simnet_set_priv_prop_ether(sdev, name, len, val);
break;
default:
err = EINVAL;
break;
}
return (err);
}
static int
simnet_m_setprop(void *arg, const char *name, mac_prop_id_t num,
const uint_t len, const void *val)
{
simnet_dev_t *sdev = arg;
int err = 0;
uint32_t mtu;
switch (num) {
case MAC_PROP_MTU:
(void) memcpy(&mtu, val, sizeof (mtu));
if (mtu > ETHERMIN && mtu < SIMNET_MAX_MTU)
return (mac_maxsdu_update(sdev->sd_mh, mtu));
else
return (EINVAL);
default:
break;
}
switch (sdev->sd_type) {
case DL_ETHER:
err = simnet_setprop_ether(sdev, name, num, len, val);
break;
case DL_WIFI:
err = simnet_setprop_wifi(sdev, name, num, len, val);
break;
default:
err = EINVAL;
break;
}
mac_capab_update(sdev->sd_mh);
return (err);
}
static int
simnet_get_priv_prop_wifi(const simnet_dev_t *sdev, const char *name,
const uint_t len, void *val)
{
simnet_wifidev_t *wdev = sdev->sd_wifidev;
int ret, value;
if (strcmp(name, "_wl_esslist") == 0) {
value = wdev->swd_esslist_num;
} else if (strcmp(name, "_wl_connected") == 0) {
value = ((wdev->swd_linkstatus == WL_CONNECTED) ? 1:0);
} else {
return (ENOTSUP);
}
ret = snprintf(val, len, "%d", value);
if (ret < 0 || ret >= len)
return (EOVERFLOW);
return (0);
}
static int
simnet_get_priv_prop_ether(const simnet_dev_t *sdev, const char *name,
const uint_t len, void *val)
{
int ret;
char *value;
if (strcmp(name, SD_PROP_RX_IP_CKSUM) == 0) {
if ((sdev->sd_rx_cksum & HCKSUM_IPHDRCKSUM) != 0) {
value = "on";
} else {
value = "off";
}
} else if (strcmp(name, SD_PROP_TX_ULP_CKSUM) == 0) {
if ((sdev->sd_tx_cksum & HCKSUM_INET_FULL_V4) != 0) {
value = "fullv4";
} else if ((sdev->sd_tx_cksum & HCKSUM_INET_PARTIAL) != 0) {
value = "partial";
} else {
value = "none";
}
} else if (strcmp(name, SD_PROP_TX_IP_CKSUM) == 0) {
if ((sdev->sd_tx_cksum & HCKSUM_IPHDRCKSUM) != 0) {
value = "on";
} else {
value = "off";
}
} else if (strcmp(name, SD_PROP_LSO) == 0) {
value = sdev->sd_lso ? "on" : "off";
} else if (strcmp(name, SD_PROP_LINKSTATE) == 0) {
if (sdev->sd_ls == LINK_STATE_UP) {
value = "up";
} else if (sdev->sd_ls == LINK_STATE_DOWN) {
value = "down";
} else {
value = "unknown";
}
} else {
return (ENOTSUP);
}
ret = snprintf(val, len, "%s", value);
if (ret < 0 || ret >= len) {
return (EOVERFLOW);
}
return (0);
}
static int
simnet_getprop_wifi(const simnet_dev_t *sdev, const char *name,
const mac_prop_id_t num, const uint_t len, void *val)
{
const simnet_wifidev_t *wdev = sdev->sd_wifidev;
int err = 0;
switch (num) {
case MAC_PROP_WL_ESSID:
(void) memcpy(val, &wdev->swd_essid, sizeof (wl_essid_t));
break;
case MAC_PROP_WL_BSSID:
(void) memcpy(val, &wdev->swd_bssid, sizeof (wl_bssid_t));
break;
case MAC_PROP_WL_PHY_CONFIG:
case MAC_PROP_WL_AUTH_MODE:
case MAC_PROP_WL_ENCRYPTION:
break;
case MAC_PROP_WL_LINKSTATUS:
(void) memcpy(val, &wdev->swd_linkstatus,
sizeof (wdev->swd_linkstatus));
break;
case MAC_PROP_WL_ESS_LIST: {
wl_ess_conf_t *w_ess_conf;
((wl_ess_list_t *)val)->wl_ess_list_num = wdev->swd_esslist_num;
w_ess_conf = (wl_ess_conf_t *)((char *)val +
offsetof(wl_ess_list_t, wl_ess_list_ess));
for (uint_t i = 0; i < wdev->swd_esslist_num; i++) {
(void) memcpy(w_ess_conf, wdev->swd_esslist[i],
sizeof (wl_ess_conf_t));
w_ess_conf++;
}
break;
}
case MAC_PROP_WL_RSSI:
*(wl_rssi_t *)val = wdev->swd_rssi;
break;
case MAC_PROP_WL_RADIO:
*(wl_radio_t *)val = B_TRUE;
break;
case MAC_PROP_WL_POWER_MODE:
break;
case MAC_PROP_WL_DESIRED_RATES:
break;
case MAC_PROP_PRIVATE:
err = simnet_get_priv_prop_wifi(sdev, name, len, val);
break;
default:
err = ENOTSUP;
break;
}
return (err);
}
static int
simnet_getprop_ether(const simnet_dev_t *sdev, const char *name,
const mac_prop_id_t num, const uint_t len, void *val)
{
int err = 0;
switch (num) {
case MAC_PROP_PRIVATE:
err = simnet_get_priv_prop_ether(sdev, name, len, val);
break;
default:
err = ENOTSUP;
break;
}
return (err);
}
static int
simnet_m_getprop(void *arg, const char *name, const mac_prop_id_t num,
const uint_t len, void *val)
{
const simnet_dev_t *sdev = arg;
int err = 0;
switch (sdev->sd_type) {
case DL_ETHER:
err = simnet_getprop_ether(sdev, name, num, len, val);
break;
case DL_WIFI:
err = simnet_getprop_wifi(sdev, name, num, len, val);
break;
default:
err = EINVAL;
break;
}
return (err);
}
static void
simnet_priv_propinfo_wifi(const char *name, mac_prop_info_handle_t prh)
{
char valstr[MAXNAMELEN];
bzero(valstr, sizeof (valstr));
if (strcmp(name, "_wl_esslist") == 0) {
(void) snprintf(valstr, sizeof (valstr), "%d", 0);
}
if (strlen(valstr) > 0)
mac_prop_info_set_default_str(prh, valstr);
}
static void
simnet_propinfo_wifi(const char *name, const mac_prop_id_t num,
mac_prop_info_handle_t prh)
{
switch (num) {
case MAC_PROP_WL_BSSTYPE:
case MAC_PROP_WL_ESS_LIST:
case MAC_PROP_WL_SUPPORTED_RATES:
case MAC_PROP_WL_RSSI:
mac_prop_info_set_perm(prh, MAC_PROP_PERM_READ);
break;
case MAC_PROP_PRIVATE:
simnet_priv_propinfo_wifi(name, prh);
break;
}
}
static void
simnet_priv_propinfo_ether(const char *name, mac_prop_info_handle_t prh)
{
if (strcmp(name, SD_PROP_RX_IP_CKSUM) == 0 ||
strcmp(name, SD_PROP_TX_ULP_CKSUM) == 0 ||
strcmp(name, SD_PROP_TX_IP_CKSUM) == 0 ||
strcmp(name, SD_PROP_LSO) == 0) {
mac_prop_info_set_perm(prh, MAC_PROP_PERM_RW);
}
if (strcmp(name, SD_PROP_TX_ULP_CKSUM) == 0) {
mac_prop_info_set_default_str(prh, "none");
}
if (strcmp(name, SD_PROP_RX_IP_CKSUM) == 0 ||
strcmp(name, SD_PROP_TX_IP_CKSUM) == 0 ||
strcmp(name, SD_PROP_LSO) == 0) {
mac_prop_info_set_default_str(prh, "off");
}
if (strcmp(name, SD_PROP_LINKSTATE) == 0) {
mac_prop_info_set_default_str(prh, "unknown");
}
}
static void
simnet_propinfo_ether(const char *name, const mac_prop_id_t num,
mac_prop_info_handle_t prh)
{
switch (num) {
case MAC_PROP_PRIVATE:
simnet_priv_propinfo_ether(name, prh);
break;
}
}
static void
simnet_m_propinfo(void *arg, const char *name, const mac_prop_id_t num,
const mac_prop_info_handle_t prh)
{
simnet_dev_t *sdev = arg;
switch (sdev->sd_type) {
case DL_ETHER:
simnet_propinfo_ether(name, num, prh);
break;
case DL_WIFI:
simnet_propinfo_wifi(name, num, prh);
break;
}
}