/* This function will attempt to post a command packet to the board */ staticint twl_post_command_packet(TW_Device_Extension *tw_dev, int request_id)
{
dma_addr_t command_que_value;
/* This function hands scsi cdb's to the firmware */ staticint twl_scsiop_execute_scsi(TW_Device_Extension *tw_dev, int request_id, unsignedchar *cdb, int use_sg,
TW_SG_Entry_ISO *sglistarg)
{
TW_Command_Full *full_command_packet;
TW_Command_Apache *command_packet; int i, sg_count; struct scsi_cmnd *srb = NULL; struct scatterlist *sg; int retval = 1;
if (tw_dev->srb[request_id])
srb = tw_dev->srb[request_id];
/* Update some stats */ if (srb) {
tw_dev->sector_count = scsi_bufflen(srb) / 512; if (tw_dev->sector_count > tw_dev->max_sector_count)
tw_dev->max_sector_count = tw_dev->sector_count;
tw_dev->sgl_entries = scsi_sg_count(srb); if (tw_dev->sgl_entries > tw_dev->max_sgl_entries)
tw_dev->max_sgl_entries = tw_dev->sgl_entries;
}
/* Now post the command to the board */
retval = twl_post_command_packet(tw_dev, request_id);
out: return retval;
} /* End twl_scsiop_execute_scsi() */
/* This function will read the aen queue from the isr */ staticint twl_aen_read_queue(TW_Device_Extension *tw_dev, int request_id)
{ unsignedchar cdb[TW_MAX_CDB_LEN];
TW_SG_Entry_ISO sglist[1];
TW_Command_Full *full_command_packet; int retval = 1;
/* Mark internal command */
tw_dev->srb[request_id] = NULL;
/* Now post the command packet */ if (twl_scsiop_execute_scsi(tw_dev, request_id, cdb, 1, sglist)) {
TW_PRINTK(tw_dev->host, TW_DRIVER, 0x2, "Post failed while reading AEN queue"); goto out;
}
retval = 0;
out: return retval;
} /* End twl_aen_read_queue() */
/* This function will sync firmware time with the host time */ staticvoid twl_aen_sync_time(TW_Device_Extension *tw_dev, int request_id)
{
u32 schedulertime;
TW_Command_Full *full_command_packet;
TW_Command *command_packet;
TW_Param_Apache *param;
time64_t local_time;
/* Convert system time in UTC to local time seconds since last
Sunday 12:00AM */
local_time = (ktime_get_real_seconds() - (sys_tz.tz_minuteswest * 60));
div_u64_rem(local_time - (3 * 86400), 604800, &schedulertime);
schedulertime = cpu_to_le32(schedulertime);
memcpy(param->data, &schedulertime, sizeof(u32));
/* Mark internal command */
tw_dev->srb[request_id] = NULL;
/* Now post the command */
twl_post_command_packet(tw_dev, request_id);
} /* End twl_aen_sync_time() */
/* This function will assign an available request id */ staticvoid twl_get_request_id(TW_Device_Extension *tw_dev, int *request_id)
{
*request_id = tw_dev->free_queue[tw_dev->free_head];
tw_dev->free_head = (tw_dev->free_head + 1) % TW_Q_LENGTH;
tw_dev->state[*request_id] = TW_S_STARTED;
} /* End twl_get_request_id() */
/* This function will free a request id */ staticvoid twl_free_request_id(TW_Device_Extension *tw_dev, int request_id)
{
tw_dev->free_queue[tw_dev->free_tail] = request_id;
tw_dev->state[request_id] = TW_S_FINISHED;
tw_dev->free_tail = (tw_dev->free_tail + 1) % TW_Q_LENGTH;
} /* End twl_free_request_id() */
/* This function will complete an aen request from the isr */ staticint twl_aen_complete(TW_Device_Extension *tw_dev, int request_id)
{
TW_Command_Full *full_command_packet;
TW_Command *command_packet;
TW_Command_Apache_Header *header; unsignedshort aen; int retval = 1;
/* First check for internal completion of set param for time sync */ if (TW_OP_OUT(command_packet->opcode__sgloffset) == TW_OP_SET_PARAM) { /* Keep reading the queue in case there are more aen's */ if (twl_aen_read_queue(tw_dev, request_id)) goto out2; else {
retval = 0; goto out;
}
}
switch (aen) { case TW_AEN_QUEUE_EMPTY: /* Quit reading the queue if this is the last one */ break; case TW_AEN_SYNC_TIME_WITH_HOST:
twl_aen_sync_time(tw_dev, request_id);
retval = 0; goto out; default:
twl_aen_queue_event(tw_dev, header);
/* If there are more aen's, keep reading the queue */ if (twl_aen_read_queue(tw_dev, request_id)) goto out2; else {
retval = 0; goto out;
}
}
retval = 0;
out2:
tw_dev->state[request_id] = TW_S_COMPLETED;
twl_free_request_id(tw_dev, request_id);
clear_bit(TW_IN_ATTENTION_LOOP, &tw_dev->flags);
out: return retval;
} /* End twl_aen_complete() */
/* This function will poll for a response */ staticint twl_poll_response(TW_Device_Extension *tw_dev, int request_id, int seconds)
{ unsignedlong before;
dma_addr_t mfa;
u32 regh, regl;
u32 response; int retval = 1; int found = 0;
/* Mark internal command */
tw_dev->srb[request_id] = NULL;
do { /* Send command to the board */ if (twl_scsiop_execute_scsi(tw_dev, request_id, cdb, 1, sglist)) {
TW_PRINTK(tw_dev->host, TW_DRIVER, 0x3, "Error posting request sense"); goto out;
}
/* Now poll for completion */ if (twl_poll_response(tw_dev, request_id, 30)) {
TW_PRINTK(tw_dev->host, TW_DRIVER, 0x4, "No valid response while draining AEN queue");
tw_dev->posted_request_count--; goto out;
}
/* This function will allocate memory and check if it is correctly aligned */ staticint twl_allocate_memory(TW_Device_Extension *tw_dev, int size, int which)
{ int i;
dma_addr_t dma_handle; unsignedlong *cpu_addr; int retval = 1;
/* This function will load the request id and various sgls for ioctls */ staticvoid twl_load_sgl(TW_Device_Extension *tw_dev, TW_Command_Full *full_command_packet, int request_id, dma_addr_t dma_handle, int length)
{
TW_Command *oldcommand;
TW_Command_Apache *newcommand;
TW_SG_Entry_ISO *sgl; unsignedint pae = 0;
/* Now copy down the entire ioctl */ if (copy_from_user(tw_ioctl, argp, driver_command.buffer_length + sizeof(TW_Ioctl_Buf_Apache))) goto out3;
/* See which ioctl we are doing */ switch (cmd) { case TW_IOCTL_FIRMWARE_PASS_THROUGH:
spin_lock_irqsave(tw_dev->host->host_lock, flags);
twl_get_request_id(tw_dev, &request_id);
/* Flag internal command */
tw_dev->srb[request_id] = NULL;
/* Flag chrdev ioctl */
tw_dev->chrdev_request_id = request_id;
/* Load request id and sglist for both command types */
twl_load_sgl(tw_dev, full_command_packet, request_id, dma_handle, data_buffer_length_adjusted);
/* Now post the command packet to the controller */
twl_post_command_packet(tw_dev, request_id);
spin_unlock_irqrestore(tw_dev->host->host_lock, flags);
timeout = TW_IOCTL_CHRDEV_TIMEOUT*HZ;
/* Now wait for command to complete */
timeout = wait_event_timeout(tw_dev->ioctl_wqueue, tw_dev->chrdev_request_id == TW_IOCTL_CHRDEV_FREE, timeout);
/* We timed out, and didn't get an interrupt */ if (tw_dev->chrdev_request_id != TW_IOCTL_CHRDEV_FREE) { /* Now we need to reset the board */
printk(KERN_WARNING "3w-sas: scsi%d: WARNING: (0x%02X:0x%04X): Character ioctl (0x%x) timed out, resetting card.\n",
tw_dev->host->host_no, TW_DRIVER, 0x6,
cmd);
retval = -EIO;
twl_reset_device_extension(tw_dev, 1); goto out3;
}
/* Now copy in the command packet response */
memcpy(&(tw_ioctl->firmware_command), tw_dev->command_packet_virt[request_id], sizeof(TW_Command_Full));
/* Now copy the entire response to userspace */ if (copy_to_user(argp, tw_ioctl, sizeof(TW_Ioctl_Buf_Apache) + driver_command.buffer_length) == 0)
retval = 0;
out3: /* Now free ioctl buf memory */
dma_free_coherent(&tw_dev->tw_pci_dev->dev, data_buffer_length_adjusted + sizeof(TW_Ioctl_Buf_Apache), cpu_addr, dma_handle);
out2:
mutex_unlock(&tw_dev->ioctl_lock);
out:
mutex_unlock(&twl_chrdev_mutex); return retval;
} /* End twl_chrdev_ioctl() */
/* This function handles open for the character device */ staticint twl_chrdev_open(struct inode *inode, struct file *file)
{ unsignedint minor_number; int retval = -ENODEV;
if (!capable(CAP_SYS_ADMIN)) {
retval = -EACCES; goto out;
}
minor_number = iminor(inode); if (minor_number >= twl_device_extension_count) goto out;
retval = 0;
out: return retval;
} /* End twl_chrdev_open() */
/* This function passes sense data from firmware to scsi layer */ staticint twl_fill_sense(TW_Device_Extension *tw_dev, int i, int request_id, int copy_sense, int print_host)
{
TW_Command_Apache_Header *header;
TW_Command_Full *full_command_packet; unsignedshort error; char *error_str;
/* This function will free up device extension resources */ staticvoid twl_free_device_extension(TW_Device_Extension *tw_dev)
{ if (tw_dev->command_packet_virt[0])
dma_free_coherent(&tw_dev->tw_pci_dev->dev, sizeof(TW_Command_Full)*TW_Q_LENGTH,
tw_dev->command_packet_virt[0],
tw_dev->command_packet_phys[0]);
if (tw_dev->generic_buffer_virt[0])
dma_free_coherent(&tw_dev->tw_pci_dev->dev,
TW_SECTOR_SIZE*TW_Q_LENGTH,
tw_dev->generic_buffer_virt[0],
tw_dev->generic_buffer_phys[0]);
if (tw_dev->sense_buffer_virt[0])
dma_free_coherent(&tw_dev->tw_pci_dev->dev, sizeof(TW_Command_Apache_Header)*
TW_Q_LENGTH,
tw_dev->sense_buffer_virt[0],
tw_dev->sense_buffer_phys[0]);
kfree(tw_dev->event_queue[0]);
} /* End twl_free_device_extension() */
/* This function will get parameter table entries from the firmware */ staticvoid *twl_get_param(TW_Device_Extension *tw_dev, int request_id, int table_id, int parameter_id, int parameter_size_bytes)
{
TW_Command_Full *full_command_packet;
TW_Command *command_packet;
TW_Param_Apache *param; void *retval = NULL;
/* Post the command packet to the board */
twl_post_command_packet(tw_dev, request_id);
/* Poll for completion */ if (twl_poll_response(tw_dev, request_id, 30))
TW_PRINTK(tw_dev->host, TW_DRIVER, 0x7, "No valid response during get param") else
retval = (void *)&(param->data[0]);
/* This function will initialize the fields of a device extension */ staticint twl_initialize_device_extension(TW_Device_Extension *tw_dev)
{ int i, retval = 1;
/* This function will handle attention interrupts */ staticint twl_handle_attention_interrupt(TW_Device_Extension *tw_dev)
{ int retval = 1;
u32 request_id, doorbell;
/* Read doorbell status */
doorbell = readl(TWL_HOBDB_REG_ADDR(tw_dev));
/* Check for controller errors */ if (doorbell & TWL_DOORBELL_CONTROLLER_ERROR) {
TW_PRINTK(tw_dev->host, TW_DRIVER, 0xd, "Microcontroller Error: clearing"); goto out;
}
/* Check if we need to perform an AEN drain */ if (doorbell & TWL_DOORBELL_ATTENTION_INTERRUPT) { if (!(test_and_set_bit(TW_IN_ATTENTION_LOOP, &tw_dev->flags))) {
twl_get_request_id(tw_dev, &request_id); if (twl_aen_read_queue(tw_dev, request_id)) {
tw_dev->state[request_id] = TW_S_COMPLETED;
twl_free_request_id(tw_dev, request_id);
clear_bit(TW_IN_ATTENTION_LOOP, &tw_dev->flags);
}
}
}
/* Check for correct state */ if (tw_dev->state[request_id] != TW_S_POSTED) { if (tw_dev->srb[request_id] != NULL) {
TW_PRINTK(tw_dev->host, TW_DRIVER, 0xe, "Received a request id that wasn't posted");
TWL_MASK_INTERRUPTS(tw_dev); goto twl_interrupt_bail;
}
}
/* Check for internal command completion */ if (tw_dev->srb[request_id] == NULL) { if (request_id != tw_dev->chrdev_request_id) { if (twl_aen_complete(tw_dev, request_id))
TW_PRINTK(tw_dev->host, TW_DRIVER, 0xf, "Error completing AEN during attention interrupt");
} else {
tw_dev->chrdev_request_id = TW_IOCTL_CHRDEV_FREE;
wake_up(&tw_dev->ioctl_wqueue);
}
} else {
cmd = tw_dev->srb[request_id];
if (!error)
cmd->result = (DID_OK << 16);
/* Report residual bytes for single sgl */ if ((scsi_sg_count(cmd) <= 1) && (full_command_packet->command.newcommand.status == 0)) { if (full_command_packet->command.newcommand.sg_list[0].length < scsi_bufflen(tw_dev->srb[request_id]))
scsi_set_resid(cmd, scsi_bufflen(cmd) - full_command_packet->command.newcommand.sg_list[0].length);
}
/* Now complete the io */
scsi_dma_unmap(cmd);
scsi_done(cmd);
tw_dev->state[request_id] = TW_S_COMPLETED;
twl_free_request_id(tw_dev, request_id);
tw_dev->posted_request_count--;
}
/* Check for another response interrupt */
reg = readl(TWL_HISTAT_REG_ADDR(tw_dev));
}
twl_interrupt_bail:
spin_unlock(tw_dev->host->host_lock); return IRQ_RETVAL(handled);
} /* End twl_interrupt() */
/* This function will poll for a register change */ staticint twl_poll_register(TW_Device_Extension *tw_dev, void *reg, u32 value, u32 result, int seconds)
{ unsignedlong before; int retval = 1;
u32 reg_value;
reg_value = readl(reg);
before = jiffies;
while ((reg_value & value) != result) {
reg_value = readl(reg); if (time_after(jiffies, before + HZ * seconds)) goto out;
msleep(50);
}
retval = 0;
out: return retval;
} /* End twl_poll_register() */
/* This function will reset a controller */ staticint twl_reset_sequence(TW_Device_Extension *tw_dev, int soft_reset)
{ int retval = 1; int i = 0;
u32 status = 0; unsignedshort fw_on_ctlr_srl = 0, fw_on_ctlr_arch_id = 0; unsignedshort fw_on_ctlr_branch = 0, fw_on_ctlr_build = 0;
u32 init_connect_result = 0; int tries = 0; int do_soft_reset = soft_reset;
while (tries < TW_MAX_RESET_TRIES) { /* Do a soft reset if one is needed */ if (do_soft_reset) {
TWL_SOFT_RESET(tw_dev);
/* Make sure controller is in a good state */ if (twl_poll_register(tw_dev, TWL_SCRPD3_REG_ADDR(tw_dev), TWL_CONTROLLER_READY, 0x0, 30)) {
TW_PRINTK(tw_dev->host, TW_DRIVER, 0x10, "Controller never went non-ready during reset sequence");
tries++; continue;
} if (twl_poll_register(tw_dev, TWL_SCRPD3_REG_ADDR(tw_dev), TWL_CONTROLLER_READY, TWL_CONTROLLER_READY, 60)) {
TW_PRINTK(tw_dev->host, TW_DRIVER, 0x11, "Controller not ready during reset sequence");
tries++; continue;
}
}
/* Load sense buffers */ while (i < TW_Q_LENGTH) {
writel((u32)((u64)tw_dev->sense_buffer_phys[i] >> 32), TWL_HOBQPH_REG_ADDR(tw_dev));
writel((u32)tw_dev->sense_buffer_phys[i], TWL_HOBQPL_REG_ADDR(tw_dev));
/* Check status for over-run after each write */
status = readl(TWL_STATUS_REG_ADDR(tw_dev)); if (!(status & TWL_STATUS_OVERRUN_SUBMIT))
i++;
}
/* Now check status */
status = readl(TWL_STATUS_REG_ADDR(tw_dev)); if (status) {
TW_PRINTK(tw_dev->host, TW_DRIVER, 0x13, "Bad controller status after loading sense buffers");
do_soft_reset = 1;
tries++; continue;
}
/* Drain the AEN queue */ if (twl_aen_drain_queue(tw_dev, soft_reset)) {
TW_PRINTK(tw_dev->host, TW_DRIVER, 0x14, "AEN drain failed during reset sequence");
do_soft_reset = 1;
tries++; continue;
}
/* If we got here, controller is in a good state */
retval = 0; goto out;
}
out: return retval;
} /* End twl_reset_sequence() */
/* This function will reset a device extension */ staticint twl_reset_device_extension(TW_Device_Extension *tw_dev, int ioctl_reset)
{ int i = 0, retval = 1; unsignedlong flags = 0;
/* Block SCSI requests while we are resetting */ if (ioctl_reset)
scsi_block_requests(tw_dev->host);
/* Abort all requests that are in progress */ for (i = 0; i < TW_Q_LENGTH; i++) { if ((tw_dev->state[i] != TW_S_FINISHED) &&
(tw_dev->state[i] != TW_S_INITIAL) &&
(tw_dev->state[i] != TW_S_COMPLETED)) { struct scsi_cmnd *cmd = tw_dev->srb[i];
retval = 0;
out: if (ioctl_reset)
scsi_unblock_requests(tw_dev->host); return retval;
} /* End twl_reset_device_extension() */
/* This funciton returns unit geometry in cylinders/heads/sectors */ staticint twl_scsi_biosparam(struct scsi_device *sdev, struct block_device *bdev, sector_t capacity, int geom[])
{ int heads, sectors;
/* This is the new scsi eh reset function */ staticint twl_scsi_eh_reset(struct scsi_cmnd *SCpnt)
{
TW_Device_Extension *tw_dev = NULL; int retval = FAILED;
/* Make sure we are not issuing an ioctl or resetting from ioctl */
mutex_lock(&tw_dev->ioctl_lock);
/* Now reset the card and some of the device extension data */ if (twl_reset_device_extension(tw_dev, 0)) {
TW_PRINTK(tw_dev->host, TW_DRIVER, 0x15, "Controller reset failed during scsi host reset"); goto out;
}
/* This is the main scsi queue function to handle scsi opcodes */ staticint twl_scsi_queue_lck(struct scsi_cmnd *SCpnt)
{ void (*done)(struct scsi_cmnd *) = scsi_done; int request_id, retval;
TW_Device_Extension *tw_dev = (TW_Device_Extension *)SCpnt->device->host->hostdata;
/* If we are resetting due to timed out ioctl, report as busy */ if (test_bit(TW_IN_RESET, &tw_dev->flags)) {
retval = SCSI_MLQUEUE_HOST_BUSY; goto out;
}
/* Get a free request id */
twl_get_request_id(tw_dev, &request_id);
/* Save the scsi command for use by the ISR */
tw_dev->srb[request_id] = SCpnt;
/* This function tells the controller to shut down */ staticvoid __twl_shutdown(TW_Device_Extension *tw_dev)
{ /* Disable interrupts */
TWL_MASK_INTERRUPTS(tw_dev);
/* Free up the IRQ */
free_irq(tw_dev->tw_pci_dev->irq, tw_dev);
printk(KERN_WARNING "3w-sas: Shutting down host %d.\n", tw_dev->host->host_no);
/* Tell the card we are shutting down */ if (twl_initconnection(tw_dev, 1, 0, 0, 0, 0, 0, NULL, NULL, NULL, NULL, NULL)) {
TW_PRINTK(tw_dev->host, TW_DRIVER, 0x16, "Connection shutdown failed");
} else {
printk(KERN_WARNING "3w-sas: Shutdown complete.\n");
}
/* Clear doorbell interrupt just before exit */
TWL_CLEAR_DB_INTERRUPT(tw_dev);
} /* End __twl_shutdown() */
if (tw_dev->online)
__twl_shutdown(tw_dev);
} /* End twl_shutdown() */
/* This function configures unit settings when a unit is coming on-line */ staticint twl_sdev_configure(struct scsi_device *sdev, struct queue_limits *lim)
{ /* Force 60 second timeout */
blk_queue_rq_timeout(sdev->request_queue, 60 * HZ);
/* This function will probe and initialize a card */ staticint twl_probe(struct pci_dev *pdev, conststruct pci_device_id *dev_id)
{ struct Scsi_Host *host = NULL;
TW_Device_Extension *tw_dev; int retval = -ENODEV; int *ptr_phycount, phycount=0;
retval = pci_enable_device(pdev); if (retval) {
TW_PRINTK(host, TW_DRIVER, 0x17, "Failed to enable pci device"); goto out_disable_device;
}
pci_set_master(pdev);
pci_try_set_mwi(pdev);
retval = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64)); if (retval) {
TW_PRINTK(host, TW_DRIVER, 0x18, "Failed to set dma mask");
retval = -ENODEV; goto out_disable_device;
}
host = scsi_host_alloc(&driver_template, sizeof(TW_Device_Extension)); if (!host) {
TW_PRINTK(host, TW_DRIVER, 0x19, "Failed to allocate memory for device extension");
retval = -ENOMEM; goto out_disable_device;
}
tw_dev = shost_priv(host);
/* Save values to device extension */
tw_dev->host = host;
tw_dev->tw_pci_dev = pdev;
if (twl_initialize_device_extension(tw_dev)) {
TW_PRINTK(tw_dev->host, TW_DRIVER, 0x1a, "Failed to initialize device extension");
retval = -ENOMEM; goto out_free_device_extension;
}
/* Request IO regions */
retval = pci_request_regions(pdev, "3w-sas"); if (retval) {
TW_PRINTK(tw_dev->host, TW_DRIVER, 0x1b, "Failed to get mem region"); goto out_free_device_extension;
}
/* Save base address, use region 1 */
tw_dev->base_addr = pci_iomap(pdev, 1, 0); if (!tw_dev->base_addr) {
TW_PRINTK(tw_dev->host, TW_DRIVER, 0x1c, "Failed to ioremap");
retval = -ENOMEM; goto out_release_mem_region;
}
/* Disable interrupts on the card */
TWL_MASK_INTERRUPTS(tw_dev);
/* Initialize the card */ if (twl_reset_sequence(tw_dev, 0)) {
TW_PRINTK(tw_dev->host, TW_DRIVER, 0x1d, "Controller reset failed during probe");
retval = -ENOMEM; goto out_iounmap;
}
/* Set host specific parameters */
host->max_id = TW_MAX_UNITS;
host->max_cmd_len = TW_MAX_CDB_LEN;
host->max_lun = TW_MAX_LUNS;
host->max_channel = 0;
/* Register the card with the kernel SCSI layer */
retval = scsi_add_host(host, &pdev->dev); if (retval) {
TW_PRINTK(tw_dev->host, TW_DRIVER, 0x1e, "scsi add host failed"); goto out_iounmap;
}
pci_set_drvdata(pdev, host);
printk(KERN_WARNING "3w-sas: scsi%d: Found an LSI 3ware %s Controller at 0x%llx, IRQ: %d.\n",
host->host_no,
(char *)twl_get_param(tw_dev, 1, TW_VERSION_TABLE,
TW_PARAM_MODEL, TW_PARAM_MODEL_LENGTH),
(u64)pci_resource_start(pdev, 1), pdev->irq);
/* This function is called to remove a device */ staticvoid twl_remove(struct pci_dev *pdev)
{ struct Scsi_Host *host = pci_get_drvdata(pdev);
TW_Device_Extension *tw_dev;
/* Free up the mem region */
pci_release_regions(pdev);
/* Free up device extension resources */
twl_free_device_extension(tw_dev);
scsi_host_put(tw_dev->host);
pci_disable_device(pdev);
twl_device_extension_count--;
} /* End twl_remove() */
/* This function is called on PCI suspend */ staticint __maybe_unused twl_suspend(struct device *dev)
{ struct Scsi_Host *host = dev_get_drvdata(dev);
TW_Device_Extension *tw_dev = (TW_Device_Extension *)host->hostdata;
/* This function is called on driver initialization */ staticint __init twl_init(void)
{
printk(KERN_INFO "LSI 3ware SAS/SATA-RAID Controller device driver for Linux v%s.\n", TW_DRIVER_VERSION);
return pci_register_driver(&twl_driver);
} /* End twl_init() */
/* This function is called on driver exit */ staticvoid __exit twl_exit(void)
{
pci_unregister_driver(&twl_driver);
} /* End twl_exit() */
module_init(twl_init);
module_exit(twl_exit);
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.57 Sekunden
(vorverarbeitet am 2026-09-29)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.