| /** @file mbt_char.c |
| * |
| * @brief This file contains the char device function calls |
| * |
| * Copyright (C) 2010-2015, Marvell International Ltd. |
| * |
| * This software file (the "File") is distributed by Marvell International |
| * Ltd. under the terms of the GNU General Public License Version 2, June 1991 |
| * (the "License"). You may use, redistribute and/or modify this File in |
| * accordance with the terms and conditions of the License, a copy of which |
| * is available by writing to the Free Software Foundation, Inc., |
| * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA or on the |
| * worldwide web at http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt. |
| * |
| * THE FILE IS DISTRIBUTED AS-IS, WITHOUT WARRANTY OF ANY KIND, AND THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE |
| * ARE EXPRESSLY DISCLAIMED. The License provides additional details about |
| * this warranty disclaimer. |
| * |
| */ |
| |
| #include <linux/path.h> |
| #include <linux/namei.h> |
| #include <linux/mount.h> |
| |
| #include "bt_drv.h" |
| #include "mbt_char.h" |
| |
| static LIST_HEAD(char_dev_list); |
| |
| static DEFINE_SPINLOCK(char_dev_list_lock); |
| |
| static int mbtchar_major = MBTCHAR_MAJOR_NUM; |
| |
| struct kobject * |
| chardev_get(struct char_dev *dev) |
| { |
| struct kobject *kobj; |
| |
| kobj = bt_priv_get(dev->m_dev->driver_data); |
| if (!kobj) |
| return NULL; |
| PRINTM(INFO, "dev get kobj\n"); |
| kobj = kobject_get(&dev->kobj); |
| if (!kobj) |
| bt_priv_put(dev->m_dev->driver_data); |
| return kobj; |
| } |
| |
| void |
| chardev_put(struct char_dev *dev) |
| { |
| if (dev) { |
| struct m_dev *m_dev = dev->m_dev; |
| PRINTM(INFO, "dev put kobj\n"); |
| kobject_put(&dev->kobj); |
| if (m_dev) |
| bt_priv_put(m_dev->driver_data); |
| } |
| } |
| |
| /** |
| * @brief Changes permissions of the dev |
| * |
| * @param name pointer to character |
| * @param mode mode_t type data |
| * @return 0--success otherwise failure |
| */ |
| int |
| mbtchar_chmod(char *name, mode_t mode) |
| { |
| struct path path; |
| struct inode *inode; |
| struct iattr newattrs; |
| int ret; |
| int retrycount = 0; |
| |
| ENTER(); |
| do { |
| os_sched_timeout(30); |
| ret = kern_path(name, LOOKUP_FOLLOW, &path); |
| if (++retrycount >= 10) { |
| PRINTM(ERROR, |
| "mbtchar_chmod(): fail to get kern_path\n"); |
| LEAVE(); |
| return -EFAULT; |
| } |
| } while (ret); |
| inode = path.dentry->d_inode; |
| |
| mutex_lock(&inode->i_mutex); |
| ret = mnt_want_write(path.mnt); |
| if (ret) |
| goto out_unlock; |
| newattrs.ia_mode = (mode & S_IALLUGO) | (inode->i_mode & ~S_IALLUGO); |
| newattrs.ia_valid = ATTR_MODE | ATTR_CTIME; |
| if (inode->i_op->setattr) |
| ret = inode->i_op->setattr(path.dentry, &newattrs); |
| else |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 35) |
| ret = simple_setattr(path.dentry, &newattrs); |
| #else |
| ret = inode_setattr(inode, &newattrs); |
| #endif |
| |
| mutex_unlock(&inode->i_mutex); |
| mnt_drop_write(path.mnt); |
| |
| path_put(&path); |
| LEAVE(); |
| return ret; |
| out_unlock: |
| mutex_unlock(&inode->i_mutex); |
| mnt_drop_write(path.mnt); |
| path_put(&path); |
| return ret; |
| } |
| |
| /** |
| * @brief Changes ownership of the dev |
| * |
| * @param name pointer to character |
| * @param user uid_t type data |
| * @param group gid_t type data |
| * @return 0--success otherwise failure |
| */ |
| int |
| mbtchar_chown(char *name, uid_t user, gid_t group) |
| { |
| struct path path; |
| struct inode *inode = NULL; |
| struct iattr newattrs; |
| int ret = 0; |
| int retrycount = 0; |
| |
| ENTER(); |
| do { |
| os_sched_timeout(30); |
| ret = kern_path(name, LOOKUP_FOLLOW, &path); |
| if (++retrycount >= 10) { |
| PRINTM(ERROR, |
| "mbtchar_chown(): fail to get kern_path\n"); |
| LEAVE(); |
| return -EFAULT; |
| } |
| } while (ret); |
| inode = path.dentry->d_inode; |
| mutex_lock(&inode->i_mutex); |
| ret = mnt_want_write(path.mnt); |
| if (ret) |
| goto out_unlock; |
| newattrs.ia_valid = ATTR_CTIME; |
| if (user != (uid_t) (-1)) { |
| newattrs.ia_valid |= ATTR_UID; |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 5, 0) |
| newattrs.ia_uid = user; |
| #else |
| newattrs.ia_uid = KUIDT_INIT(user); |
| #endif |
| } |
| if (group != (gid_t) (-1)) { |
| newattrs.ia_valid |= ATTR_GID; |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(3, 5, 0) |
| newattrs.ia_gid = group; |
| #else |
| newattrs.ia_gid = KGIDT_INIT(group); |
| #endif |
| } |
| if (!S_ISDIR(inode->i_mode)) |
| newattrs.ia_valid |= |
| ATTR_KILL_SUID | ATTR_KILL_SGID | ATTR_KILL_PRIV; |
| if (inode->i_op->setattr) |
| ret = inode->i_op->setattr(path.dentry, &newattrs); |
| else |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 35) |
| ret = simple_setattr(path.dentry, &newattrs); |
| #else |
| ret = inode_setattr(inode, &newattrs); |
| #endif |
| |
| mutex_unlock(&inode->i_mutex); |
| mnt_drop_write(path.mnt); |
| |
| path_put(&path); |
| LEAVE(); |
| return ret; |
| out_unlock: |
| mutex_unlock(&inode->i_mutex); |
| mnt_drop_write(path.mnt); |
| path_put(&path); |
| return ret; |
| } |
| |
| /** |
| * @brief write handler for char dev |
| * |
| * @param filp pointer to structure file |
| * @param buf pointer to char buffer |
| * @param count size of receive buffer |
| * @param f_pos pointer to loff_t type data |
| * @return number of bytes written |
| */ |
| ssize_t |
| chardev_write(struct file * filp, const char *buf, size_t count, loff_t * f_pos) |
| { |
| int nwrite = 0; |
| struct sk_buff *skb; |
| struct char_dev *dev = (struct char_dev *)filp->private_data; |
| struct m_dev *m_dev = NULL; |
| |
| ENTER(); |
| |
| if (!dev || !dev->m_dev) { |
| LEAVE(); |
| return -ENXIO; |
| } |
| m_dev = dev->m_dev; |
| if (!test_bit(HCI_UP, &m_dev->flags)) { |
| LEAVE(); |
| return -EBUSY; |
| } |
| nwrite = count; |
| skb = bt_skb_alloc(count, GFP_ATOMIC); |
| if (!skb) { |
| PRINTM(ERROR, "mbtchar_write(): fail to alloc skb\n"); |
| LEAVE(); |
| return -ENOMEM; |
| } |
| |
| if (copy_from_user((void *)skb_put(skb, count), buf, count)) { |
| PRINTM(ERROR, "mbtchar_write(): cp_from_user failed\n"); |
| kfree_skb(skb); |
| nwrite = -EFAULT; |
| goto exit; |
| } |
| |
| skb->dev = (void *)m_dev; |
| bt_cb(skb)->pkt_type = *((unsigned char *)skb->data); |
| skb_pull(skb, 1); |
| |
| PRINTM(DATA, "Write: pkt_type: 0x%x, len=%d @%lu\n", |
| bt_cb(skb)->pkt_type, skb->len, jiffies); |
| DBG_HEXDUMP(DAT_D, "chardev_write", skb->data, skb->len); |
| |
| /* Send skb to the hci wrapper layer */ |
| if (m_dev->send(m_dev, skb)) { |
| PRINTM(ERROR, "Write: Fail\n"); |
| nwrite = 0; |
| /* Send failed */ |
| kfree_skb(skb); |
| } |
| exit: |
| LEAVE(); |
| return nwrite; |
| } |
| |
| /** |
| * @brief read handler for BT char dev |
| * |
| * @param filp pointer to structure file |
| * @param buf pointer to char buffer |
| * @param count size of receive buffer |
| * @param f_pos pointer to loff_t type data |
| * @return number of bytes read |
| */ |
| ssize_t |
| chardev_read(struct file * filp, char *buf, size_t count, loff_t * f_pos) |
| { |
| struct char_dev *dev = (struct char_dev *)filp->private_data; |
| struct m_dev *m_dev = NULL; |
| DECLARE_WAITQUEUE(wait, current); |
| ssize_t ret = 0; |
| struct sk_buff *skb = NULL; |
| |
| ENTER(); |
| if (!dev || !dev->m_dev) { |
| LEAVE(); |
| return -ENXIO; |
| } |
| m_dev = dev->m_dev; |
| /* Wait for rx data */ |
| add_wait_queue(&m_dev->req_wait_q, &wait); |
| while (1) { |
| set_current_state(TASK_INTERRUPTIBLE); |
| skb = skb_dequeue(&m_dev->rx_q); |
| if (skb) |
| break; |
| if (!test_bit(HCI_UP, &m_dev->flags)) { |
| ret = -EBUSY; |
| break; |
| } |
| |
| if (filp->f_flags & O_NONBLOCK) { |
| ret = -EAGAIN; |
| break; |
| } |
| if (signal_pending(current)) { |
| ret = -EINTR; |
| break; |
| } |
| schedule(); |
| } |
| set_current_state(TASK_RUNNING); |
| remove_wait_queue(&m_dev->req_wait_q, &wait); |
| |
| if (!skb) |
| goto out; |
| |
| if (m_dev->read_continue_flag == 0) { |
| /* Put type byte before the data */ |
| memcpy(skb_push(skb, 1), &bt_cb(skb)->pkt_type, 1); |
| PRINTM(DATA, "Read: pkt_type: 0x%x, len=%d @%lu\n", |
| bt_cb(skb)->pkt_type, skb->len, jiffies); |
| } |
| DBG_HEXDUMP(DAT_D, "chardev_read", skb->data, skb->len); |
| if (skb->len > count) { |
| /* user data length is smaller than the skb length */ |
| if (copy_to_user(buf, skb->data, count)) { |
| ret = -EFAULT; |
| goto outf; |
| } |
| skb_pull(skb, count); |
| skb_queue_head(&m_dev->rx_q, skb); |
| m_dev->read_continue_flag = 1; |
| wake_up_interruptible(&m_dev->req_wait_q); |
| ret = count; |
| goto out; |
| } else { |
| if (copy_to_user(buf, skb->data, skb->len)) { |
| ret = -EFAULT; |
| goto outf; |
| } |
| m_dev->read_continue_flag = 0; |
| ret = skb->len; |
| } |
| outf: |
| kfree_skb(skb); |
| out: |
| if (m_dev->wait_rx_complete && skb_queue_empty(&m_dev->rx_q)) { |
| m_dev->rx_complete_flag = TRUE; |
| wake_up_interruptible(&m_dev->rx_wait_q); |
| } |
| LEAVE(); |
| return ret; |
| } |
| |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 36) |
| /** |
| * @brief ioctl common handler for char dev |
| * |
| * @param inode pointer to structure inode |
| * @param filp pointer to structure file |
| * @param cmd contains the IOCTL |
| * @param arg contains the arguement |
| * @return 0--success otherwise failure |
| */ |
| int |
| char_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, void *arg) |
| #else |
| /** |
| * @brief ioctl common handler for char dev |
| * |
| * @param filp pointer to structure file |
| * @param cmd contains the IOCTL |
| * @param arg contains the arguement |
| * @return 0--success otherwise failure |
| */ |
| long |
| char_ioctl(struct file *filp, unsigned int cmd, void *arg) |
| #endif |
| { |
| struct char_dev *dev = (struct char_dev *)filp->private_data; |
| struct m_dev *m_dev = NULL; |
| |
| ENTER(); |
| if (!dev || !dev->m_dev) { |
| LEAVE(); |
| return -ENXIO; |
| } |
| m_dev = dev->m_dev; |
| PRINTM(INFO, "IOCTL: cmd=%d\n", cmd); |
| switch (cmd) { |
| case MBTCHAR_IOCTL_RELEASE: |
| m_dev->close(m_dev); |
| break; |
| case MBTCHAR_IOCTL_QUERY_TYPE: |
| m_dev->query(m_dev, arg); |
| break; |
| default: |
| break; |
| } |
| LEAVE(); |
| return 0; |
| } |
| |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 36) |
| /** |
| * @brief ioctl handler for char dev |
| * |
| * @param inode pointer to structure inode |
| * @param filp pointer to structure file |
| * @param cmd contains the IOCTL |
| * @param arg contains the arguement |
| * @return 0--success otherwise failure |
| */ |
| int |
| chardev_ioctl(struct inode *inode, struct file *filp, |
| unsigned int cmd, unsigned long arg) |
| #else |
| /** |
| * @brief ioctl handler for char dev |
| * |
| * @param filp pointer to structure file |
| * @param cmd contains the IOCTL |
| * @param arg contains the arguement |
| * @return 0--success otherwise failure |
| */ |
| long |
| chardev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) |
| #endif |
| { |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 36) |
| return char_ioctl(inode, filp, cmd, (void *)arg); |
| #else |
| return char_ioctl(filp, cmd, (void *)arg); |
| #endif |
| } |
| |
| #ifdef CONFIG_COMPAT |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 36) |
| /** |
| * @brief compat ioctl handler for char dev |
| * |
| * @param inode pointer to structure inode |
| * @param filp pointer to structure file |
| * @param cmd contains the IOCTL |
| * @param arg contains the arguement |
| * @return 0--success otherwise failure |
| */ |
| int |
| chardev_ioctl_compat(struct inode *inode, struct file *filp, |
| unsigned int cmd, unsigned long arg) |
| #else |
| /** |
| * @brief compat ioctl handler for char dev |
| * |
| * @param filp pointer to structure file |
| * @param cmd contains the IOCTL |
| * @param arg contains the arguement |
| * @return 0--success otherwise failure |
| */ |
| long |
| chardev_ioctl_compat(struct file *filp, unsigned int cmd, unsigned long arg) |
| #endif |
| { |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 36) |
| return char_ioctl(inode, filp, cmd, compat_ptr(arg)); |
| #else |
| return char_ioctl(filp, cmd, compat_ptr(arg)); |
| #endif |
| } |
| #endif /* CONFIG_COMPAT */ |
| |
| /** |
| * @brief open handler for char dev |
| * |
| * @param inode pointer to structure inode |
| * @param filp pointer to structure file |
| * @return 0--success otherwise failure |
| */ |
| int |
| chardev_open(struct inode *inode, struct file *filp) |
| { |
| int ret = 0; |
| struct char_dev *dev = NULL; |
| struct m_dev *m_dev = NULL; |
| struct char_dev *cdev = NULL; |
| struct list_head *p = NULL; |
| ENTER(); |
| |
| list_for_each(p, &char_dev_list) { |
| cdev = list_entry(p, struct char_dev, list); |
| if (mbtchar_major == MAJOR(inode->i_cdev->dev) && |
| cdev->minor == MINOR(inode->i_cdev->dev)) { |
| dev = cdev; |
| break; |
| } |
| } |
| if (!dev) { |
| PRINTM(ERROR, "cannot find dev from inode\n"); |
| LEAVE(); |
| return -ENXIO; |
| } |
| if (!chardev_get(dev)) { |
| LEAVE(); |
| return -ENXIO; |
| } |
| filp->private_data = dev; /* for other methods */ |
| m_dev = dev->m_dev; |
| mdev_req_lock(m_dev); |
| if (test_bit(HCI_UP, &m_dev->flags)) { |
| ret = -EALREADY; |
| goto done; |
| } |
| if (m_dev->open(m_dev)) { |
| ret = -EIO; |
| goto done; |
| } |
| set_bit(HCI_UP, &m_dev->flags); |
| |
| done: |
| mdev_req_unlock(m_dev); |
| if (ret) |
| chardev_put(dev); |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief release handler for char dev |
| * |
| * @param inode pointer to structure inode |
| * @param filp pointer to structure file |
| * @return 0--success otherwise failure |
| */ |
| int |
| chardev_release(struct inode *inode, struct file *filp) |
| { |
| int ret = 0; |
| struct char_dev *dev = (struct char_dev *)filp->private_data; |
| struct m_dev *m_dev = NULL; |
| ENTER(); |
| if (!dev) { |
| LEAVE(); |
| return -ENXIO; |
| } |
| m_dev = dev->m_dev; |
| if (m_dev) |
| ret = dev->m_dev->close(dev->m_dev); |
| filp->private_data = NULL; |
| chardev_put(dev); |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief poll handler for char dev |
| * |
| * @param filp pointer to structure file |
| * @param wait pointer to poll_table structure |
| * @return mask |
| */ |
| static unsigned int |
| chardev_poll(struct file *filp, poll_table * wait) |
| { |
| unsigned int mask; |
| struct char_dev *dev = (struct char_dev *)filp->private_data; |
| struct m_dev *m_dev = NULL; |
| ENTER(); |
| if (!dev || !dev->m_dev) { |
| LEAVE(); |
| return -ENXIO; |
| } |
| |
| m_dev = dev->m_dev; |
| poll_wait(filp, &m_dev->req_wait_q, wait); |
| mask = POLLOUT | POLLWRNORM; |
| if (skb_peek(&m_dev->rx_q)) |
| mask |= POLLIN | POLLRDNORM; |
| if (!test_bit(HCI_UP, &(m_dev->flags))) |
| mask |= POLLHUP; |
| PRINTM(INFO, "poll mask=0x%x\n", mask); |
| LEAVE(); |
| return mask; |
| } |
| |
| /* File ops for the Char driver */ |
| const struct file_operations chardev_fops = { |
| .owner = THIS_MODULE, |
| .read = chardev_read, |
| .write = chardev_write, |
| #if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 36) |
| .ioctl = chardev_ioctl, |
| #else |
| .unlocked_ioctl = chardev_ioctl, |
| #endif |
| #ifdef CONFIG_COMPAT |
| .compat_ioctl = chardev_ioctl_compat, |
| #endif |
| .open = chardev_open, |
| .release = chardev_release, |
| .poll = chardev_poll, |
| }; |
| |
| /** |
| * @brief This function creates the char dev |
| * |
| * @param dev A pointer to structure char_dev |
| * @param char_class A pointer to class struct |
| * @param mod_name A pointer to char |
| * @param dev_name A pointer to char |
| * @return 0--success otherwise failure |
| */ |
| int |
| register_char_dev(struct char_dev *dev, struct class *char_class, |
| char *mod_name, char *dev_name) |
| { |
| int ret = 0, dev_num; |
| unsigned long flags; |
| ENTER(); |
| /* create the chrdev region */ |
| if (mbtchar_major) { |
| dev_num = MKDEV(mbtchar_major, dev->minor); |
| ret = register_chrdev_region(dev_num, 1, mod_name); |
| } else { |
| PRINTM(INFO, "chardev: no major # yet\n"); |
| ret = alloc_chrdev_region((dev_t *) & dev_num, dev->minor, 1, |
| mod_name); |
| } |
| |
| if (ret) { |
| PRINTM(ERROR, "chardev: create chrdev_region failed\n"); |
| LEAVE(); |
| return ret; |
| } |
| if (!mbtchar_major) { |
| /* Store the allocated dev major # */ |
| mbtchar_major = MAJOR(dev_num); |
| } |
| dev->cdev = cdev_alloc(); |
| dev->cdev->ops = &chardev_fops; |
| dev->cdev->owner = chardev_fops.owner; |
| dev_num = MKDEV(mbtchar_major, dev->minor); |
| |
| if (cdev_add(dev->cdev, dev_num, 1)) { |
| PRINTM(ERROR, "chardev: cdev_add failed\n"); |
| ret = -EFAULT; |
| goto free_cdev_region; |
| } |
| #if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 26) |
| if ((dev->dev_type == BT_TYPE) || (dev->dev_type == BT_AMP_TYPE)) { |
| device_create(char_class, NULL, |
| MKDEV(mbtchar_major, dev->minor), NULL, dev_name); |
| } |
| if (dev->dev_type == FM_TYPE) { |
| device_create(char_class, NULL, |
| MKDEV(mbtchar_major, dev->minor), NULL, dev_name); |
| } |
| if (dev->dev_type == NFC_TYPE) { |
| device_create(char_class, NULL, |
| MKDEV(mbtchar_major, dev->minor), NULL, dev_name); |
| } |
| #else |
| if ((dev->dev_type == BT_TYPE) || (dev->dev_type == BT_AMP_TYPE)) { |
| device_create(char_class, NULL, |
| MKDEV(mbtchar_major, dev->minor), dev_name); |
| } |
| if (dev->dev_type == FM_TYPE) { |
| device_create(char_class, NULL, |
| MKDEV(mbtchar_major, dev->minor), dev_name); |
| } |
| if (dev->dev_type == NFC_TYPE) { |
| device_create(char_class, NULL, |
| MKDEV(mbtchar_major, dev->minor), dev_name); |
| } |
| #endif |
| PRINTM(INFO, "register char dev=%s\n", dev_name); |
| |
| /** modify later */ |
| |
| spin_lock_irqsave(&char_dev_list_lock, flags); |
| list_add_tail(&dev->list, &char_dev_list); |
| spin_unlock_irqrestore(&char_dev_list_lock, flags); |
| |
| LEAVE(); |
| return ret; |
| free_cdev_region: |
| unregister_chrdev_region(MKDEV(mbtchar_major, dev->minor), 1); |
| LEAVE(); |
| return ret; |
| } |
| |
| /** |
| * @brief This function deletes the char dev |
| * |
| * @param dev A pointer to structure char_dev |
| * @param char_class A pointer to class struct |
| * @param dev_name A pointer to char |
| * @return 0--success otherwise failure |
| */ |
| int |
| unregister_char_dev(struct char_dev *dev, struct class *char_class, |
| char *dev_name) |
| { |
| ENTER(); |
| device_destroy(char_class, MKDEV(mbtchar_major, dev->minor)); |
| cdev_del(dev->cdev); |
| unregister_chrdev_region(MKDEV(mbtchar_major, dev->minor), 1); |
| PRINTM(INFO, "unregister char dev=%s\n", dev_name); |
| |
| LEAVE(); |
| return 0; |
| } |
| |
| /** |
| * @brief This function cleans module |
| * |
| * @param char_class A pointer to class struct |
| * @return N/A |
| */ |
| void |
| chardev_cleanup(struct class *char_class) |
| { |
| unsigned long flags; |
| struct list_head *p = NULL; |
| struct char_dev *dev = NULL; |
| ENTER(); |
| spin_lock_irqsave(&char_dev_list_lock, flags); |
| do { |
| dev = NULL; |
| list_for_each(p, &char_dev_list) { |
| dev = list_entry(p, struct char_dev, list); |
| list_del(p); |
| spin_unlock_irqrestore(&char_dev_list_lock, flags); |
| unregister_char_dev(dev, char_class, dev->m_dev->name); |
| kobject_put(&dev->kobj); |
| spin_lock_irqsave(&char_dev_list_lock, flags); |
| break; |
| } |
| } while (dev); |
| spin_unlock_irqrestore(&char_dev_list_lock, flags); |
| class_destroy(char_class); |
| LEAVE(); |
| } |
| |
| /** |
| * @brief This function cleans module |
| * |
| * @param m_dev A pointer to m_dev struct |
| * @param char_class A pointer to class struct |
| * @return N/A |
| */ |
| void |
| chardev_cleanup_one(struct m_dev *m_dev, struct class *char_class) |
| { |
| unsigned long flags; |
| struct list_head *p = NULL; |
| struct char_dev *dev = NULL; |
| ENTER(); |
| spin_lock_irqsave(&char_dev_list_lock, flags); |
| list_for_each(p, &char_dev_list) { |
| dev = list_entry(p, struct char_dev, list); |
| if (dev->minor == m_dev->index) { |
| list_del(p); |
| spin_unlock_irqrestore(&char_dev_list_lock, flags); |
| dev->m_dev = NULL; |
| unregister_char_dev(dev, char_class, m_dev->name); |
| kobject_put(&dev->kobj); |
| spin_lock_irqsave(&char_dev_list_lock, flags); |
| break; |
| } |
| } |
| spin_unlock_irqrestore(&char_dev_list_lock, flags); |
| LEAVE(); |
| } |