[PATCH 1/2] scsi_transport_fc: FC pass through support via bsg interface - revised

Seokmann Ju
2008-10-13T17:53:26Z

Signed-off-by: Seokmann Ju <seokmann.ju@qlogic.com>
---
diff --git a/drivers/scsi/qla2xxx/qla_attr.c b/drivers/scsi/qla2xxx/
qla_attr.c
index 45e7dcb..d240da9 100644
--- a/drivers/scsi/qla2xxx/qla_attr.c
+++ b/drivers/scsi/qla2xxx/qla_attr.c
@@ -1230,6 +1230,234 @@ qla24xx_vport_disable(struct fc_vport
*fc_vport, bool disable)
return 0;
}

+static int
+qla2x00_execute_fc_service(struct fc_service *service)
+{
+ struct fc_rport *rport = service->rport;
+ fc_port_t *fcport = *(fc_port_t **) rport->dd_data;
+ struct Scsi_Host *host = rport_to_shost(rport);
+ scsi_qla_host_t *ha = shost_priv(host);
+ struct els_entry_24xx *els_iocb;
+ struct ct_entry_24xx *ct_iocb;
+ request_t *iocb;
+ srb_t *sp;
+ unsigned long flags;
+ uint32_t handle;
+ int index, sg_cnt;
+ uint8_t *els_pkt = service->payload;
+ uint16_t avail_dsds;
+ uint32_t *cur_dsd;
+
+ if (rport->port_state != FC_PORTSTATE_ONLINE || !fcport)
+ goto pt_error0;
+
+ if ((service->srv_request->request_type != FC_FRAME_TYPE_ELS) &&
+ (service->srv_request->request_type != FC_FRAME_TYPE_FC_CT))
+ goto pt_error0;
+
+ /* ELS doesn't support multiple SG */
+ if ((service->srv_request->request_type == FC_FRAME_TYPE_ELS) &&
+ (service->payload_sg_cnt > 1 || service->response_sg_cnt > 1)) {
+ printk(KERN_WARNING "ERROR: multiple SG of payload/response "
+ "[%u/%u] are not supported for ELS services\n",
+ service->payload_sg_cnt, service->response_sg_cnt);
+ service->srv_reply.status = FC_SERVICE_UNSUPPORTED;
+ goto pt_error0;
+ }
+
+ sp = mempool_alloc(ha->srb_mempool, GFP_ATOMIC);
+ if (!sp)
+ goto pt_error0;
+
+ sp->ha = ha;
+ sp->fcport = fcport;
+ sp->cmd = (struct scsi_cmnd *)service;
+ sp->flags = SRB_ELS_CT;
+
+ spin_lock_irqsave(&ha->hardware_lock, flags);
+ iocb = qla2x00_req_pkt(ha);
+ if (iocb == NULL) {
+ qla_printk(KERN_WARNING, ha,
+ "Passthru request failed to get request packet\n");
+ goto pt_error1;
+ }
+
+ /* Check for room in outstanding command list. */
+ handle = ha->current_outstanding_cmd;
+ for (index = 1; index < MAX_OUTSTANDING_COMMANDS; index++) {
+ handle++;
+ if (handle == MAX_OUTSTANDING_COMMANDS)
+ handle = 1;
+ if (!ha->outstanding_cmds[handle])
+ break;
+ }
+
+ ha->current_outstanding_cmd = handle;
+ ha->outstanding_cmds[handle] = (srb_t *) sp;
+
+ iocb->handle = handle;
+
+ service->payload_sg_cnt = dma_map_sg(&rport->dev,
+ service->payload_dma, service->payload_sg_cnt, DMA_TO_DEVICE);
+ service->response_sg_cnt = dma_map_sg(&rport->dev,
+ service->response_dma, service->response_sg_cnt, DMA_FROM_DEVICE);
+
+ if (service->srv_request->request_type == FC_FRAME_TYPE_ELS) {
+ els_iocb = (struct els_entry_24xx *)iocb;
+ els_iocb->entry_type = ELS_IOCB_TYPE;
+ els_iocb->entry_count = 1;
+ els_iocb->sys_define = 0;
+ els_iocb->entry_status = 0;
+ els_iocb->nport_handle = cpu_to_le16(fcport->loop_id);
+ els_iocb->tx_dsd_count = __constant_cpu_to_le16(1);
+ els_iocb->vp_index = ha->vp_idx;
+ els_iocb->sof_type = EST_SOFI3;
+ els_iocb->rx_dsd_count = __constant_cpu_to_le16(1);
+ els_iocb->opcode = els_pkt[0];
+ els_iocb->port_id[0] = fcport->d_id.b.al_pa;
+ els_iocb->port_id[1] = fcport->d_id.b.area;
+ els_iocb->port_id[2] = fcport->d_id.b.domain;
+ els_iocb->control_flags = 0;
+ els_iocb->rx_byte_count = cpu_to_le32(service->response_size);
+ els_iocb->tx_byte_count = cpu_to_le32(service->payload_size);
+ els_iocb->tx_address[0] = cpu_to_le32(LSD(sg_dma_address
+ (&service->payload_dma[0])));
+ els_iocb->tx_address[1] = cpu_to_le32(MSD(sg_dma_address
+ (&service->payload_dma[0])));
+
+ els_iocb->tx_len = cpu_to_le32(sg_dma_len
+ (&service->payload_dma[0]));
+
+ els_iocb->rx_address[0] = cpu_to_le32(LSD(sg_dma_address
+ (&service->response_dma[0])));
+ els_iocb->rx_address[1] = cpu_to_le32(MSD(sg_dma_address
+ (&service->response_dma[0])));
+
+ els_iocb->rx_len = cpu_to_le32(sg_dma_len
+ (&service->response_dma[0]));
+
+ /* lld_pkt is only for ELS services in case of abort */
+ service->lld_pkt = (void *) els_iocb;
+ } else {
+ ct_iocb = (struct ct_entry_24xx *)iocb;
+ ct_iocb->entry_type = CT_IOCB_TYPE;
+ ct_iocb->entry_count = 1;
+ ct_iocb->entry_status = 0;
+ ct_iocb->comp_status = __constant_cpu_to_le16(0);
+ if (IS_FWI2_CAPABLE(ha))
+ ct_iocb->nport_handle = cpu_to_le16(NPH_SNS);
+ else
+ ct_iocb->nport_handle =
+ cpu_to_le16(SIMPLE_NAME_SERVER);
+ ct_iocb->cmd_dsd_count =
+ __constant_cpu_to_le16(service->payload_sg_cnt);
+ ct_iocb->vp_index = ha->vp_idx;
+ ct_iocb->timeout =
+ __constant_cpu_to_le16(service->timer.expires);
+ ct_iocb->rsp_dsd_count =
+ __constant_cpu_to_le16(service->response_sg_cnt);
+ ct_iocb->rsp_byte_count =
+ cpu_to_le32(service->response_size);
+ ct_iocb->cmd_byte_count =
+ cpu_to_le32(service->payload_size);
+ ct_iocb->dseg_0_address[0] = cpu_to_le32(LSD(sg_dma_address
+ (&service->payload_dma[0])));
+ ct_iocb->dseg_0_address[1] = cpu_to_le32(MSD(sg_dma_address
+ (&service->payload_dma[0])));
+
+ ct_iocb->dseg_0_len = cpu_to_le32(sg_dma_len
+ (&service->payload_dma[0]));
+
+ sg_cnt = service->response_sg_cnt;
+ avail_dsds = 1;
+ cur_dsd = (uint32_t *)ct_iocb->dseg_1_address;
+ index = 0;
+ while (sg_cnt) {
+ cont_a64_entry_t *cont_pkt;
+
+ /* Allocate additional continuation packets? */
+ if (avail_dsds == 0) {
+ /*
+ * Five DSDs are available in the Continuation
+ * Type 1 IOCB.
+ */
+ cont_pkt = qla2x00_prep_cont_type1_iocb(ha);
+ cur_dsd =
+ (uint32_t *) cont_pkt->dseg_0_address;
+ avail_dsds = 5;
+ }
+
+ *cur_dsd++ = cpu_to_le32(LSD(sg_dma_address
+ (&service->response_dma[index])));
+ *cur_dsd++ = cpu_to_le32(MSD(sg_dma_address
+ (&service->response_dma[index])));
+ *cur_dsd++ = cpu_to_le32(sg_dma_len
+ (&service->response_dma[index]));
+ avail_dsds--;
+ sg_cnt--;
+ index++;
+ }
+{
+ int i;
+ ushort *tmp = ct_iocb;
+ for (i=0;i<sizeof (struct ct_entry_24xx) / 2; i++)
+ printk("%4x\n", tmp[i]);
+}
+ }
+
+ wmb();
+ qla2x00_isp_cmd(ha);
+
+ spin_unlock_irqrestore(&ha->hardware_lock, flags);
+ return 0;
+pt_error1:
+ spin_unlock_irqrestore(&ha->hardware_lock, flags);
+pt_error0:
+ qla_printk(KERN_WARNING, ha, "Passthru ELS failed\n");
+
+ return -EINVAL;
+}
+
+static int
+qla2x00_abort_fc_service(struct fc_service *service)
+{
+ unsigned long flags;
+ struct fc_rport *rport = service->rport;
+ struct Scsi_Host *host = rport_to_shost(rport);
+ scsi_qla_host_t *ha = shost_priv(host);
+ struct abort_entry_24xx *abort_iocb;
+ struct els_entry_24xx *els_iocb = service->lld_pkt;
+
+ printk("ELS/CT: DEBUG: %s is called...\n", __func__);
+ if (service->srv_request->request_type != FC_FRAME_TYPE_ELS) {
+ printk(KERN_ERR "abort frame is not supported\n");
+ return -EINVAL;
+ }
+
+ spin_lock_irqsave(&ha->hardware_lock, flags);
+ abort_iocb = (struct abort_entry_24xx *)qla2x00_req_pkt(ha);
+
+ abort_iocb->entry_type = ABORT_IOCB_TYPE;
+ abort_iocb->entry_count = 1;
+ abort_iocb->handle_count = 0;
+ abort_iocb->entry_status = 0;
+ abort_iocb->options = 0;
+ abort_iocb->handle_to_abort = els_iocb->handle;
+ abort_iocb->port_id[0] = els_iocb->port_id[0];
+ abort_iocb->port_id[1] = els_iocb->port_id[1];
+ abort_iocb->port_id[2] = els_iocb->port_id[2];
+ abort_iocb->vp_index = ha->vp_idx;
+
+ wmb();
+ qla2x00_isp_cmd(ha);
+
+ spin_unlock_irqrestore(&ha->hardware_lock, flags);
+
+ service->service_state_flag = FC_SERVICE_STATE_ABORTED;
+
+ return 0;
+}
+
struct fc_function_template qla2xxx_transport_functions = {

.show_host_node_name = 1,
@@ -1273,6 +1501,9 @@ struct fc_function_template
qla2xxx_transport_functions = {
.vport_create = qla24xx_vport_create,
.vport_disable = qla24xx_vport_disable,
.vport_delete = qla24xx_vport_delete,
+
+ .execute_fc_service = qla2x00_execute_fc_service,
+ .abort_fc_service = qla2x00_abort_fc_service,
};

struct fc_function_template qla2xxx_transport_vport_functions = {
@@ -1313,6 +1544,9 @@ struct fc_function_template
qla2xxx_transport_vport_functions = {
.dev_loss_tmo_callbk = qla2x00_dev_loss_tmo_callbk,
.terminate_rport_io = qla2x00_terminate_rport_io,
.get_fc_host_stats = qla2x00_get_fc_host_stats,
+
+ .execute_fc_service = qla2x00_execute_fc_service,
+ .abort_fc_service = qla2x00_abort_fc_service,
};

void
diff --git a/drivers/scsi/qla2xxx/qla_def.h b/drivers/scsi/qla2xxx/
qla_def.h
index 431c19e..c5510db 100644
--- a/drivers/scsi/qla2xxx/qla_def.h
+++ b/drivers/scsi/qla2xxx/qla_def.h
@@ -93,6 +93,10 @@
#define LSD(x) ((uint32_t)((uint64_t)(x)))
#define MSD(x) ((uint32_t)((((uint64_t)(x)) >> 16) >> 16))

+/* ELS PT request buffer = 32 bytes */
+#define EXT_ELS_PT_REQ_WWPN_VALID 0x1
+#define EXT_ELS_PT_REQ_WWNN_VALID 0x2
+#define EXT_ELS_PT_REQ_PID_VALID 0x4

/*
* I/O register
@@ -213,6 +217,7 @@ typedef struct srb {
#define SRB_FO_CANCEL BIT_9 /* Command don't need to do failover */
#define SRB_IOCTL BIT_10 /* IOCTL command. */
#define SRB_TAPE BIT_11 /* FCP2 (Tape) command. */
+#define SRB_ELS_CT BIT_12 /* Link/Generic Service requests */

/*
* ISP I/O Register Set structure definitions.
@@ -2422,6 +2427,8 @@ typedef struct scsi_qla_host {
/* SNS command interfaces for 2200. */
struct sns_cmd_pkt *sns_cmd;
dma_addr_t sns_cmd_dma;
+ char *pass_thru;
+ dma_addr_t pass_thru_dma;

#define SFP_DEV_SIZE 256
#define SFP_BLOCK_SIZE 64
@@ -2463,6 +2470,7 @@ typedef struct scsi_qla_host {
struct mutex vport_lock; /* Virtual port synchronization */
struct completion mbx_cmd_comp; /* Serialize mbx access */
struct completion mbx_intr_comp; /* Used for completion
notification */
+ struct completion pass_thru_intr_comp;

uint32_t mbx_flags;
#define MBX_IN_PROGRESS BIT_0
diff --git a/drivers/scsi/qla2xxx/qla_fw.h b/drivers/scsi/qla2xxx/
qla_fw.h
index cf19451..481ab56 100644
--- a/drivers/scsi/qla2xxx/qla_fw.h
+++ b/drivers/scsi/qla2xxx/qla_fw.h
@@ -625,6 +625,41 @@ struct els_entry_24xx {
uint32_t rx_len; /* Data segment 1 length. */
};

+struct els_status_24xx {
+ uint8_t entry_type; /* Entry type. */
+ uint8_t entry_count; /* Entry count. */
+ uint8_t sys_define; /* System Defined. */
+ uint8_t entry_status; /* Entry Status. */
+
+ uint32_t handle; /* System handle. */
+
+ uint16_t comp_status;
+
+ uint16_t nport_handle; /* N_PORT handle. */
+
+ uint16_t tx_dsd_count;
+
+ uint8_t vp_index;
+ uint8_t sof_type;
+
+ uint32_t rx_xchg_address; /* Receive exchange address. */
+ uint16_t rx_dsd_count;
+
+ uint8_t opcode;
+ uint8_t reserved_2;
+
+ uint8_t port_id[3];
+ uint8_t reserved_3;
+
+ uint16_t reserved_4;
+
+ uint16_t control_flags; /* Control flags. */
+
+ uint32_t total_byte_count;
+ uint32_t err_subcode1;
+ uint32_t err_subcode2;
+};
+
/*
* ISP queue - Mailbox Command entry structure definition.
*/
diff --git a/drivers/scsi/qla2xxx/qla_gbl.h b/drivers/scsi/qla2xxx/
qla_gbl.h
index 0b15673..0295d39 100644
--- a/drivers/scsi/qla2xxx/qla_gbl.h
+++ b/drivers/scsi/qla2xxx/qla_gbl.h
@@ -121,6 +121,9 @@ extern int qla2x00_start_scsi(srb_t *sp);
extern int qla24xx_start_scsi(srb_t *sp);
int qla2x00_marker(scsi_qla_host_t *, uint16_t, uint16_t, uint8_t);
int __qla2x00_marker(scsi_qla_host_t *, uint16_t, uint16_t, uint8_t);
+extern request_t * qla2x00_req_pkt(scsi_qla_host_t *);
+extern void qla2x00_isp_cmd(scsi_qla_host_t *ha);
+extern cont_a64_entry_t *qla2x00_prep_cont_type1_iocb(scsi_qla_host_t
*);

/*
* Global Function Prototypes in qla_mbx.c source file.
@@ -345,6 +348,7 @@ extern int qla2x00_fdmi_register(scsi_qla_host_t *);
extern int qla2x00_gfpn_id(scsi_qla_host_t *, sw_info_t *);
extern int qla2x00_gpsc(scsi_qla_host_t *, sw_info_t *);
extern void qla2x00_get_sym_node_name(scsi_qla_host_t *, uint8_t *);
+extern int qla2x00_mgmt_svr_login(scsi_qla_host_t *);

/*
* Global Function Prototypes in qla_attr.c source file.
diff --git a/drivers/scsi/qla2xxx/qla_gs.c b/drivers/scsi/qla2xxx/
qla_gs.c
index c2a4bfb..ceef231 100644
--- a/drivers/scsi/qla2xxx/qla_gs.c
+++ b/drivers/scsi/qla2xxx/qla_gs.c
@@ -1093,7 +1093,7 @@ qla2x00_sns_rnn_id(scsi_qla_host_t *ha)
*
* Returns 0 on success.
*/
-static int
+int
qla2x00_mgmt_svr_login(scsi_qla_host_t *ha)
{
int ret;
diff --git a/drivers/scsi/qla2xxx/qla_init.c b/drivers/scsi/qla2xxx/
qla_init.c
index ee89ddd..4956a9b 100644
--- a/drivers/scsi/qla2xxx/qla_init.c
+++ b/drivers/scsi/qla2xxx/qla_init.c
@@ -2386,6 +2386,18 @@ qla2x00_configure_fabric(scsi_qla_host_t *ha)
return (QLA_SUCCESS);
}

+ /* allocate fc_port_t structure for name/directory server */
+ fcptemp = qla2x00_alloc_fcport(ha, GFP_KERNEL);
+ fcptemp->flags |= (FCF_FABRIC_DEVICE | FCF_LOGIN_NEEDED);
+ fcptemp->loop_id = loop_id;
+ fcptemp->d_id.b.domain = 0xff;
+ fcptemp->d_id.b.area = 0xff;
+ fcptemp->d_id.b.al_pa = 0xfc;
+ list_add_tail(&fcptemp->list, &pha->fcports);
+ atomic_set(&fcptemp->state, FCS_ONLINE);
+ qla2x00_reg_remote_port(ha, fcptemp);
+
+
if (test_and_clear_bit(REGISTER_FC4_NEEDED, &ha->dpc_flags)) {
if (qla2x00_rft_id(ha)) {
/* EMPTY */
diff --git a/drivers/scsi/qla2xxx/qla_iocb.c b/drivers/scsi/qla2xxx/
qla_iocb.c
index d57669a..45e227e 100644
--- a/drivers/scsi/qla2xxx/qla_iocb.c
+++ b/drivers/scsi/qla2xxx/qla_iocb.c
@@ -11,8 +11,6 @@

#include <scsi/scsi_tcq.h>

-static request_t *qla2x00_req_pkt(scsi_qla_host_t *ha);
-static void qla2x00_isp_cmd(scsi_qla_host_t *ha);

/**
* qla2x00_get_cmd_direction() - Determine control_flag data
direction.
@@ -114,7 +112,7 @@ qla2x00_prep_cont_type0_iocb(scsi_qla_host_t *ha)
*
* Returns a pointer to the continuation type 1 IOCB packet.
*/
-static inline cont_a64_entry_t *
+inline cont_a64_entry_t *
qla2x00_prep_cont_type1_iocb(scsi_qla_host_t *ha)
{
cont_a64_entry_t *cont_pkt;
@@ -471,7 +469,7 @@ qla2x00_marker(scsi_qla_host_t *ha, uint16_t
loop_id, uint16_t lun,
*
* Returns NULL if function failed, else, a pointer to the request
packet.
*/
-static request_t *
+request_t *
qla2x00_req_pkt(scsi_qla_host_t *ha)
{
device_reg_t __iomem *reg = ha->iobase;
@@ -541,7 +539,7 @@ qla2x00_req_pkt(scsi_qla_host_t *ha)
*
* Note: The caller must hold the hardware lock before calling this
routine.
*/
-static void
+void
qla2x00_isp_cmd(scsi_qla_host_t *ha)
{
device_reg_t __iomem *reg = ha->iobase;
diff --git a/drivers/scsi/qla2xxx/qla_isr.c b/drivers/scsi/qla2xxx/
qla_isr.c
index 45a3b93..1b11358 100644
--- a/drivers/scsi/qla2xxx/qla_isr.c
+++ b/drivers/scsi/qla2xxx/qla_isr.c
@@ -1398,6 +1398,9 @@ qla24xx_process_response_queue(struct
scsi_qla_host *ha)
{
struct device_reg_24xx __iomem *reg = &ha->iobase->isp24;
struct sts_entry_24xx *pkt;
+ struct els_status_24xx *els_status;
+ struct fc_service *service;
+ srb_t *sp;

if (!ha->flags.online)
return;
@@ -1430,10 +1433,72 @@ qla24xx_process_response_queue(struct
scsi_qla_host *ha)
case STATUS_CONT_TYPE:
qla2x00_status_cont_entry(ha, (sts_cont_entry_t *)pkt);
break;
+ case MS_IOCB_TYPE:
+ case ELS_IOCB_TYPE:
+ if (pkt->handle < MAX_OUTSTANDING_COMMANDS) {
+ sp = ha->outstanding_cmds[pkt->handle];
+ service = (struct fc_service *) sp->cmd;
+ ha->outstanding_cmds[pkt->handle] = NULL;
+ } else {
+ service = NULL;
+ break;
+ }
+
+ service->service_state_flag = FC_SERVICE_STATE_DONE;
+ service->srv_reply.status = FC_SERVICE_COMPLETE;
+ service->srv_reply.error_code = pkt->comp_status;
+
+ if (pkt->comp_status) {
+ printk("ELS/CT: comp_status = %x\n", pkt->comp_status);
+ els_status = (struct els_status_24xx *)pkt;
+ service->srv_reply.residual = 0;
+ if (pkt->comp_status == CS_DATA_UNDERRUN)
+ service->srv_reply.residual =
+ els_status->total_byte_count;
+ else if (pkt->comp_status == CS_ABORTED) {
+ service->service_state_flag =
+ FC_SERVICE_STATE_ABORTED;
+ service->srv_reply.status =
+ FC_SERVICE_ABORT;
+ } else
+ service->srv_reply.status = FC_SERVICE_ERROR;
+ }
+
+ dma_unmap_sg(&ha->pdev->dev, service->payload_dma,
+ service->payload_sg_cnt, DMA_TO_DEVICE);
+ dma_unmap_sg(&ha->pdev->dev, service->response_dma,
+ service->response_sg_cnt, DMA_FROM_DEVICE);
+
+ mempool_free(sp, ha->srb_mempool);
+ service->service_done(service);
+ break;
case VP_RPT_ID_IOCB_TYPE:
qla24xx_report_id_acquisition(ha,
(struct vp_rpt_id_entry_24xx *)pkt);
break;
+ case ABORT_IOCB_TYPE:
+ if (pkt->comp_status) {
+ if (pkt->handle < MAX_OUTSTANDING_COMMANDS) {
+ sp = ha->outstanding_cmds[pkt->handle];
+ ha->outstanding_cmds[pkt->handle] =
+ NULL;
+ if (sp->flags & SRB_ELS_CT) {
+ service = (struct fc_service *)
+ sp->cmd;
+ qla_printk(KERN_ERR, ha,
+ "failed to abort"
+ " FC service %p\n",
+ service);
+
+ } else
+ qla_printk(KERN_ERR, ha,
+ "failed to abort"
+ " SCSI CMD[0] = %2x\n",
+ sp->cmd->cmnd[0]);
+ }
+ }
+
+ break;
default:
/* Type Not Supported. */
DEBUG4(printk(KERN_WARNING
diff --git a/drivers/scsi/qla2xxx/qla_os.c b/drivers/scsi/qla2xxx/
qla_os.c
index 26afe44..7e1f027 100644
--- a/drivers/scsi/qla2xxx/qla_os.c
+++ b/drivers/scsi/qla2xxx/qla_os.c
@@ -1672,6 +1672,8 @@ qla2x00_probe_one(struct pci_dev *pdev, const
struct pci_device_id *id)
init_completion(&ha->mbx_cmd_comp);
complete(&ha->mbx_cmd_comp);
init_completion(&ha->mbx_intr_comp);
+ init_completion(&ha->pass_thru_intr_comp);
+

INIT_LIST_HEAD(&ha->list);
INIT_LIST_HEAD(&ha->fcports);
@@ -2020,6 +2022,8 @@ qla2x00_mem_alloc(scsi_qla_host_t *ha)
sizeof(struct ct_sns_pkt), &ha->ct_sns_dma, GFP_KERNEL);
if (!ha->ct_sns)
goto fail_free_ms_iocb;
+ ha->pass_thru = dma_alloc_coherent(&ha->pdev->dev,
+ PAGE_SIZE, &ha->pass_thru_dma, GFP_KERNEL);
}

return 0;
---