From patchwork Tue Jan 13 13:22:54 2009 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Uri Shkolnik X-Patchwork-Id: 2142 Received: from vger.kernel.org (vger.kernel.org [209.132.176.167]) by demeter.kernel.org (8.14.2/8.14.2) with ESMTP id n0DDJ1Zv006304 for ; Tue, 13 Jan 2009 05:19:01 -0800 Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1751471AbZAMNW6 (ORCPT ); Tue, 13 Jan 2009 08:22:58 -0500 Received: (majordomo@vger.kernel.org) by vger.kernel.org id S1751563AbZAMNW6 (ORCPT ); Tue, 13 Jan 2009 08:22:58 -0500 Received: from web110812.mail.gq1.yahoo.com ([67.195.13.235]:33951 "HELO web110812.mail.gq1.yahoo.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with SMTP id S1751471AbZAMNW4 (ORCPT ); Tue, 13 Jan 2009 08:22:56 -0500 Received: (qmail 60422 invoked by uid 60001); 13 Jan 2009 13:22:55 -0000 DomainKey-Signature: a=rsa-sha1; q=dns; c=nofws; s=s1024; d=yahoo.com; h=X-YMail-OSG:Received:X-Mailer:Date:From:Reply-To:Subject:To:Cc:MIME-Version:Content-Type:Message-ID; b=h6wFil+0R8S0oSaxl4w+2RAiJTtCCSJwdiXYvQskyzU/e1IsKX4kZImnonWmCaQ68eAsC+HC7H5mRrat3Goz0oWE7++VBFdhd1dxnlxE+5k/dNf/wBNhIxErrVtfgddDpChZqOnp00+E5DIWWuPKbSQosJEVO+oKoaiD2ojBeh4=; X-YMail-OSG: rkNjnNwVM1lEJqQmhzRr4y4O9bFRbSURslPTkSWJKc0Mx3SHeAdTAiGZg_SotPrpGtkL_5hubKs.wm2pfsjOtF_AvZMpviqigEYO0kcLwuqFHIM7bAIm_7pglx5BFS4IOl.z1t3WAsXN6ZpkeanVFY_iyXTwhJJ6wsGX3u5tbB1wjouCEmwdBRoOe7Gv2yif0HurrQBVffRJ Received: from [199.203.99.233] by web110812.mail.gq1.yahoo.com via HTTP; Tue, 13 Jan 2009 05:22:54 PST X-Mailer: YahooMailWebService/0.7.260.1 Date: Tue, 13 Jan 2009 05:22:54 -0800 (PST) From: Uri Shkolnik Reply-To: urishk@yahoo.com Subject: [PATCH] Siano 10226 Siano sub-system To: linux-media@vger.kernel.org Cc: Mauro Carvalho MIME-Version: 1.0 Message-ID: <250460.60370.qm@web110812.mail.gq1.yahoo.com> Sender: linux-media-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-media@vger.kernel.org # HG changeset patch # User Uri Shkolnik # Date 1231847968 -7200 # Node ID 0d667d493399447a285bac21f0f38162fbbc2241 # Parent a9dd63fe39745548432a32617f059f2ed64b5d48 Siano sub-system From: Uri Shkolnik This patch adds Siano subsystem, which supports the CMMB and T-DMB DTV standards. The patch also adds Network interface (network driver) in order to support the DVB-H and DAB-IP standards. Priority: normal Signed-off-by: Uri Shkolnik --- To unsubscribe from this list: send the line "unsubscribe linux-media" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html diff -r a9dd63fe3974 -r 0d667d493399 linux/drivers/media/dvb/siano/smschar.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/linux/drivers/media/dvb/siano/smschar.c Tue Jan 13 13:59:28 2009 +0200 @@ -0,0 +1,652 @@ +/**************************************************************** + +Siano Mobile Silicon, Inc. +MDTV receiver kernel modules. +Copyright (C) 2006-2008, Uri Shkolnik + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + + 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. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +****************************************************************/ +#include +#include +#include + +#include /* printk() */ +#include /* everything... */ +#include /* size_t */ +#include +#include +#include +#include /* cli(), *_flags */ +#include /* copy_*_user */ + +#include "smscoreapi.h" + +#include "smscharioctl.h" + +/* max number of packets allowed to be pending on queue*/ +#define SMS_CHR_MAX_Q_LEN 15 +#define SMSCHAR_NR_DEVS 17 + +struct smschar_device_t { + struct cdev cdev; /*!< Char device structure */ + wait_queue_head_t waitq; /* Processes waiting */ + int cancel_waitq; + spinlock_t lock; /*!< critical section */ + int pending_count; + struct list_head pending_data; /*!< list of pending data */ + struct smscore_buffer_t *currentcb; + int device_index; + struct smscore_device_t *coredev; + struct smscore_client_t *smsclient; +}; + +/*! Holds the major number of the device node. may be changed at load +time.*/ +int smschar_major = 251; + +/*! Holds the first minor number of the device node. +may be changed at load time.*/ +int smschar_minor; /*= 0*/ + +/* macros that allow the load time parameters change*/ +module_param(smschar_major, int, S_IRUGO); +module_param(smschar_minor, int, S_IRUGO); + +struct smschar_device_t smschar_devices[SMSCHAR_NR_DEVS]; +static int g_smschar_inuse; + +static int g_pnp_status_changed = 1; +wait_queue_head_t g_pnp_event; + +/** + * unregisters sms client and returns all queued buffers + * + * @param dev pointer to the client context (smschar parameters block) + * + */ +static void smschar_unregister_client(struct smschar_device_t *dev) +{ + unsigned long flags; + + if (dev->coredev && dev->smsclient) { + dev->cancel_waitq = 1; + wake_up_interruptible(&dev->waitq); + + spin_lock_irqsave(&dev->lock, flags); + + while (!list_empty(&dev->pending_data)) { + struct smscore_buffer_t *cb = + (struct smscore_buffer_t *)dev->pending_data.next; + list_del(&cb->entry); + + smscore_putbuffer(dev->coredev, cb); + dev->pending_count--; + } + + if (dev->currentcb) { + smscore_putbuffer(dev->coredev, dev->currentcb); + dev->currentcb = NULL; + dev->pending_count--; + } + + smscore_unregister_client(dev->smsclient); + dev->smsclient = NULL; + + spin_unlock_irqrestore(&dev->lock, flags); + } +} + +/** + * queues incoming buffers into buffers queue + * + * @param context pointer to the client context (smschar parameters block) + * @param cb pointer to incoming buffer descriptor + * + * @return 0 on success, <0 on queue overflow. + */ +static int smschar_onresponse(void *context, struct smscore_buffer_t *cb) +{ + struct smschar_device_t *dev = context; + unsigned long flags; + + if (!dev) { + sms_err("recieved bad dev pointer\n"); + return -EFAULT; + } + spin_lock_irqsave(&dev->lock, flags); + + if (dev->pending_count > SMS_CHR_MAX_Q_LEN) { + spin_unlock_irqrestore(&dev->lock, flags); + return -EBUSY; + } + + dev->pending_count++; + /* if data channel, remove header */ + if (dev->device_index) { + cb->size -= sizeof(struct SmsMsgHdr_ST); + cb->offset += sizeof(struct SmsMsgHdr_ST); + } + + list_add_tail(&cb->entry, &dev->pending_data); + spin_unlock_irqrestore(&dev->lock, flags); + + if (waitqueue_active(&dev->waitq)) + wake_up_interruptible(&dev->waitq); + + return 0; +} + +/** + * handles device removal event + * + * @param context pointer to the client context (smschar parameters block) + * + */ +static void smschar_onremove(void *context) +{ + struct smschar_device_t *dev = (struct smschar_device_t *)context; + + smschar_unregister_client(dev); + dev->coredev = NULL; +} + +/** + * registers client associated with the node + * + * @param inode Inode concerned. + * @param file File concerned. + * + * @return 0 on success, <0 on error. + */ +static int smschar_open(struct inode *inode, struct file *file) +{ + struct smschar_device_t *dev = container_of(inode->i_cdev, + struct smschar_device_t, + cdev); + int rc = -ENODEV; + + sms_info("entering index %d\n", dev->device_index); + if (dev->coredev) { + struct smsclient_params_t params; + params.initial_id = dev->device_index ? + dev->device_index : SMS_HOST_LIB; + params.data_type = dev->device_index ? MSG_SMS_DAB_CHANNEL : 0; + params.onresponse_handler = smschar_onresponse; + params.onremove_handler = smschar_onremove; + params.context = dev; + + rc = smscore_register_client(dev->coredev, ¶ms, + &dev->smsclient); + if (!rc) + file->private_data = dev; + dev->cancel_waitq = 0; + g_pnp_status_changed = 1; + } + + if (rc) + sms_err(" exiting, rc %d\n", rc); + + return rc; +} + +/** + * unregisters client associated with the node + * + * @param inode Inode concerned. + * @param file File concerned. + * + */ +static int smschar_release(struct inode *inode, struct file *file) +{ + smschar_unregister_client(file->private_data); + + sms_info("exiting\n"); + + return 0; +} + +/** + * copies data from buffers in incoming queue into a user buffer + * + * @param file File structure. + * @param buf Source buffer. + * @param count Size of source buffer. + * @param f_pos Position in file (ignored). + * + * @return Number of bytes read, or <0 on error. + */ +static ssize_t smschar_read(struct file *file, char __user *buf, + size_t count, loff_t *f_pos) +{ + struct smschar_device_t *dev = file->private_data; + unsigned long flags; + int rc, copied = 0; + + if (!buf) { + sms_err("Bad pointer recieved from user.\n"); + return -EFAULT; + } + if (!dev->coredev || !dev->smsclient) { + sms_err("no client\n"); + return -ENODEV; + } + rc = wait_event_interruptible(dev->waitq, + !list_empty(&dev->pending_data) + || (dev->cancel_waitq)); + if (rc < 0) { + sms_err("wait_event_interruptible error %d\n", rc); + return rc; + } + if (dev->cancel_waitq) + return 0; + if (!dev->smsclient) { + sms_err("no client\n"); + return -ENODEV; + } + spin_lock_irqsave(&dev->lock, flags); + + while (!list_empty(&dev->pending_data) && (copied < count)) { + struct smscore_buffer_t *cb = + (struct smscore_buffer_t *)dev->pending_data.next; + int actual_size = min(((int)count - copied), cb->size); + if (copy_to_user(&buf[copied], &((char *)cb->p)[cb->offset], + actual_size)) { + sms_err("copy_to_user failed\n"); + spin_unlock_irqrestore(&dev->lock, flags); + return -EFAULT; + } + copied += actual_size; + cb->offset += actual_size; + cb->size -= actual_size; + + if (!cb->size) { + list_del(&cb->entry); + smscore_putbuffer(dev->coredev, cb); + dev->pending_count--; + } + } + spin_unlock_irqrestore(&dev->lock, flags); + return copied; +} + +/** + * sends the buffer to the associated device + * + * @param file File structure. + * @param buf Source buffer. + * @param count Size of source buffer. + * @param f_pos Position in file (ignored). + * + * @return Number of bytes read, or <0 on error. + */ +static ssize_t smschar_write(struct file *file, const char __user *buf, + size_t count, loff_t *f_pos) +{ + struct smschar_device_t *dev; + void *buffer; + + if (file == NULL) { + sms_err("file is NULL\n"); + return EINVAL; + } + + if (file->private_data == NULL) { + sms_err("file->private_data is NULL\n"); + return -EINVAL; + } + + dev = file->private_data; + if (!dev->smsclient) { + sms_err("no client\n"); + return -ENODEV; + } + + buffer = kmalloc(ALIGN(count, SMS_ALLOC_ALIGNMENT) + SMS_DMA_ALIGNMENT, + GFP_KERNEL | GFP_DMA); + if (buffer) { + void *msg_buffer = (void *)SMS_ALIGN_ADDRESS(buffer); + + if (!copy_from_user(msg_buffer, buf, count)) + smsclient_sendrequest(dev->smsclient, + msg_buffer, count); + else + count = 0; + + kfree(buffer); + } + + return count; +} + +static int smschar_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct smschar_device_t *dev = file->private_data; + return smscore_map_common_buffer(dev->coredev, vma); +} + +/** + * waits until buffer inserted into a queue. when inserted buffer offset + * are reportedto the calling process. previously reported buffer is + * returned to smscore pool. + * + * @param dev pointer to smschar parameters block + * @param touser pointer to a structure that receives incoming buffer offsets + * + * @return 0 on success, <0 on error. + */ +static int smschar_wait_get_buffer(struct smschar_device_t *dev, + struct smschar_buffer_t *touser) +{ + unsigned long flags; + int rc; + + spin_lock_irqsave(&dev->lock, flags); + + if (dev->currentcb) { + smscore_putbuffer(dev->coredev, dev->currentcb); + dev->currentcb = NULL; + dev->pending_count--; + } + + spin_unlock_irqrestore(&dev->lock, flags); + + rc = wait_event_interruptible(dev->waitq, + !list_empty(&dev->pending_data) + || (dev->cancel_waitq)); + if (rc < 0) { + sms_err("wait_event_interruptible error, rc=%d\n", rc); + return rc; + } + if (dev->cancel_waitq) { + touser->offset = 0; + touser->size = 0; + return 0; + } + if (!dev->smsclient) { + sms_err("no client\n"); + return -ENODEV; + } + + spin_lock_irqsave(&dev->lock, flags); + + if (!list_empty(&dev->pending_data)) { + struct smscore_buffer_t *cb = + (struct smscore_buffer_t *)dev->pending_data.next; + touser->offset = cb->offset_in_common + cb->offset; + touser->size = cb->size; + + list_del(&cb->entry); + + dev->currentcb = cb; + } else { + touser->offset = 0; + touser->size = 0; + } + + spin_unlock_irqrestore(&dev->lock, flags); + + return 0; +} + +/** + * poll for data availability + * + * @param file File structure. + * @param wait kernel polling table. + * + * @return POLLIN flag if read data is available. + */ +static unsigned int smschar_poll(struct file *file, + struct poll_table_struct *wait) +{ + struct smschar_device_t *dev; + int mask = 0; + + if (file == NULL) { + sms_err("file is NULL\n"); + return EINVAL; + } + + if (file->private_data == NULL) { + sms_err("file->private_data is NULL\n"); + return -EINVAL; + } + + dev = file->private_data; + + if (list_empty(&dev->pending_data)) { + sms_info("No data is ready, waiting for data recieve.\n"); + poll_wait(file, &dev->waitq, wait); + } + + if (!list_empty(&dev->pending_data)) + mask |= POLLIN | POLLRDNORM; + return mask; +} + +static int smschar_ioctl(struct inode *inode, struct file *file, + unsigned int cmd, unsigned long arg) +{ + struct smschar_device_t *dev = file->private_data; + void __user *up = (void __user *)arg; + + if (!dev->coredev || !dev->smsclient) { + sms_err("no client\n"); + return -ENODEV; + } + + switch (cmd) { + case SMSCHAR_SET_DEVICE_MODE: + return smscore_set_device_mode(dev->coredev, (int)arg); + + case SMSCHAR_GET_DEVICE_MODE: + { + if (put_user(smscore_get_device_mode(dev->coredev), + (int *)up)) + return -EFAULT; + break; + } + case SMSCHAR_IS_DEVICE_PNP_EVENT: + { + sms_info("Waiting for PnP event.\n"); + wait_event_interruptible(g_pnp_event, + !g_pnp_status_changed); + g_pnp_status_changed = 0; + sms_info("PnP Event %d.\n", g_smschar_inuse); + if (put_user(g_smschar_inuse, (int *)up)) + return -EFAULT; + break; + } + case SMSCHAR_GET_BUFFER_SIZE: + { + if (put_user + (smscore_get_common_buffer_size(dev->coredev), + (int *)up)) + return -EFAULT; + + break; + } + + case SMSCHAR_WAIT_GET_BUFFER: + { + struct smschar_buffer_t touser; + int rc; + + rc = smschar_wait_get_buffer(dev, &touser); + if (rc < 0) + return rc; + + if (copy_to_user(up, &touser, + sizeof(struct smschar_buffer_t))) + return -EFAULT; + + break; + } + case SMSCHAR_CANCEL_WAIT_BUFFER: + { + dev->cancel_waitq = 1; + wake_up_interruptible(&dev->waitq); + break; + } + case SMSCHAR_GET_FW_FILE_NAME: + { + if (!up) + return -EINVAL; + return smscore_get_fw_filename(dev->coredev, + ((struct + smschar_get_fw_filename_ioctl_t + *)up)->mode, + ((struct + smschar_get_fw_filename_ioctl_t + *)up)->filename); + } + case SMSCHAR_SEND_FW_FILE: + { + if (!up) + return -EINVAL; + return smscore_send_fw_file(dev->coredev, + ((struct + smschar_send_fw_file_ioctl_t + *)up)->fw_buf, + ((struct + smschar_send_fw_file_ioctl_t + *)up)->fw_size); + } + + default: + return -ENOIOCTLCMD; + } + + return 0; +} + +struct file_operations smschar_fops = { + .owner = THIS_MODULE, + .read = smschar_read, + .write = smschar_write, + .open = smschar_open, + .release = smschar_release, + .mmap = smschar_mmap, + .poll = smschar_poll, + .ioctl = smschar_ioctl, +}; + +static int smschar_setup_cdev(struct smschar_device_t *dev, int index) +{ + int rc, devno = MKDEV(smschar_major, smschar_minor + index); + + cdev_init(&dev->cdev, &smschar_fops); + + dev->cdev.owner = THIS_MODULE; + dev->cdev.ops = &smschar_fops; + + kobject_set_name(&dev->cdev.kobj, "Siano_sms%d", index); + rc = cdev_add(&dev->cdev, devno, 1); + sms_info("exiting %p %d, rc %d", dev, index, rc); + + return rc; +} + +/** + * smschar callback that called when device plugged in/out. the function + * register or unregisters char device interface according to plug in/out + * + * @param coredev pointer to device that is being plugged in/out + * @param device pointer to system device object + * @param arrival 1 on plug-on, 0 othewise + * + * @return 0 on success, <0 on error. + */ +static int smschar_hotplug(struct smscore_device_t *coredev, + struct device *device, int arrival) +{ + int rc = 0, i; + + sms_info("entering %d\n", arrival); + + g_pnp_status_changed = 1; + if (arrival) { + /* currently only 1 instance supported */ + if (!g_smschar_inuse) { + /* data notification callbacks assignment */ + memset(smschar_devices, 0, SMSCHAR_NR_DEVS * + sizeof(struct smschar_device_t)); + + /* Initialize each device. */ + for (i = 0; i < SMSCHAR_NR_DEVS; i++) { + sms_info("create device %d", i); + smschar_setup_cdev(&smschar_devices[i], i); + INIT_LIST_HEAD(&smschar_devices[i]. + pending_data); + spin_lock_init(&smschar_devices[i].lock); + init_waitqueue_head(&smschar_devices[i].waitq); + + smschar_devices[i].coredev = coredev; + smschar_devices[i].device_index = i; + } + g_smschar_inuse = 1; + wake_up_interruptible(&g_pnp_event); + } + } else { + /* currently only 1 instance supported */ + if (g_smschar_inuse) { + /* Get rid of our char dev entries */ + for (i = 0; i < SMSCHAR_NR_DEVS; i++) { + cdev_del(&smschar_devices[i].cdev); + sms_info("remove device %d\n", i); + } + + g_smschar_inuse = 0; + wake_up_interruptible(&g_pnp_event); + } + } + + sms_info("exiting, rc %d\n", rc); + + return rc; /* succeed */ +} + +int smschar_register(void) +{ + dev_t devno = MKDEV(smschar_major, smschar_minor); + int rc; + + sms_info("registering device major=%d minor=%d\n", smschar_major, + smschar_minor); + if (smschar_major) { + rc = register_chrdev_region(devno, SMSCHAR_NR_DEVS, "smschar"); + } else { + rc = alloc_chrdev_region(&devno, smschar_minor, + SMSCHAR_NR_DEVS, "smschar"); + smschar_major = MAJOR(devno); + } + + if (rc < 0) { + sms_warn("smschar: can't get major %d\n", smschar_major); + return rc; + } + init_waitqueue_head(&g_pnp_event); + + return smscore_register_hotplug(smschar_hotplug); +} + +void smschar_unregister(void) +{ + dev_t devno = MKDEV(smschar_major, smschar_minor); + + unregister_chrdev_region(devno, SMSCHAR_NR_DEVS); + smscore_unregister_hotplug(smschar_hotplug); + sms_info("unregistered\n"); +} diff -r a9dd63fe3974 -r 0d667d493399 linux/drivers/media/dvb/siano/smscharioctl.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/linux/drivers/media/dvb/siano/smscharioctl.h Tue Jan 13 13:59:28 2009 +0200 @@ -0,0 +1,52 @@ +/**************************************************************** + +Siano Mobile Silicon, Inc. +MDTV receiver kernel modules. +Copyright (C) 2006-2008, Uri Shkolnik + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + + 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. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +****************************************************************/ +#ifndef __SMS_CHAR_IOCTL_H__ +#define __SMS_CHAR_IOCTL_H__ + +#include + +struct smschar_buffer_t { + unsigned long offset; /* offset in common buffer (mapped to user) */ + int size; +}; + +struct smschar_get_fw_filename_ioctl_t { + int mode; + char filename[200]; +}; + +struct smschar_send_fw_file_ioctl_t { + char *fw_buf; + int fw_size; +}; + +#define SMSCHAR_SET_DEVICE_MODE _IOW('K', 0, int) +#define SMSCHAR_GET_DEVICE_MODE _IOR('K', 1, int) +#define SMSCHAR_GET_BUFFER_SIZE _IOR('K', 2, int) +#define SMSCHAR_WAIT_GET_BUFFER _IOR('K', 3, struct smschar_buffer_t) +#define SMSCHAR_IS_DEVICE_PNP_EVENT _IOR('K', 4, int) +#define SMSCHAR_GET_FW_FILE_NAME \ + _IOWR('K', 5, struct smschar_get_fw_filename_ioctl_t) +#define SMSCHAR_SEND_FW_FILE \ + _IOW('K', 6, struct smschar_send_fw_file_ioctl_t) +#define SMSCHAR_CANCEL_WAIT_BUFFER _IO('K', 7) + +#endif /* __SMS_CHAR_IOCTL_H__ */ diff -r a9dd63fe3974 -r 0d667d493399 linux/drivers/media/dvb/siano/smsnet.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/linux/drivers/media/dvb/siano/smsnet.c Tue Jan 13 13:59:28 2009 +0200 @@ -0,0 +1,442 @@ +/**************************************************************** + +Siano Mobile Silicon, Inc. +MDTV receiver kernel modules. +Copyright (C) 2006-2008, Uri Shkolnik + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + + 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. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . + +****************************************************************/ + +#include +#include +#include /* struct device, and other headers */ +#include /* eth_type_trans */ +#include /* struct iphdr */ +#include /* struct ipv6hdr */ +#include + +#include "smscoreapi.h" + +#define IPV4VERSION 0x40 +#define IPV6VERSION 0x60 +#define GETIPVERSION(_x_) ((_x_) & 0xf0) + +struct smsnet_client_t { + struct list_head entry; + + struct smscore_device_t *coredev; + struct smscore_client_t *smsclient; + + int packet_length, splitpacket_length; + int header_length, splitheader_length; + u8 splitpacket[ETH_DATA_LEN]; +}; + +struct list_head g_smsnet_clients; +struct mutex g_smsnet_clientslock; + +struct net_device *g_smsnet_device; +struct net_device_stats g_smsnet_stats; + +int g_smsnet_inuse; + +void smsnet_send_packet(u8 *buffer, int length) +{ + u8 *eth; + struct sk_buff *skb = dev_alloc_skb(length + ETH_HLEN + NET_IP_ALIGN); + + if (!skb) { + g_smsnet_stats.rx_dropped++; + return; + } + + skb_reserve(skb, NET_IP_ALIGN); + + eth = (u8 *) skb_put(skb, length + ETH_HLEN); + memcpy(eth + ETH_HLEN, buffer, length); + + eth[6] = 0; + eth[7] = 1; + eth[8] = 1; + eth[9] = 3; + eth[10] = 4; + eth[11] = 5; + + if (GETIPVERSION(*buffer) == IPV4VERSION) { + eth[0] = 1; + eth[1] = 0; + eth[2] = 0x5e; + eth[3] = buffer[17] & 0x7f; + eth[4] = buffer[18]; + eth[5] = buffer[19]; + + eth[12] = 0x08; + eth[13] = 0x00; + } else { + /* ip6 mcast address */ + eth[0] = 0x33; + eth[1] = 0x33; + eth[2] = buffer[36]; + eth[3] = buffer[37]; + eth[4] = buffer[38]; + eth[5] = buffer[39]; + + eth[12] = 0x86; + eth[13] = 0xdd; + } + + skb->dev = g_smsnet_device; + skb->protocol = eth_type_trans(skb, g_smsnet_device); + skb->ip_summed = CHECKSUM_COMPLETE; + + g_smsnet_stats.rx_packets++; + g_smsnet_stats.rx_bytes += skb->len; + + netif_rx(skb); +} + +int check_header(struct smsnet_client_t *client, u8 *buffer) +{ + struct iphdr *ip4_hdr; + struct ipv6hdr *ip6_hdr; + struct udphdr *udp_hdr; + u16 csum; + + /* check if packet header is valid and it is a UDP */ + if (GETIPVERSION(*buffer) == IPV4VERSION) { + ip4_hdr = (struct iphdr *)buffer; + csum = ip4_hdr->check; + + ip4_hdr->check = 0; + + /* check header checksum for IPv4 packets */ + if (ip4_hdr->protocol != IPPROTO_UDP || csum != + ip_fast_csum(buffer, ip4_hdr->ihl)) { + ip4_hdr->check = csum; + return 0; + } + + ip4_hdr->check = csum; + client->packet_length = ntohs(ip4_hdr->tot_len); + } else { + ip6_hdr = (struct ipv6hdr *)buffer; + udp_hdr = (struct udphdr *)(ip6_hdr + 1); + + if ((ip6_hdr->nexthdr != IPPROTO_UDP) || + (ip6_hdr->payload_len != udp_hdr->len)) + return 0; + + client->packet_length = ntohs(ip6_hdr->payload_len) + + sizeof(struct ipv6hdr); + } + + /* check for abnormal packet length */ + if (client->packet_length > ETH_DATA_LEN) + return 0; + + return 1; +} + +int smsnet_onresponse(void *context, struct smscore_buffer_t *cb) +{ + struct smsnet_client_t *client = (struct smsnet_client_t *)context; + int length, rest; + u8 ip_ver, *buffer; + + buffer = ((u8 *) cb->p) + cb->offset + sizeof(struct SmsMsgHdr_ST); + length = cb->size - sizeof(struct SmsMsgHdr_ST); + + if (client->splitheader_length) { + /* how much data is missing ? */ + rest = client->header_length - client->splitheader_length; + + /* do we have enough in this buffer ? */ + rest = min(rest, length); + + memcpy(&client->splitpacket[client->splitheader_length], + buffer, rest); + + client->splitheader_length += rest; + + if (client->splitheader_length != client->header_length) + goto exit; + + if (check_header(client, client->splitpacket)) { + buffer += rest; + length -= rest; + + client->splitpacket_length = client->header_length; + } + + client->splitheader_length = 0; + } + + if (client->splitpacket_length) { + /* how much data is missing ? */ + rest = client->packet_length - client->splitpacket_length; + + /* do we have enough in this buffer ? */ + rest = min(rest, length); + + memcpy(&client->splitpacket[client->splitpacket_length], + buffer, rest); + + client->splitpacket_length += rest; + + if (client->splitpacket_length != client->packet_length) + goto exit; + + client->splitpacket_length = 0; + + smsnet_send_packet(client->splitpacket, client->packet_length); + + buffer += rest; + length -= rest; + } + + while (length > 0) { + ip_ver = GETIPVERSION(*buffer); + while (length && (ip_ver != IPV4VERSION) && + (ip_ver != IPV6VERSION)) { + buffer++; + length--; + ip_ver = GETIPVERSION(*buffer); + } + + /* No more data in section */ + if (!length) + break; + + /* Set the header length at start of packet according + to the version no problem with the IP header cast, since + we have at least 1 byte (we use only the first byte) */ + client->header_length = + (ip_ver == IPV4VERSION) ? + (((struct iphdr *)buffer)->ihl * 4) : + (sizeof(struct ipv6hdr) + sizeof(struct udphdr)); + + /*Check that Header length is at least 20 (min IPv4 length) */ + if (client->header_length < 20) { + length--; + buffer++; + continue; + } + + /* check split header case */ + if (client->header_length > length) { + memcpy(client->splitpacket, buffer, length); + client->splitheader_length = length; + break; + } + + if (check_header(client, buffer)) { + /* check split packet case */ + if (client->packet_length > length) { + memcpy(client->splitpacket, buffer, length); + client->splitpacket_length = length; + break; + } + } else { + length--; + buffer++; + continue; + } + + smsnet_send_packet(buffer, client->packet_length); + + buffer += client->packet_length; + length -= client->packet_length; + } + +exit: + smscore_putbuffer(client->coredev, cb); + + return 0; +} + +void smsnet_unregister_client(struct smsnet_client_t *client) +{ + /* must be called under clientslock */ + + list_del(&client->entry); + + smscore_unregister_client(client->smsclient); + kfree(client); +} + +void smsnet_onremove(void *context) +{ + kmutex_lock(&g_smsnet_clientslock); + + smsnet_unregister_client((struct smsnet_client_t *)context); + + kmutex_unlock(&g_smsnet_clientslock); +} + +int smsnet_hotplug(struct smscore_device_t *coredev, struct device *device, + int arrival) +{ + struct smsclient_params_t params; + struct smsnet_client_t *client; + int rc; + + /* device removal handled by onremove callback */ + if (!arrival) + return 0; + + client = kzalloc(sizeof(struct smsnet_client_t), GFP_KERNEL); + if (!client) { + sms_err("kmalloc() failed"); + return -ENOMEM; + } + + params.initial_id = 1; + params.data_type = MSG_SMS_DATA_MSG; + params.onresponse_handler = smsnet_onresponse; + params.onremove_handler = smsnet_onremove; + params.context = client; + + rc = smscore_register_client(coredev, ¶ms, &client->smsclient); + if (rc < 0) { + sms_err("smscore_register_client() failed %d", rc); + kfree(client); + return rc; + } + + client->coredev = coredev; + kmutex_lock(&g_smsnet_clientslock); + list_add(&client->entry, &g_smsnet_clients); + kmutex_unlock(&g_smsnet_clientslock); + sms_info("success"); + return 0; +} + +static int smsnet_open(struct net_device *dev) +{ + g_smsnet_inuse++; + + netif_start_queue(dev); + sms_info("smsnet in use %d", g_smsnet_inuse); + return 0; +} + +static int smsnet_stop(struct net_device *dev) +{ + netif_stop_queue(dev); + g_smsnet_inuse--; + sms_info("smsnet in use %d", g_smsnet_inuse); + return 0; +} + +static int smsnet_hard_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + sms_info("enter"); + dev_kfree_skb(skb); + return 0; +} + +static struct net_device_stats *smsnet_get_stats(struct net_device *dev) +{ + return &g_smsnet_stats; +} + +static void smsnet_set_multicast_list(struct net_device *dev) +{ + sms_info("mc count %d", dev->mc_count); + if (dev->mc_count) { + struct dev_mc_list *p; + + for (p = dev->mc_list; p; p = p->next) + sms_info( + "%d %02x %02x %02x %02x %02x %02x %02x %02x", + p->dmi_addrlen, p->dmi_addr[0], + p->dmi_addr[1], p->dmi_addr[2], + p->dmi_addr[3], p->dmi_addr[4], + p->dmi_addr[5], p->dmi_addr[6], p->dmi_addr[7] + ); + } +} + +static void smsnet_setup_device(struct net_device *dev) +{ + ether_setup(dev); + + dev->open = smsnet_open; + dev->stop = smsnet_stop; + dev->hard_start_xmit = smsnet_hard_start_xmit; + dev->get_stats = smsnet_get_stats; + dev->set_multicast_list = smsnet_set_multicast_list; + + dev->mc_count = 0; + + memcpy(dev->dev_addr, "\0SIANO", ETH_ALEN); + + dev->flags |= IFF_NOARP | IFF_MULTICAST | IFF_UP; + dev->features |= NETIF_F_IP_CSUM; +} + +int smsnet_register(void) +{ + int rc; + + INIT_LIST_HEAD(&g_smsnet_clients); + kmutex_init(&g_smsnet_clientslock); + + memset(&g_smsnet_stats, 0, sizeof(g_smsnet_stats)); + + g_smsnet_device = alloc_netdev(0, "sms", smsnet_setup_device); + if (!g_smsnet_device) { + sms_err("alloc_netdev() failed"); + return -ENOMEM; + } + + rc = register_netdev(g_smsnet_device); + if (rc < 0) { + sms_err("register_netdev() failed %d\n", rc); + free_netdev(g_smsnet_device); + return rc; + } + + rc = smscore_register_hotplug(smsnet_hotplug); + sms_info("exit - rc %d", rc); + + return rc; +} + +void smsnet_unregister(void) +{ + if (g_smsnet_device) { + unregister_netdev(g_smsnet_device); + free_netdev(g_smsnet_device); + + g_smsnet_device = NULL; + } + + smscore_unregister_hotplug(smsnet_hotplug); + + kmutex_lock(&g_smsnet_clientslock); + + while (!list_empty(&g_smsnet_clients)) + smsnet_unregister_client((struct smsnet_client_t *) + g_smsnet_clients.next); + + kmutex_unlock(&g_smsnet_clientslock); + + sms_info("exit"); +} + +MODULE_DESCRIPTION("Siano Network Client module"); +MODULE_AUTHOR("Siano Mobile Silicon, Inc. (uris@siano-ms.com)"); +MODULE_LICENSE("GPL");