字符设备 --- file_operations 、class、device

本文深入讲解字符设备驱动的实现过程,包括设备号的申请、cdev结构体的使用及初始化、设备类与设备实例的创建等关键技术点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

字符驱动
-------------------------------------------------------------
核心 file_operations 结构体
    
--------------------------------------------------------------
注册字符设备方法
步骤:1.申请设备号
            2.设置cdev 结构体
            3.初始化 cdev结构体
            4.将cdev 结构体添加到链表中
申请设备号:            
方法1: int register_chrdev_region(dev_t from, unsigned count, const char *name)
方法2. int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)



    方法1中,需要先设置设备号,在不能动态的申请设备号,可以申请用一个主设备下的count个从设备
    方法2中,动态的申请设备号,需要提供次设备的base ,可以连续申请count个从设备

申请 cdev 结构体
        cdev 结构体可以使用全局变量,可以使用使用指针
        使用指针就需要使用cdev_alloc 进行申请
设置cdedv 结构体
    void cdev_init(struct cdev *cdev, const struct file_operations *fops)


    cdev_init --->将file_opertions 结构体赋值给cdev 的ops
                                其实也可以进行手工的设置
cdev 添加到链表中
    int cdev_add(struct cdev *p, dev_t dev, unsigned count)



    
----------------------------------------------------------------------------------------
注销:
        从链表中删除cdev
    void cdev_del(struct cdev *p)
    
-------------------------------------------------------------------------------------
字符设备的注册还可以使用一个简化的版本 register_chrdev
 
   int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)


    如果major 为0 的话,则动态申请主设备号。
    
//撤销注册字符设备
int unregister_chrdev(unsigned int major, const char *name)    



------------------------------------------------------------------
字符设备申请的主设备号,会在 proc/devices  显示已经申请的主设备号。
次设备号,是如何体现出来的呢?次设备号,是通过/dev/xxx 即dev下的设备体现出来的。
----------------------------------------------------------------------------------------------------
在字符设备驱动中,以上的操作仅仅完成了设备号的申请,并没有涉及到生成设备,
如果不自动生成设备,就需要手动的使用mknod命令来创建这个设置。
自动创建设备:
    自动创建设备的前提是创建class类。只有在创建了class类后。自动创建设备
    
    类的创建:struct class *class_create(struct module *owner, const char *name)
                 功能:create a struct class structure
                 参数:owner : THIS_MODULE
                              name: pointer to a string for the name of this class.
                 返回值:
                         返回值需要使用 IS_ERR判断
              
          if(IS_ERR(cls))
                         {
                             PTR_ERR(cls);
                         }

                         这样的处理方式
    类的注销:        void class_destroy(struct class *cls)
                    
            以上的操作也仅仅是创建了类。并没有进行设备的创建
                
    设备的创建:
  
     struct device *device_create(struct class *class, struct device *parent, dev_t devt, const char *fmt, ...)


        功能:creates a device and registers it with sysfs
                参数:class :创建的类
                          parent: pointer to the parent struct device of this new device, if any
                          devt:设备号。devt=MKDEV(major,min)
                          fmt: 这里就可以创建多个次设备 ( eg:"dev%d", i)
                返回值:返回值的判断也需要和class_create一样进行判断
                          
    设备的注销:
void device_destroy(struct class *class, dev_t devt)    


                //如果有多个次设备的注销,需要使用MKDEV(major,min)生成devt。并循环调用 device_destroy    
    ----------------------------------------------------------------------
  
  #include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>

#include <linux/cdev.h>
#include <linux/fs.h>

#include <linux/uaccess.h>
#include <linux/device.h>

#include <asm/current.h>
#include <linux/sched.h>

static struct cdev * pdev = NULL;

#define DEVNAME "demo"

static int major = 0;
static int minor = 0;
static int count = 3;

#define KMAX 1024
static char kbuf[KMAX];
static int counter = 0;

static struct class *cls = NULL;

//打开设备
//int open(const char *pathname, int flags);
static int demo_open(struct inode *inode, struct file *filp)
{
    printk("%s:%d, pid = %d\n", __func__, __LINE__, current->pid);
    printk("%s:%d, %d\n", __func__, MAJOR(inode->i_rdev), MINOR(inode->i_rdev));
    return 0;
}

//关闭设备
//int close(int fd)
static int demo_release(struct inode *inode, struct file *filp)
{
    printk("%s:%d, pid = %d\n", __func__, __LINE__, current->pid);
    printk("%s:%d, %d\n", __func__, MAJOR(inode->i_rdev), MINOR(inode->i_rdev));
    return 0;
}

//读设备
//ssize_t read(int fd, void *buf, size_t count);
static ssize_t demo_read(struct file *filp, char __user *buffer, size_t size, loff_t *offset)
{
    printk("%s:%d, pid = %d\n", __func__, __LINE__, current->pid);
    printk("%s:%p, %d\n", __func__, buffer, size);

    if(0 == counter){
        return -EAGAIN;
    }

    if(counter < size){
        size = counter;
    }

    if(size == copy_to_user(buffer, kbuf, size)){
        return  -EAGAIN;
    }

    counter = 0;

    return size;
}

//写设备
static ssize_t demo_write(struct file *filp, const char __user *buffer, size_t size, loff_t *offset)
{
    printk("%s:%d, pid = %d\n", __func__, __LINE__, current->pid);
    printk("%s:%p, %d\n", __func__, buffer, size);

    if(size > KMAX){
        return -ENOMEM;
    }

    if(size == copy_from_user(kbuf, buffer, size)){
        return -EAGAIN;
    }

    counter = size;

    return size;
}

static struct file_operations misc = {
    .owner    = THIS_MODULE,
    .open    = demo_open,
    .release= demo_release,
    .read    = demo_read,
    .write    = demo_write,
};

static int main_open(struct inode *inode, struct file *filp)
{
    printk("%s:%d, pid = %d\n", __func__, __LINE__, current->pid);
    printk("%s:%d, %d\n", __func__, MAJOR(inode->i_rdev), MINOR(inode->i_rdev));

    filp->f_op = &misc;
    return filp->f_op->open(inode, filp);
}

static struct file_operations fops = {
    .owner    = THIS_MODULE,
    .open    = main_open,
};

static int __init demo_init(void)
{
    dev_t dev;
    int ret, i;
    struct device *device = NULL;

    printk("%s:%d\n", __func__, __LINE__);

    //1.alloc obj
    pdev = cdev_alloc();
    if(NULL == pdev){
        printk("cdev_alloc fail.\n");
        return -ENOMEM;        
    }

    //2.init obj
    cdev_init(pdev, &fops);

    ret = alloc_chrdev_region(&dev, minor, count, DEVNAME);
    if(ret){
        printk("alloc_chrdev_region fail.\n");
        goto ERR_STEP;        
    }
    major = MAJOR(dev);

    //3. register obj
    ret = cdev_add(pdev, dev, count);
    if(ret){
        printk("cdev_add fail.\n");
        goto ERR_STEP1;        
    }

    cls = class_create(THIS_MODULE, DEVNAME);
    if (IS_ERR(cls)) {
        ret = PTR_ERR(cls);
        goto ERR_STEP1;
    }

    for(i = minor; i < count+minor; i++){
        device = device_create(cls, NULL, MKDEV(major, i), NULL, "%s%d", DEVNAME, i);
        if (IS_ERR(device)) {
            ret = PTR_ERR(device);
            goto ERR_STEP2;
        }        
    }    

    return 0; //0-成功;失败:负数,绝对值是错误码,(被保存到应用进程的errno)

ERR_STEP2:
    for(i--; i >= minor; i--){
        device_destroy(cls, MKDEV(major, i));
    }

    class_destroy(cls);

ERR_STEP1:
    unregister_chrdev_region(dev, count);

ERR_STEP:
    cdev_del(pdev);
    return ret;
}

static void  __exit demo_exit(void)
{
    int i;

    printk("%s:%d\n", __func__, __LINE__);

    //1. free resorce
    unregister_chrdev_region(MKDEV(major, minor), count);

    for(i = minor; i < count+minor; i++){
        device_destroy(cls, MKDEV(major, i));
    }
    class_destroy(cls);

    //2.unregister
    cdev_del(pdev);
}

module_init(demo_init);
module_exit(demo_exit);

MODULE_LICENSE("GPL");



                
                
            
                         

/* * This file is part of the Xilinx DMA IP Core driver for Linux * * Copyright (c) 2016-present, Xilinx, Inc. * All rights reserved. * * This source code is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * 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. * * The full GNU General Public License is included in this distribution in * the file called "COPYING". */ #define pr_fmt(fmt) KBUILD_MODNAME ":%s: " fmt, __func__ #include "xdma_cdev.h" static struct class *g_xdma_class; struct kmem_cache *cdev_cache; enum cdev_type { CHAR_USER, CHAR_CTRL, CHAR_XVC, CHAR_EVENTS, CHAR_XDMA_H2C, CHAR_XDMA_C2H, CHAR_BYPASS_H2C, CHAR_BYPASS_C2H, CHAR_BYPASS, }; static const char * const devnode_names[] = { XDMA_NODE_NAME "%d_user", XDMA_NODE_NAME "%d_control", XDMA_NODE_NAME "%d_xvc", XDMA_NODE_NAME "%d_events_%d", XDMA_NODE_NAME "%d_h2c_%d", XDMA_NODE_NAME "%d_c2h_%d", XDMA_NODE_NAME "%d_bypass_h2c_%d", XDMA_NODE_NAME "%d_bypass_c2h_%d", XDMA_NODE_NAME "%d_bypass", }; enum xpdev_flags_bits { XDF_CDEV_USER, XDF_CDEV_CTRL, XDF_CDEV_XVC, XDF_CDEV_EVENT, XDF_CDEV_SG, XDF_CDEV_BYPASS, }; static inline void xpdev_flag_set(struct xdma_pci_dev *xpdev, enum xpdev_flags_bits fbit) { xpdev->flags |= 1 << fbit; } static inline void xcdev_flag_clear(struct xdma_pci_dev *xpdev, enum xpdev_flags_bits fbit) { xpdev->flags &= ~(1 << fbit); } static inline int xpdev_flag_test(struct xdma_pci_dev *xpdev, enum xpdev_flags_bits fbit) { return xpdev->flags & (1 << fbit); } #ifdef __XDMA_SYSFS__ ssize_t xdma_dev_instance_show(struct device *dev, struct device_attribute *attr, char *buf) { struct xdma_pci_dev *xpdev = (struct xdma_pci_dev *)dev_get_drvdata(dev); return snprintf(buf, PAGE_SIZE, "%d\t%d\n", xpdev->major, xpdev->xdev->idx); } static DEVICE_ATTR_RO(xdma_dev_instance); #endif static int config_kobject(struct xdma_cdev *xcdev, enum cdev_type type) { int rv = -EINVAL; struct xdma_dev *xdev = xcdev->xdev; struct xdma_engine *engine = xcdev->engine; switch (type) { case CHAR_XDMA_H2C: case CHAR_XDMA_C2H: case CHAR_BYPASS_H2C: case CHAR_BYPASS_C2H: if (!engine) { pr_err("Invalid DMA engine\n"); return rv; } rv = kobject_set_name(&xcdev->cdev.kobj, devnode_names[type], xdev->idx, engine->channel); break; case CHAR_BYPASS: case CHAR_USER: case CHAR_CTRL: case CHAR_XVC: rv = kobject_set_name(&xcdev->cdev.kobj, devnode_names[type], xdev->idx); break; case CHAR_EVENTS: rv = kobject_set_name(&xcdev->cdev.kobj, devnode_names[type], xdev->idx, xcdev->bar); break; default: pr_warn("%s: UNKNOWN type 0x%x.\n", __func__, type); break; } if (rv) pr_err("%s: type 0x%x, failed %d.\n", __func__, type, rv); return rv; } int xcdev_check(const char *fname, struct xdma_cdev *xcdev, bool check_engine) { struct xdma_dev *xdev; if (!xcdev || xcdev->magic != MAGIC_CHAR) { pr_info("%s, xcdev 0x%p, magic 0x%lx.\n", fname, xcdev, xcdev ? xcdev->magic : 0xFFFFFFFF); return -EINVAL; } xdev = xcdev->xdev; if (!xdev || xdev->magic != MAGIC_DEVICE) { pr_info("%s, xdev 0x%p, magic 0x%lx.\n", fname, xdev, xdev ? xdev->magic : 0xFFFFFFFF); return -EINVAL; } if (check_engine) { struct xdma_engine *engine = xcdev->engine; if (!engine || engine->magic != MAGIC_ENGINE) { pr_info("%s, engine 0x%p, magic 0x%lx.\n", fname, engine, engine ? engine->magic : 0xFFFFFFFF); return -EINVAL; } } return 0; } int char_open(struct inode *inode, struct file *file) { struct xdma_cdev *xcdev; /* pointer to containing structure of the character device inode */ xcdev = container_of(inode->i_cdev, struct xdma_cdev, cdev); if (xcdev->magic != MAGIC_CHAR) { pr_err("xcdev 0x%p inode 0x%lx magic mismatch 0x%lx\n", xcdev, inode->i_ino, xcdev->magic); return -EINVAL; } /* create a reference to our char device in the opened file */ file->private_data = xcdev; return 0; } /* * Called when the device goes from used to unused. */ int char_close(struct inode *inode, struct file *file) { struct xdma_dev *xdev; struct xdma_cdev *xcdev = (struct xdma_cdev *)file->private_data; if (!xcdev) { pr_err("char device with inode 0x%lx xcdev NULL\n", inode->i_ino); return -EINVAL; } if (xcdev->magic != MAGIC_CHAR) { pr_err("xcdev 0x%p magic mismatch 0x%lx\n", xcdev, xcdev->magic); return -EINVAL; } /* fetch device specific data stored earlier during open */ xdev = xcdev->xdev; if (!xdev) { pr_err("char device with inode 0x%lx xdev NULL\n", inode->i_ino); return -EINVAL; } if (xdev->magic != MAGIC_DEVICE) { pr_err("xdev 0x%p magic mismatch 0x%lx\n", xdev, xdev->magic); return -EINVAL; } return 0; } /* create_xcdev() -- create a character device interface to data or control bus * * If at least one SG DMA engine is specified, the character device interface * is coupled to the SG DMA file operations which operate on the data bus. If * no engines are specified, the interface is coupled with the control bus. */ static int create_sys_device(struct xdma_cdev *xcdev, enum cdev_type type) { struct xdma_dev *xdev = xcdev->xdev; struct xdma_engine *engine = xcdev->engine; int last_param; if (type == CHAR_EVENTS) last_param = xcdev->bar; else last_param = engine ? engine->channel : 0; xcdev->sys_device = device_create(g_xdma_class, &xdev->pdev->dev, xcdev->cdevno, NULL, devnode_names[type], xdev->idx, last_param); if (!xcdev->sys_device) { pr_err("device_create(%s) failed\n", devnode_names[type]); return -1; } return 0; } static int destroy_xcdev(struct xdma_cdev *cdev) { if (!cdev) { pr_warn("cdev NULL.\n"); return -EINVAL; } if (cdev->magic != MAGIC_CHAR) { pr_warn("cdev 0x%p magic mismatch 0x%lx\n", cdev, cdev->magic); return -EINVAL; } if (!cdev->xdev) { pr_err("xdev NULL\n"); return -EINVAL; } if (!g_xdma_class) { pr_err("g_xdma_class NULL\n"); return -EINVAL; } if (!cdev->sys_device) { pr_err("cdev sys_device NULL\n"); return -EINVAL; } if (cdev->sys_device) device_destroy(g_xdma_class, cdev->cdevno); cdev_del(&cdev->cdev); return 0; } static int create_xcdev(struct xdma_pci_dev *xpdev, struct xdma_cdev *xcdev, int bar, struct xdma_engine *engine, enum cdev_type type) { int rv; int minor; struct xdma_dev *xdev = xpdev->xdev; dev_t dev; spin_lock_init(&xcdev->lock); /* new instance? */ if (!xpdev->major) { /* allocate a dynamically allocated char device node */ int rv = alloc_chrdev_region(&dev, XDMA_MINOR_BASE, XDMA_MINOR_COUNT, XDMA_NODE_NAME); if (rv) { pr_err("unable to allocate cdev region %d.\n", rv); return rv; } xpdev->major = MAJOR(dev); } /* * do not register yet, create kobjects and name them, */ xcdev->magic = MAGIC_CHAR; xcdev->cdev.owner = THIS_MODULE; xcdev->xpdev = xpdev; xcdev->xdev = xdev; xcdev->engine = engine; xcdev->bar = bar; rv = config_kobject(xcdev, type); if (rv < 0) return rv; switch (type) { case CHAR_USER: case CHAR_CTRL: /* minor number is type index for non-SGDMA interfaces */ minor = type; cdev_ctrl_init(xcdev); break; case CHAR_XVC: /* minor number is type index for non-SGDMA interfaces */ minor = type; cdev_xvc_init(xcdev); break; case CHAR_XDMA_H2C: minor = 32 + engine->channel; cdev_sgdma_init(xcdev); break; case CHAR_XDMA_C2H: minor = 36 + engine->channel; cdev_sgdma_init(xcdev); break; case CHAR_EVENTS: minor = 10 + bar; cdev_event_init(xcdev); break; case CHAR_BYPASS_H2C: minor = 64 + engine->channel; cdev_bypass_init(xcdev); break; case CHAR_BYPASS_C2H: minor = 68 + engine->channel; cdev_bypass_init(xcdev); break; case CHAR_BYPASS: minor = 100; cdev_bypass_init(xcdev); break; default: pr_info("type 0x%x NOT supported.\n", type); return -EINVAL; } xcdev->cdevno = MKDEV(xpdev->major, minor); /* bring character device live */ rv = cdev_add(&xcdev->cdev, xcdev->cdevno, 1); if (rv < 0) { pr_err("cdev_add failed %d, type 0x%x.\n", rv, type); goto unregister_region; } dbg_init("xcdev 0x%p, %u:%u, %s, type 0x%x.\n", xcdev, xpdev->major, minor, xcdev->cdev.kobj.name, type); /* create device on our class */ if (g_xdma_class) { rv = create_sys_device(xcdev, type); if (rv < 0) goto del_cdev; } return 0; del_cdev: cdev_del(&xcdev->cdev); unregister_region: unregister_chrdev_region(xcdev->cdevno, XDMA_MINOR_COUNT); return rv; } void xpdev_destroy_interfaces(struct xdma_pci_dev *xpdev) { int i = 0; int rv; #ifdef __XDMA_SYSFS__ device_remove_file(&xpdev->pdev->dev, &dev_attr_xdma_dev_instance); #endif if (xpdev_flag_test(xpdev, XDF_CDEV_SG)) { /* iterate over channels */ for (i = 0; i < xpdev->h2c_channel_max; i++) { /* remove SG DMA character device */ rv = destroy_xcdev(&xpdev->sgdma_h2c_cdev[i]); if (rv < 0) pr_err("Failed to destroy h2c xcdev %d error :0x%x\n", i, rv); } for (i = 0; i < xpdev->c2h_channel_max; i++) { rv = destroy_xcdev(&xpdev->sgdma_c2h_cdev[i]); if (rv < 0) pr_err("Failed to destroy c2h xcdev %d error 0x%x\n", i, rv); } } if (xpdev_flag_test(xpdev, XDF_CDEV_EVENT)) { for (i = 0; i < xpdev->user_max; i++) { rv = destroy_xcdev(&xpdev->events_cdev[i]); if (rv < 0) pr_err("Failed to destroy cdev event %d error 0x%x\n", i, rv); } } /* remove control character device */ if (xpdev_flag_test(xpdev, XDF_CDEV_CTRL)) { rv = destroy_xcdev(&xpdev->ctrl_cdev); if (rv < 0) pr_err("Failed to destroy cdev ctrl event %d error 0x%x\n", i, rv); } /* remove user character device */ if (xpdev_flag_test(xpdev, XDF_CDEV_USER)) { rv = destroy_xcdev(&xpdev->user_cdev); if (rv < 0) pr_err("Failed to destroy user cdev %d error 0x%x\n", i, rv); } if (xpdev_flag_test(xpdev, XDF_CDEV_XVC)) { rv = destroy_xcdev(&xpdev->xvc_cdev); if (rv < 0) pr_err("Failed to destroy xvc cdev %d error 0x%x\n", i, rv); } if (xpdev_flag_test(xpdev, XDF_CDEV_BYPASS)) { /* iterate over channels */ for (i = 0; i < xpdev->h2c_channel_max; i++) { /* remove DMA Bypass character device */ rv = destroy_xcdev(&xpdev->bypass_h2c_cdev[i]); if (rv < 0) pr_err("Failed to destroy bypass h2c cdev %d error 0x%x\n", i, rv); } for (i = 0; i < xpdev->c2h_channel_max; i++) { rv = destroy_xcdev(&xpdev->bypass_c2h_cdev[i]); if (rv < 0) pr_err("Failed to destroy bypass c2h %d error 0x%x\n", i, rv); } rv = destroy_xcdev(&xpdev->bypass_cdev_base); if (rv < 0) pr_err("Failed to destroy base cdev\n"); } if (xpdev->major) unregister_chrdev_region( MKDEV(xpdev->major, XDMA_MINOR_BASE), XDMA_MINOR_COUNT); } int xpdev_create_interfaces(struct xdma_pci_dev *xpdev) { struct xdma_dev *xdev = xpdev->xdev; struct xdma_engine *engine; int i; int rv = 0; /* initialize control character device */ rv = create_xcdev(xpdev, &xpdev->ctrl_cdev, xdev->config_bar_idx, NULL, CHAR_CTRL); if (rv < 0) { pr_err("create_char(ctrl_cdev) failed\n"); goto fail; } xpdev_flag_set(xpdev, XDF_CDEV_CTRL); /* initialize events character device */ for (i = 0; i < xpdev->user_max; i++) { rv = create_xcdev(xpdev, &xpdev->events_cdev[i], i, NULL, CHAR_EVENTS); if (rv < 0) { pr_err("create char event %d failed, %d.\n", i, rv); goto fail; } } xpdev_flag_set(xpdev, XDF_CDEV_EVENT); /* iterate over channels */ for (i = 0; i < xpdev->h2c_channel_max; i++) { engine = &xdev->engine_h2c[i]; if (engine->magic != MAGIC_ENGINE) continue; rv = create_xcdev(xpdev, &xpdev->sgdma_h2c_cdev[i], i, engine, CHAR_XDMA_H2C); if (rv < 0) { pr_err("create char h2c %d failed, %d.\n", i, rv); goto fail; } } for (i = 0; i < xpdev->c2h_channel_max; i++) { engine = &xdev->engine_c2h[i]; if (engine->magic != MAGIC_ENGINE) continue; rv = create_xcdev(xpdev, &xpdev->sgdma_c2h_cdev[i], i, engine, CHAR_XDMA_C2H); if (rv < 0) { pr_err("create char c2h %d failed, %d.\n", i, rv); goto fail; } } xpdev_flag_set(xpdev, XDF_CDEV_SG); /* Initialize Bypass Character Device */ if (xdev->bypass_bar_idx > 0) { for (i = 0; i < xpdev->h2c_channel_max; i++) { engine = &xdev->engine_h2c[i]; if (engine->magic != MAGIC_ENGINE) continue; rv = create_xcdev(xpdev, &xpdev->bypass_h2c_cdev[i], i, engine, CHAR_BYPASS_H2C); if (rv < 0) { pr_err("create h2c %d bypass I/F failed, %d.\n", i, rv); goto fail; } } for (i = 0; i < xpdev->c2h_channel_max; i++) { engine = &xdev->engine_c2h[i]; if (engine->magic != MAGIC_ENGINE) continue; rv = create_xcdev(xpdev, &xpdev->bypass_c2h_cdev[i], i, engine, CHAR_BYPASS_C2H); if (rv < 0) { pr_err("create c2h %d bypass I/F failed, %d.\n", i, rv); goto fail; } } rv = create_xcdev(xpdev, &xpdev->bypass_cdev_base, xdev->bypass_bar_idx, NULL, CHAR_BYPASS); if (rv < 0) { pr_err("create bypass failed %d.\n", rv); goto fail; } xpdev_flag_set(xpdev, XDF_CDEV_BYPASS); } /* initialize user character device */ if (xdev->user_bar_idx >= 0) { rv = create_xcdev(xpdev, &xpdev->user_cdev, xdev->user_bar_idx, NULL, CHAR_USER); if (rv < 0) { pr_err("create_char(user_cdev) failed\n"); goto fail; } xpdev_flag_set(xpdev, XDF_CDEV_USER); /* xvc */ rv = create_xcdev(xpdev, &xpdev->xvc_cdev, xdev->user_bar_idx, NULL, CHAR_XVC); if (rv < 0) { pr_err("create xvc failed, %d.\n", rv); goto fail; } xpdev_flag_set(xpdev, XDF_CDEV_XVC); } #ifdef __XDMA_SYSFS__ /* sys file */ rv = device_create_file(&xpdev->pdev->dev, &dev_attr_xdma_dev_instance); if (rv) { pr_err("Failed to create device file\n"); goto fail; } #endif return 0; fail: rv = -1; xpdev_destroy_interfaces(xpdev); return rv; } int xdma_cdev_init(void) { g_xdma_class = class_create(THIS_MODULE, XDMA_NODE_NAME); if (IS_ERR(g_xdma_class)) { dbg_init(XDMA_NODE_NAME ": failed to create class"); return -EINVAL; } /* using kmem_cache_create to enable sequential cleanup */ cdev_cache = kmem_cache_create("cdev_cache", sizeof(struct cdev_async_io), 0, SLAB_HWCACHE_ALIGN, NULL); if (!cdev_cache) { pr_info("memory allocation for cdev_cache failed. OOM\n"); return -ENOMEM; } return 0; } void xdma_cdev_cleanup(void) { if (cdev_cache) kmem_cache_destroy(cdev_cache); if (g_xdma_class) class_destroy(g_xdma_class); } 分许上述代码
最新发布
05-15
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值