在内核模块中通过系统调用ioctl获取ATA/SCSI硬盘序列号

open, read, ioctl 这些系统调用本来是提供给用户空间的程序访问的,所以,对传递给它的参数,它默认会认为来自用户空间,在->write()函数中,为了保护内核空间,一般会用get_fs()得到的值来和USER_DS进行比较,从而防止用户空间程序“蓄意”破坏内核空间;

而现在要在内核空间使用系统调用,此时传递给->ioctl()的参数地址就是内核空间的地址了,在USER_DS之上(USER_DS ~ KERNEL_DS),如果不做任何其它处理,在ioctl()函数中,会认为该地址超过了USER_DS范围,所以会认为是用户空间的“蓄意破坏”,从而不允许进一步的执行; 为了解决这个问题; set_fs(KERNEL_DS);将其能访问的空间限制扩大到KERNEL_DS,这样就可以在内核顺利使用系统调用了!

以下是实现代码, dbg.h中主要是DBG_ERROR这些调试宏,所有不列出来了.

#ifdef MODVERSIONS #include <linux/modversions.h> #endif #include <linux/kernel.h> #include <linux/module.h> #include <linux/skbuff.h> #include <linux/netdevice.h> #include <linux/netfilter_ipv4/ip_tables.h> #include <linux/types.h> #include <linux/random.h> #include <linux/vmalloc.h> #include <linux/init.h> #include <net/pkt_sched.h> #include <net/ip.h> #include <net/tcp.h> #include <net/udp.h> #include <net/icmp.h> #include <linux/inet.h> #include <linux/list.h> #include <linux/mm.h> #include <linux/sysctl.h> #include <linux/fs.h> #include <linux/kernel_stat.h> #include <linux/if_vlan.h> #include <linux/netfilter_bridge.h> #include <linux/hdreg.h> #include <scsi/scsi.h> #include <scsi/sg.h> #include <asm/uaccess.h> /* for put_user */ #include <asm/atomic.h> /* for put_user */ #define _rdtsc(x) {asm volatile("rdtsc" : "=A" (x));} #include "dbg.h" #define SCSI_TIMEOUT (2*HZ) #define HDD_IDENTITY_LEN 20 static int verbose = LOG_VERBOSE_LEVEL_INFO; /*****************************************************************************/ static struct file* vfs_open( const char *name ); static int vfs_close( struct file *fp ); static long vfs_ioctl( struct file *filp, u32 cmd, unsigned long arg ); int scsi_get_inquiry( const char *name, char *buf ); int ide_get_identity( const char *name, char *buf ); int hdd_get_identity( char *identity, int len ); /*****************************************************************************/ static struct file* vfs_open( const char *name ) { struct file *fp = filp_open( name , O_RDONLY, 0444 ); if ( IS_ERR( fp ) ) { DBG_ERROR( verbose, "filp_open file %s failed.\n", name ); fp = NULL; } return fp; } static int vfs_close( struct file *fp ) { return filp_close( fp, NULL ); } static long vfs_ioctl( struct file *filp, u32 cmd, unsigned long arg ) { int error = -ENOTTY; if ( !filp->f_op ) goto out; if ( filp->f_op->unlocked_ioctl ) { error = filp->f_op->unlocked_ioctl( filp, cmd, arg ); if ( error == -ENOIOCTLCMD ) error = -EINVAL; goto out; } else if ( filp->f_op->ioctl ) { lock_kernel(); error = filp->f_op->ioctl( filp->f_path.dentry->d_inode, filp, cmd, arg ); unlock_kernel(); } out: return error; } /*****************************************************************************/ /* * 取得scsi设备序列号 * 通过SCSI INQUIRY命令取得Vital Product Data(VPD)页面信息 * Page Code 80h - Unit serial number */ int scsi_get_inquiry( const char *name, char *buf ) { struct file *fp = NULL; mm_segment_t old_fs; sg_io_hdr_t io_hdr; #define VPD_INQUIRY_SIZE 0x00ff u32 data_size = VPD_INQUIRY_SIZE; u8 data[VPD_INQUIRY_SIZE]; u8 cmd[] = { INQUIRY, 1, 0x80, VPD_INQUIRY_SIZE >> 8, VPD_INQUIRY_SIZE & 0xff, 0 }; u32 sense_len = 32; u8 sense_buffer[sense_len]; int len, i, rc = -1; int copy = 0; cmd[3] = ( data_size>>8 )&0xff; cmd[4] = data_size&0xff; fp = vfs_open( name ); if ( NULL == fp ) { return -1; } memset( &io_hdr, 0, sizeof( sg_io_hdr_t ) ); io_hdr.interface_id = 'S'; /* CDB */ io_hdr.cmdp = cmd; io_hdr.cmd_len = sizeof( cmd ); /* Where to store the sense_data, if there was an error */ io_hdr.sbp = sense_buffer; io_hdr.mx_sb_len = sense_len; sense_len = 0; /* Transfer direction, either in or out. Linux does not yet support bidirectional SCSI transfers ? */ io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; /* Where to store the DATA IN/OUT from the device and how big the buffer is */ io_hdr.dxferp = data; io_hdr.dxfer_len = data_size; /* SCSI timeout in ms */ io_hdr.timeout = SCSI_TIMEOUT; // old_fs = get_fs(); // 改变ioctl只能操作用户空间的限制 set_fs( KERNEL_DS ); if ( vfs_ioctl( fp, SG_IO, ( unsigned long )&io_hdr ) < 0 ) { set_fs( old_fs ); DBG_ERROR( verbose, "vfs_ioctl %s SG_IO failed.\n", name ); goto _out; } set_fs( old_fs ); // if ( ( io_hdr.info & SG_INFO_OK_MASK ) != SG_INFO_OK ) { if ( io_hdr.sb_len_wr > 0 ) { sense_len = io_hdr.sb_len_wr; rc = -1; printk ( "INQUIRY sense data: "); for (i = 0; i < io_hdr.sb_len_wr; ++i) { if ((i > 0) && (0 == (i % 10))) printk("\n "); printk("0x%02x ", sense_buffer[i] & 0xff); } printk("\n"); goto _next; } } if ( io_hdr.masked_status ) { DBG_ERROR( verbose, "status=0x%x masked_status=0x%x\n", io_hdr.status, io_hdr.masked_status ); rc = -2; goto _out; } if ( io_hdr.host_status ) { DBG_ERROR( verbose, "host_status=0x%x\n", io_hdr.host_status ); rc = -3; goto _out; } if ( io_hdr.driver_status ) { DBG_ERROR( verbose, "driver_status=0x%x\n", io_hdr.driver_status ); rc = -4; goto _out; } _next: if ( sense_len ) { goto _out; } /* Page Length */ len = data[3]; DBG_INFO( verbose, "%s Unit Serial Number Length: %d\n", name, len ); if ( buf ) { if ( len > HDD_IDENTITY_LEN ) { DBG_ERROR( verbose, "identity buffer length %d is greater than %d\n", len, HDD_IDENTITY_LEN ); copy = 0; } else { copy = 1; } memset( buf, 0x0, HDD_IDENTITY_LEN ); } #if 0 /* Unit Serial Number */ //printk( "%s Unit Serial Number:", name ); for ( i = 4; i < ( len+4 ); i ++ ) { //printk( "%c",data[i]&0xff ); if ( copy && buf ) { *buf = data[i]&0xff; buf ++; } } //printk( "\n" ); #else if ( copy && buf ) { memcpy( buf, data + 4, len ); } #endif rc = 0; _out: vfs_close( fp ); return rc; } /* * 取得ATA设备序列号 * 直接发送HDIO_GET_IDENTITY ioctl code获取信息 */ int ide_get_identity( const char *name, char *buf ) { struct file *fp = NULL; mm_segment_t old_fs; int rc = -1; struct hd_driveid driveid; fp = vfs_open( name ); if ( NULL == fp ) { return -1; } old_fs = get_fs(); // 改变ioctl只能操作用户空间的限制 set_fs( KERNEL_DS ); if ( vfs_ioctl( fp, HDIO_GET_IDENTITY, ( unsigned long )&driveid ) < 0 ) { set_fs( old_fs ); DBG_ERROR( verbose, "vfs_ioctl %s HDIO_GET_IDENTITY failed.\n", name ); goto _out; } set_fs( old_fs ); DBG_INFO( verbose, "%s Model=%.40s, FwRev=%.8s, SerialNo=%.20s\n", name, driveid.model, driveid.fw_rev, driveid.serial_no ); if ( buf ) { memset( buf, 0x0, HDD_IDENTITY_LEN ); memcpy( buf, driveid.serial_no, HDD_IDENTITY_LEN ); } rc = 0; _out: vfs_close( fp ); return rc; } /*****************************************************************************/ int hdd_get_identity( char *identity, int len ) { if ( identity != NULL && len < HDD_IDENTITY_LEN ){ DBG_ERROR( verbose, "identity buffer length is less than %d\n", HDD_IDENTITY_LEN ); } if ( !ide_get_identity( "/dev/hda", identity ) ) { return 0; } if ( !ide_get_identity( "/dev/hdb", identity ) ) { return 0; } if ( !scsi_get_inquiry( "/dev/sda", identity ) ) { return 0; } if ( !scsi_get_inquiry( "/dev/sdb", identity ) ) { return 0; } DBG_ERROR( verbose, "can't found a exist hard disk.\n" ); return -1; } char hdd_sn[HDD_IDENTITY_LEN]; static int __init mod_init( void ) { int err = 0; pr_info( "%s.\n", __func__ ); if ( !hdd_get_identity( hdd_sn, HDD_IDENTITY_LEN) ){ printk( "HDD SerialNo: %-20s\n", hdd_sn ); } return err; } static void __exit mod_fini( void ) { pr_info( "%s.\n", __func__ ); } module_init( mod_init ); module_exit( mod_fini ); MODULE_LICENSE( "GPL" );

Makefile:

ARCH := x86 KSP := /work/linux-2.6.29.6_x86 EXTRA_CFLAGS +=-DDEBUG obj-m += hdd.o hdd-objs = main.o all: clean make -C $(KSP) M=`pwd` modules clean: make -C $(KSP) M=`pwd` clean rm -f Module.symvers rm -f modules.order

参考:
SCSI Inquiry Command
http://en.wikipedia.org/wiki/SCSI_Inquiry_Command
SCSI INQUIRY 命令的详细解说;

The sdparm utility
http://sg.danny.cz/sg/sdparm.html
lsscsi http://sg.danny.cz/scsi/lsscsi.html
The Linux sg3_utils package http://sg.danny.cz/sg/sg3_utils.html
linux下访问SCSI设备参数的几个工具, 对VPD pages有较详细的解说;

The Linux SCSI Generic (sg) HOWTO
http://www.tldp.org/HOWTO/SCSI-Generic-HOWTO/index.html
The Linux 2.4 SCSI subsystem HOWTO http://tldp.org/HOWTO/SCSI-2.4-HOWTO/index.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值