/* * Copyright 2007-2009, Axel Dörfler, axeld@pinc-software.de. * Copyright 2007, Hugo Santos. All Rights Reserved. * Copyright 2004, Marcus Overhagen. All Rights Reserved. * Distributed under the terms of the MIT License. */ #include "device.h" #include #include #include #include #include #include #include #include #include #include #include static status_t compat_open(const char *name, uint32 flags, void **cookie) { struct ifnet *ifp; struct ifreq ifr; int i; status_t status; for (i = 0; i < MAX_DEVICES; i++) { if (gDevices[i] != NULL && !strcmp(gDevices[i]->device_name, name)) break; } if (i == MAX_DEVICES) return B_ERROR; ifp = gDevices[i]; if_printf(ifp, "compat_open(0x%" B_PRIx32 ")\n", flags); if (atomic_or(&ifp->open_count, 1)) return B_BUSY; IFF_LOCKGIANT(ifp); if (ifp->if_init != NULL) ifp->if_init(ifp->if_softc); if (!HAIKU_DRIVER_REQUIRES(FBSD_WLAN_FEATURE)) { ifp->if_flags &= ~IFF_UP; ifp->if_ioctl(ifp, SIOCSIFFLAGS, NULL); memset(&ifr, 0, sizeof(ifr)); ifr.ifr_media = IFM_MAKEWORD(IFM_ETHER, IFM_AUTO, 0, 0); status = ifp->if_ioctl(ifp, SIOCSIFMEDIA, (caddr_t)&ifr); if (status != B_OK) { ifr.ifr_media = IFM_MAKEWORD(IFM_ETHER, IFM_10_T, 0, 0); status = ifp->if_ioctl(ifp, SIOCSIFMEDIA, (caddr_t)&ifr); } } ifp->if_flags |= IFF_UP; ifp->flags &= ~DEVICE_CLOSED; ifp->if_ioctl(ifp, SIOCSIFFLAGS, NULL); IFF_UNLOCKGIANT(ifp); *cookie = ifp; return B_OK; } static status_t compat_close(void *cookie) { struct ifnet *ifp = cookie; if_printf(ifp, "compat_close()\n"); IFF_LOCKGIANT(ifp); atomic_or(&ifp->flags, DEVICE_CLOSED); wlan_close(cookie); release_sem_etc(ifp->receive_sem, 1, B_RELEASE_ALL); IFF_UNLOCKGIANT(ifp); return B_OK; } static status_t compat_free(void *cookie) { struct ifnet *ifp = cookie; if_printf(ifp, "compat_free()\n"); // TODO: empty out the send queue atomic_and(&ifp->open_count, 0); return B_OK; } static status_t compat_receive(void *cookie, net_buffer **_buffer) { struct ifnet *ifp = cookie; uint32 semFlags = B_CAN_INTERRUPT; status_t status; struct mbuf *mb; //if_printf(ifp, "compat_receive(%p)\n", _buffer); if ((ifp->flags & DEVICE_CLOSED) != 0) return B_INTERRUPTED; if (ifp->flags & DEVICE_NON_BLOCK) semFlags |= B_RELATIVE_TIMEOUT; do { status = acquire_sem_etc(ifp->receive_sem, 1, semFlags, 0); if ((ifp->flags & DEVICE_CLOSED) != 0) return B_INTERRUPTED; if (status != B_OK) return status; IF_DEQUEUE(&ifp->receive_queue, mb); } while (mb == NULL); net_buffer *buffer = gBufferModule->create(0); if (buffer == NULL) { m_freem(mb); return B_NO_MEMORY; } for (struct mbuf *m = mb; m != NULL; m = m->m_next) { status = gBufferModule->append(buffer, mtod(m, void *), m->m_len); if (status != B_OK) break; } if (status != B_OK) { gBufferModule->free(buffer); m_freem(mb); return status; } if ((mb->m_pkthdr.csum_flags & CSUM_L3_VALID) != 0) buffer->buffer_flags |= NET_BUFFER_L3_CHECKSUM_VALID; if ((mb->m_pkthdr.csum_flags & CSUM_L4_VALID) != 0) buffer->buffer_flags |= NET_BUFFER_L4_CHECKSUM_VALID; *_buffer = buffer; m_freem(mb); return B_OK; } static status_t compat_send(void *cookie, net_buffer *buffer) { struct ifnet *ifp = cookie; struct mbuf *mb; int length = buffer->size; //if_printf(ifp, "compat_send(%p, [%lu])\n", buffer, length); if (length <= MHLEN) { mb = m_gethdr(0, MT_DATA); if (mb == NULL) return ENOBUFS; } else { mb = m_get2(length, 0, MT_DATA, M_PKTHDR); if (mb == NULL) return E2BIG; length = min_c(length, mb->m_ext.ext_size); } status_t status = gBufferModule->read(buffer, 0, mtod(mb, void *), length); if (status != B_OK) return status; mb->m_pkthdr.len = mb->m_len = length; if ((ifp->flags & DEVICE_CLOSED) != 0) return B_INTERRUPTED; IFF_LOCKGIANT(ifp); int result = ifp->if_output(ifp, mb, NULL, NULL); IFF_UNLOCKGIANT(ifp); if (result == 0) gBufferModule->free(buffer); return result; } static status_t compat_control(void *cookie, uint32 op, void *arg, size_t length) { struct ifnet *ifp = cookie; status_t status; //if_printf(ifp, "compat_control(op %lu, %p, [%lu])\n", op, // arg, length); switch (op) { case ETHER_INIT: return B_OK; case ETHER_GETADDR: return user_memcpy(arg, IF_LLADDR(ifp), ETHER_ADDR_LEN); case ETHER_NONBLOCK: { int32 value; if (length < 4) return B_BAD_VALUE; if (user_memcpy(&value, arg, sizeof(int32)) < B_OK) return B_BAD_ADDRESS; if (value) ifp->flags |= DEVICE_NON_BLOCK; else ifp->flags &= ~DEVICE_NON_BLOCK; return B_OK; } case ETHER_SETPROMISC: { int32 value; if (length < 4) return B_BAD_VALUE; if (user_memcpy(&value, arg, sizeof(int32)) < B_OK) return B_BAD_ADDRESS; if (value) ifp->if_flags |= IFF_PROMISC; else ifp->if_flags &= ~IFF_PROMISC; IFF_LOCKGIANT(ifp); status = ifp->if_ioctl(ifp, SIOCSIFFLAGS, NULL); IFF_UNLOCKGIANT(ifp); return status; } case ETHER_GETFRAMESIZE: { uint32 frameSize; if (length < 4) return B_BAD_VALUE; const int MTUs[] = { ETHERMTU_JUMBO, PAGESIZE - (ETHER_HDR_LEN + ETHER_CRC_LEN), 2290, /* IEEE80211_MTU_MAX */ 0 }; // This is (usually) only invoked during initialization to get the // maximum frame size. Thus we try a few common possible values, // as there is no way to determine what is supported (or required). for (int i = 0; MTUs[i] != 0; i++) { struct ifreq ifr; ifr.ifr_mtu = MTUs[i]; if (compat_control(cookie, SIOCSIFMTU, &ifr, sizeof(ifr)) == 0) break; } frameSize = ifp->if_mtu + ETHER_HDR_LEN; return user_memcpy(arg, &frameSize, 4); } case ETHER_ADDMULTI: case ETHER_REMMULTI: { struct sockaddr_dl address; if ((ifp->if_flags & IFF_MULTICAST) == 0) return B_NOT_SUPPORTED; if (length != ETHER_ADDR_LEN) return B_BAD_VALUE; memset(&address, 0, sizeof(address)); address.sdl_family = AF_LINK; if (user_memcpy(LLADDR(&address), arg, ETHER_ADDR_LEN) < B_OK) return B_BAD_ADDRESS; IFF_LOCKGIANT(ifp); if (op == ETHER_ADDMULTI) status = if_addmulti(ifp, (struct sockaddr *)&address, NULL); else status = if_delmulti(ifp, (struct sockaddr *)&address); IFF_UNLOCKGIANT(ifp); return status; } case ETHER_GET_LINK_STATE: { struct ifmediareq mediareq; ether_link_state_t state; if (length < sizeof(ether_link_state_t)) return EINVAL; memset(&mediareq, 0, sizeof(mediareq)); IFF_LOCKGIANT(ifp); status = ifp->if_ioctl(ifp, SIOCGIFMEDIA, (caddr_t)&mediareq); IFF_UNLOCKGIANT(ifp); if (status < B_OK) return status; state.media = mediareq.ifm_active; if ((mediareq.ifm_status & IFM_ACTIVE) != 0) state.media |= IFM_ACTIVE; state.speed = ifmedia_baudrate(mediareq.ifm_active); state.quality = 1000; return user_memcpy(arg, &state, sizeof(ether_link_state_t)); } case ETHER_SET_LINK_STATE_SEM: if (user_memcpy(&ifp->link_state_sem, arg, sizeof(sem_id)) < B_OK) { ifp->link_state_sem = -1; return B_BAD_ADDRESS; } return B_OK; case ETHER_SEND_NET_BUFFER: if (arg == NULL || length == 0) return B_BAD_DATA; if (!IS_KERNEL_ADDRESS(arg)) return B_BAD_ADDRESS; return compat_send(cookie, (net_buffer*)arg); case ETHER_RECEIVE_NET_BUFFER: if (arg == NULL || length == 0) return B_BAD_DATA; if (!IS_KERNEL_ADDRESS(arg)) return B_BAD_ADDRESS; return compat_receive(cookie, (net_buffer**)arg); case SIOCSIFFLAGS: case SIOCSIFMEDIA: case SIOCSIFMTU: { IFF_LOCKGIANT(ifp); status = ifp->if_ioctl(ifp, op, (caddr_t)arg); IFF_UNLOCKGIANT(ifp); return status; } } return wlan_control(cookie, op, arg, length); } device_hooks gDeviceHooks = { compat_open, compat_close, compat_free, compat_control, NULL, NULL, };