Related
I'm trying to enable LPUART1 to write/read traces and debug my code.
Im setting PC0/PC1 pin:
/*Configure GPIO pins : PC0 PC1 */
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF8_LPUART1;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
Created the handlers:
UART_HandleTypeDef hlpuart1;
DMA_HandleTypeDef hdma_lpuart_tx;
DMA_HandleTypeDef hdma_lpuart_rx;
My init function. I call it from main.c:
/* LPUART1 init function */
void MX_LPUART1_UART_Init(void)
{
hlpuart1.Instance = LPUART1;
hlpuart1.Init.BaudRate = 115200;
hlpuart1.Init.WordLength = UART_WORDLENGTH_8B;
hlpuart1.Init.StopBits = UART_STOPBITS_1;
hlpuart1.Init.Parity = UART_PARITY_NONE;
hlpuart1.Init.Mode = UART_MODE_TX_RX;
hlpuart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
hlpuart1.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE;
hlpuart1.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT;
if (HAL_UART_Init(&hlpuart1) != HAL_OK)
{
Error_Handler();
}
}
The function HAL_UART_Init:
/* USER CODE BEGIN LPUART1_MspInit 0 */
/* USER CODE END LPUART1_MspInit 0 */
/* LPUART1 clock enable */
__HAL_RCC_LPUART1_CLK_ENABLE();
__HAL_RCC_GPIOC_CLK_ENABLE();
/**LPUART1 GPIO Configuration
PC0 ------> LPUART1_RX
PC1 ------> LPUART1_TX
*/
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF8_LPUART1;
HAL_GPIO_Init(GPIOC, &GPIO_InitStruct);
/* LPUART1 DMA Init */
/* LPUART_RX Init */
hdma_lpuart_rx.Instance = DMA2_Channel7;
hdma_lpuart_rx.Init.Request = DMA_REQUEST_4;
hdma_lpuart_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_lpuart_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_lpuart_rx.Init.MemInc = DMA_MINC_ENABLE;
hdma_lpuart_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_lpuart_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_lpuart_rx.Init.Mode = DMA_CIRCULAR;
hdma_lpuart_rx.Init.Priority = DMA_PRIORITY_LOW;
if (HAL_DMA_Init(&hdma_lpuart_rx) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(uartHandle,hdmarx,hdma_usart1_rx);
/* LPUART_TX Init */
hdma_lpuart_tx.Instance = DMA2_Channel6;
hdma_lpuart_tx.Init.Request = DMA_REQUEST_4;
hdma_lpuart_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_lpuart_tx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_lpuart_tx.Init.MemInc = DMA_MINC_ENABLE;
hdma_lpuart_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_lpuart_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_lpuart_tx.Init.Mode = DMA_NORMAL;
hdma_lpuart_tx.Init.Priority = DMA_PRIORITY_LOW;
if (HAL_DMA_Init(&hdma_lpuart_tx) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(uartHandle,hdmatx,hdma_lpuart_tx);
/* LPUART1 interrupt Init */
HAL_NVIC_SetPriority(LPUART1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(LPUART1_IRQn);
I have set the NVIC LPUART1 at the end of the last code. I have enable too the DMA2 channel 6 and 7 interrupts:
/**
* Enable DMA controller clock
*/
void MX_DMA_Init(void)
{
/* DMA controller clock enable */
__HAL_RCC_DMA1_CLK_ENABLE();
__HAL_RCC_DMA2_CLK_ENABLE();
/* DMA interrupt init */
/* DMA1_Channel1_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA1_Channel1_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel1_IRQn);
/* DMA1_Channel2_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA1_Channel2_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel2_IRQn);
/* DMA1_Channel4_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA1_Channel4_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel4_IRQn);
/* DMA1_Channel5_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA1_Channel5_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(DMA1_Channel5_IRQn);
/* DMA2_Channel1_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA2_Channel1_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(DMA2_Channel1_IRQn);
/* DMA2_Channel2_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA2_Channel2_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(DMA2_Channel2_IRQn);
/* DMA2_Channel6_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA2_Channel6_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(DMA2_Channel6_IRQn);
/* DMA2_Channel7_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA2_Channel7_IRQn, 5, 0);
HAL_NVIC_EnableIRQ(DMA2_Channel7_IRQn);
}
In my main.c im trying to print something and it always return "HAL_OK":
HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)'ACK\n', 5);
Inside the last function, we have set this callback:
/* Set the UART DMA transfer complete callback */
huart->hdmatx->XferCpltCallback = UART_DMATransmitCplt;
And inside this one, we have this callback:
HAL_UART_TxCpltCallback(huart);
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart) {
/* Set transmission flag: transfer complete */
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
if(huart->Instance == USART1)
{
if(UART_TX_Acess != NULL)
{
xSemaphoreGiveFromISR(UART_TX_Acess, &xHigherPriorityTaskWoken); //Usart transmision complete
}
}
else if(huart->Instance == LPUART1)
{
modbus_tx_finish = 0;
}
portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
}
Everything seems to be fine coded but DMA2_Channel6_IRQHandler is jumping and and inside DMA IRQhandler an error appears:
/* Transfer Error Interrupt management **************************************/
But I cant understand why...
Ok I noticed the error. I was writting wrong the function to write. THis was the original:
HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)'ACK\n', 5);
And this is the change:
HAL_UART_Transmit_DMA(&hlpuart1, (uint8_t *)"ACK\n", 5);
I'm trying to use GPT to measuring frequency on a gpio. After some research on the google and Linux's documentation, unfortunately I couldn't see any compatible GPT driver in kernel. I found a patch on google and applied it. But I have a getting clock error now. There is a function which It gets the clock but it's never executing.
P.S : I prepared a platform driver to use exported functions (e.g. mxc_request_input_capture) in timer-imx-gpt.c.
My dts nodes
/*These nodes are child node if aips1*/
gpt1: gpt#302d0000 {
compatible = "fsl,imx8mm-gpt";
reg = <0x302d0000 0x10000>;
interrupts = <GIC_SPI 55 IRQ_TYPE_LEVEL_HIGH>;
clocks = <&clk IMX8MM_CLK_GPT1>,
<&clk IMX8MM_CLK_GPT1_ROOT>,
<&clk IMX8MM_CLK_GPT_3M>;
clock-names = "ipg", "per", "osc_per";
pinctrl-names = "default";
pinctrl-0 = <&pinctrl_gpt_input_capture0>;
status = "okay";
};
iomuxc: pinctrl#30330000 {
compatible = "fsl,imx8mm-iomuxc";
reg = <0x30330000 0x10000>;
pinctrl_gpt_input_capture0: gptinputcapture0grp {
fsl,pins = <
MX8MM_IOMUXC_SAI3_RXFS_GPT1_CAPTURE1 0xd6 /*0x00000000*/
>;
};
};
request gpt input capture func (in timer-imx-gpt.c)
int mxc_request_input_capture(unsigned int chan, mxc_icap_handler_t handler,
unsigned long capflags, void *dev_id)
{
struct imx_timer *imxtm;
struct icap_channel *ic;
unsigned long flags;
u64 start_cycles;
int ret = 0;
u32 mode;
/* we only care about rising and falling flags */
capflags &= (IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING);
if (chan > 1 || !handler || !capflags)
return -EINVAL;
printk("mxc_request_input_capture 1\n");
ic = &icap_channel_arr[chan];
imxtm = ic->imxtm;
printk("mxc_request_input_capture 2\n");
if (!imxtm->gpt->gpt_ic_enable)
return -ENODEV; //THIS ERROR CODE RETURN
printk("mxc_request_input_capture 3\n");
spin_lock_irqsave(&icap_lock, flags);
if (ic->handler) {
ret = -EBUSY;
goto out;
}
printk("mxc_request_input_capture 4\n");
ic->handler = handler;
ic->dev_id = dev_id;
switch (capflags) {
case IRQF_TRIGGER_RISING:
mode = V2_IM_RISING;
break;
case IRQF_TRIGGER_FALLING:
mode = V2_IM_FALLING;
break;
default:
mode = V2_IM_BOTH;
break;
}
printk("mxc_request_input_capture 4\n");
/* ack any pending input capture interrupt before enabling */
imxtm->gpt->gpt_ic_irq_acknowledge(ic);
/*
* initialize the cyclecounter. The input capture is capturing
* from the mxc clocksource, so it has the same mask/shift/mult.
*/
memset(&ic->cc, 0, sizeof(ic->cc));
ic->cc.read = gpt_ic_read;
ic->cc.mask = clocksource_mxc.mask;
ic->cc.shift = clocksource_mxc.shift;
ic->cc.mult = clocksource_mxc.mult;
printk("mxc_request_input_capture 5\n");
/* initialize a timecounter for the input capture */
start_cycles = mxc_read_sched_clock();
timecounter_init(&ic->tc, &ic->cc, ktime_get_ns());
printk("mxc_request_input_capture 6\n");
/*
* timecounter_init() read the last captured timer count, but
* that's not the start cycle counter, so update it with the
* real start cycles.
*/
ic->tc.cycle_last = start_cycles;
imxtm->gpt->gpt_ic_enable(ic, mode);
imxtm->gpt->gpt_ic_irq_enable(ic);
printk("mxc_request_input_capture 7\n");
out:
spin_unlock_irqrestore(&icap_lock, flags);
printk("mxc_request_input_capture 8\n");
return ret;
}
EXPORT_SYMBOL_GPL(mxc_request_input_capture);
I realized that imxtm->gpt->gpt_ic_enable variable seems NULL. imxtm structure gets filled in the end of static int __init _mxc_timer_init function. But the function returns error before filling the struct. The error is happening checking the clock. The function which related to getting clock is mxc_timer_init_dt but it never executes.
log:
root:~# dmesg | grep gpt
[ 0.000174] gpt imx6dl_timer_init_dt HEAD
[ 0.000240] gpt _mxc_timer_init
[ 0.000244] gpt imxtm->type 3
[ 0.000249] gpt imxtm->type GPT_TYPE_IMX6DL
[ 0.000252] imx-gpt is NOT null
[ 0.000260] gpt GPT_TYPE_IMX6DL 3
[ 0.000263] gpt mxc_timer_init_dt error -517
[ 16.837810] mxc-timer 302d0000.gpt: GPT Timer Probe Function head
[ 16.843920] mxc-timer 302d0000.gpt: GPT Timer Probe Function end
It start with executing imx6dl_timer_init_dt. mxc_timer_init_dt function must be execute after imx6dl_timer_init_dt to get the clock but it is not executing. Due to failing on getting clock, I am not able to use the gpt module.
some parts of timer-imx-gpt.c
static int __init _mxc_timer_init(struct imx_timer *imxtm)
{
struct icap_channel *ic;
int i, ret;
printk(KERN_INFO "gpt _mxc_timer_init\n");
printk(KERN_INFO "gpt imxtm->type %d \n", imxtm->type);
switch (imxtm->type) {
case GPT_TYPE_IMX1:
imxtm->gpt = &imx1_gpt_data;
printk(KERN_INFO "gpt imxtm->type GPT_TYPE_IMX1 \n");
break;
case GPT_TYPE_IMX21:
imxtm->gpt = &imx21_gpt_data;
printk(KERN_INFO "gpt imxtm->type GPT_TYPE_IMX21 \n");
break;
case GPT_TYPE_IMX31:
imxtm->gpt = &imx31_gpt_data;
printk(KERN_INFO "gpt imxtm->type GPT_TYPE_IMX31 \n");
break;
case GPT_TYPE_IMX6DL:
imxtm->gpt = &imx6dl_gpt_data;
printk(KERN_INFO "gpt imxtm->type GPT_TYPE_IMX6DL \n");
break;
default:
printk(KERN_INFO "gpt imxtm->type DEFAULT \n");
return -EINVAL;
}
imxtm->gpt = &imx6dl_gpt_data;
if(!imxtm->gpt)
printk(KERN_INFO "imx-gpt is null");
printk(KERN_INFO "imx-gpt is NOT null");
if (IS_ERR(imxtm->clk_per)) {
pr_err("i.MX timer: unable to get clk\n"); //I SAW THIS LOG IN LINUX LOGS
return PTR_ERR(imxtm->clk_per); //Function return error at this line
}
printk(KERN_INFO "gpt clk_per success\n");
if (!IS_ERR(imxtm->clk_ipg))
clk_prepare_enable(imxtm->clk_ipg);
printk(KERN_INFO "gpt clk_ipg success\n");
clk_prepare_enable(imxtm->clk_per);
printk(KERN_INFO "gpt clk_prepare_enable\n");
/*
* Initialise to a known state (all timers off, and timing reset)
*/
writel_relaxed(0, imxtm->base + MXC_TCTL);
writel_relaxed(0, imxtm->base + MXC_TPRER); /* see datasheet note */
imxtm->gpt->gpt_oc_setup_tctl(imxtm);
/* init and register the timer to the framework */
ret = mxc_clocksource_init(imxtm);
if (ret)
return ret;
ret = mxc_clockevent_init(imxtm);
if (ret)
return ret;
/*Filling the imx structure. But never reach here*/
for (i = 0; i < 2; i++) {
ic = &icap_channel_arr[i];
ic->imxtm = imxtm;
}
printk(KERN_INFO, "_mxc_timer_init RETURN SUCCESS\n");
return 0;
}
void __init mxc_timer_init(unsigned long pbase, int irq, enum imx_gpt_type type)
{
struct imx_timer *imxtm;
printk(KERN_INFO, "gpt mxc_timer_init HEAD\n");
imxtm = kzalloc(sizeof(*imxtm), GFP_KERNEL);
BUG_ON(!imxtm);
imxtm->clk_per = clk_get_sys("imx-gpt.0", "per");
imxtm->clk_ipg = clk_get_sys("imx-gpt.0", "ipg");
imxtm->base = ioremap(pbase, SZ_4K);
BUG_ON(!imxtm->base);
imxtm->type = type;
imxtm->irq = irq;
_mxc_timer_init(imxtm);
}
/*
* a platform driver is needed in order to acquire pinmux
* for input capture pins. The probe call is also useful
* for setting up the input capture channel structures.
*/
static int mxc_timer_probe(struct platform_device *pdev)
{
struct icap_channel *ic;
int i;
dev_info(&pdev->dev, "GPT Timer Probe Function head\n");
/* setup the input capture channels */
for (i = 0; i < 2; i++) {
ic = &icap_channel_arr[i];
ic->chan = i;
if (i == 0) {
ic->cnt_reg = V2_TCAP1;
ic->irqen_bit = V2_IR_IF1;
ic->status_bit = V2_TSTAT_IF1;
ic->mode_bit = V2_TCTL_IM1_BIT;
} else {
ic->cnt_reg = V2_TCAP2;
ic->irqen_bit = V2_IR_IF2;
ic->status_bit = V2_TSTAT_IF2;
ic->mode_bit = V2_TCTL_IM2_BIT;
}
}
dev_info(&pdev->dev, "GPT Timer Probe Function end\n");
return 0;
}
static int mxc_timer_remove(struct platform_device *pdev)
{
return 0;
}
static const struct of_device_id timer_of_match[] = {
{ .compatible = "fsl,imx1-gpt" },
{ .compatible = "fsl,imx21-gpt" },
{ .compatible = "fsl,imx27-gpt" },
{ .compatible = "fsl,imx31-gpt" },
{ .compatible = "fsl,imx25-gpt" },
{ .compatible = "fsl,imx50-gpt" },
{ .compatible = "fsl,imx51-gpt" },
{ .compatible = "fsl,imx53-gpt" },
{ .compatible = "fsl,imx6q-gpt" },
{ .compatible = "fsl,imx6dl-gpt" },
{ .compatible = "fsl,imx6sl-gpt" },
{ .compatible = "fsl,imx6sx-gpt" },
{ .compatible = "fsl,imx8mm-gpt" },
{ },
};
MODULE_DEVICE_TABLE(of, timer_of_match);
static struct platform_driver mxc_timer_pdrv = {
.probe = mxc_timer_probe,
.remove = mxc_timer_remove,
.driver = {
.name = "mxc-timer",
.owner = THIS_MODULE,
.of_match_table = timer_of_match,
},
};
module_platform_driver(mxc_timer_pdrv);
static int __init mxc_timer_init_dt(struct device_node *np, enum imx_gpt_type type)
{
struct imx_timer *imxtm;
static int initialized;
int ret;
printk(KERN_INFO, "gpt mxc_timer_init_dt head\n");
/* Support one instance only */
if (initialized)
return 0;
imxtm = kzalloc(sizeof(*imxtm), GFP_KERNEL);
if (!imxtm)
return -ENOMEM;
imxtm->base = of_iomap(np, 0);
if (!imxtm->base)
return -ENXIO;
imxtm->irq = irq_of_parse_and_map(np, 0);
if (imxtm->irq <= 0)
return -EINVAL;
imxtm->clk_ipg = of_clk_get_by_name(np, "ipg");
/* Try osc_per first, and fall back to per otherwise */
imxtm->clk_per = of_clk_get_by_name(np, "osc_per");
if (IS_ERR(imxtm->clk_per))
imxtm->clk_per = of_clk_get_by_name(np, "per");
imxtm->type = type;
printk(KERN_INFO, "gpt mxc_timer_init_dt line 883\n");
ret = _mxc_timer_init(imxtm);
if (ret)
return ret;
initialized = 1;
printk(KERN_INFO, "gpt mxc_timer_init_dt end\n");
return 0;
}
static int __init imx1_timer_init_dt(struct device_node *np)
{
return mxc_timer_init_dt(np, GPT_TYPE_IMX1);
}
static int __init imx21_timer_init_dt(struct device_node *np)
{
return mxc_timer_init_dt(np, GPT_TYPE_IMX21);
}
static int __init imx31_timer_init_dt(struct device_node *np)
{
enum imx_gpt_type type = GPT_TYPE_IMX31;
printk(KERN_INFO "gpt imx31_timer_init_dt HEAD");
/*
* We were using the same compatible string for i.MX6Q/D and i.MX6DL/S
* GPT device, while they actually have different programming model.
* This is a workaround to keep the existing i.MX6DL/S DTBs continue
* working with the new kernel.
*/
if (of_machine_is_compatible("fsl,imx6dl"))
type = GPT_TYPE_IMX6DL;
return mxc_timer_init_dt(np, type);
}
static int __init imx6dl_timer_init_dt(struct device_node *np)
{
printk(KERN_INFO "gpt imx6dl_timer_init_dt HEAD");
return mxc_timer_init_dt(np, GPT_TYPE_IMX6DL); //This function must execute to get clock
}
TIMER_OF_DECLARE(imx1_timer, "fsl,imx1-gpt", imx1_timer_init_dt);
TIMER_OF_DECLARE(imx21_timer, "fsl,imx21-gpt", imx21_timer_init_dt);
TIMER_OF_DECLARE(imx27_timer, "fsl,imx27-gpt", imx21_timer_init_dt);
TIMER_OF_DECLARE(imx31_timer, "fsl,imx31-gpt", imx31_timer_init_dt);
TIMER_OF_DECLARE(imx25_timer, "fsl,imx25-gpt", imx31_timer_init_dt);
TIMER_OF_DECLARE(imx50_timer, "fsl,imx50-gpt", imx31_timer_init_dt);
TIMER_OF_DECLARE(imx51_timer, "fsl,imx51-gpt", imx31_timer_init_dt);
TIMER_OF_DECLARE(imx53_timer, "fsl,imx53-gpt", imx31_timer_init_dt);
TIMER_OF_DECLARE(imx6q_timer, "fsl,imx6q-gpt", imx31_timer_init_dt);
TIMER_OF_DECLARE(imx6dl_timer, "fsl,imx6dl-gpt", imx6dl_timer_init_dt);
TIMER_OF_DECLARE(imx6sl_timer, "fsl,imx6sl-gpt", imx6dl_timer_init_dt);
TIMER_OF_DECLARE(imx6sx_timer, "fsl,imx6sx-gpt", imx6dl_timer_init_dt);
TIMER_OF_DECLARE(imx8mm_timer, "fsl,imx8mm-gpt", imx6dl_timer_init_dt); //This line added after applying patch
I will be very grateful for your help
I am currently refactoring a driver for the AMD Sensor Fusion Hub.
The original driver can be found here.
When sending commands to the device, the chip takes some time to process the request. I wasusing a hack using msleep to wait for the device to respond. However I'd like to cleanly implement IRQ handling. As a template I looked into the implementation of drivers/i2c/busses/i2c-amd-mp2-pci.c and came up with the following stub interrupt handling.
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* AMD Sensor Fusion Hub (SFH) PCIe driver
*
* Authors: Shyam Sundar S K <Shyam-sundar.S-k#amd.com>
* Nehal Bakulchandra Shah <Nehal-bakulchandra.Shah#amd.com>
* Richard Neumann <mail#richard-neumann.de>
*/
#include <linux/bitops.h>
#include <linux/dma-mapping.h>
#include <linux/interrupt.h>
#include <linux/io-64-nonatomic-lo-hi.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/pm_runtime.h>
#include <linux/types.h>
#include "amd-sfh-pci.h"
/**
* amd_sfh_get_sensor_mask - Returns the sensors mask.
* #pci_dev: The Sensor Fusion Hub PCI device
*
* Returns an unsigned integer representing the bitmask to match
* the sensors connected to the Sensor Fusion Hub.
*/
int amd_sfh_get_sensor_mask(struct pci_dev *pci_dev)
{
int sensor_mask;
struct amd_sfh_dev *sfh_dev = pci_get_drvdata(pci_dev);
sensor_mask = readl(sfh_dev->mmio + AMD_SFH_SENSOR_MASK);
/* Correct bit shift in firmware register */
sensor_mask = sensor_mask >> 4;
if (!sensor_mask)
dev_err(&pci_dev->dev, "no sensors marked active on device\n");
return sensor_mask;
}
EXPORT_SYMBOL_GPL(amd_sfh_get_sensor_mask);
/**
* amd_sfh_start_sensor- Starts the respective sensor.
* #pci_dev: The Sensor Fusion Hub PCI device
* #sensor_idx: The sensor's index
* #dma_handle: The DMA handle
*/
void amd_sfh_start_sensor(struct pci_dev *pci_dev, enum sensor_idx sensor_idx,
dma_addr_t dma_handle)
{
struct amd_sfh_dev *sfh_dev = pci_get_drvdata(pci_dev);
union amd_sfh_cmd command;
union amd_sfh_parm parameter;
command.ul = 0;
command.s.cmd_id = enable_sensor;
command.s.period = PERIOD;
command.s.sensor_id = sensor_idx;
parameter.ul = 0;
parameter.s.buffer_layout = 1;
parameter.s.buffer_length = 16;
writeq(dma_handle, sfh_dev->mmio + AMD_SFH_ADDR);
writel(parameter.ul, sfh_dev->mmio + AMD_SFH_PARM);
writel(command.ul, sfh_dev->mmio + AMD_SFH_CMD);
}
EXPORT_SYMBOL_GPL(amd_sfh_start_sensor);
/**
* amd_sfh_stop_sensor- Stops the respective sensor.
* #pci_dev: The Sensor Fusion Hub PCI device
* #sensor_idx: The sensor's index
*/
void amd_sfh_stop_sensor(struct pci_dev *pci_dev, enum sensor_idx sensor_idx)
{
struct amd_sfh_dev *sfh_dev = pci_get_drvdata(pci_dev);
union amd_sfh_cmd command;
command.ul = 0;
command.s.cmd_id = disable_sensor;
command.s.period = 0;
command.s.sensor_id = sensor_idx;
writeq(0x0, sfh_dev->mmio + AMD_SFH_ADDR);
writel(command.ul, sfh_dev->mmio + AMD_SFH_CMD);
}
EXPORT_SYMBOL_GPL(amd_sfh_stop_sensor);
/**
* amd_sfh_stop_all_sensors- Stops all sensors on the SFH.
* #pci_dev: The Sensor Fusion Hub PCI device
*/
static void amd_sfh_stop_all_sensors(struct pci_dev *pci_dev)
{
struct amd_sfh_dev *sfh_dev = pci_get_drvdata(pci_dev);
union amd_sfh_cmd command;
command.ul = 0;
command.s.cmd_id = stop_all_sensors;
command.s.period = 0;
command.s.sensor_id = 0;
writel(command.ul, sfh_dev->mmio + AMD_SFH_CMD);
}
/**
* amd_sfh_irq_isr - IRQ handler
* #irq: The IRQ number received
* #dev: The underlying device
*/
static irqreturn_t amd_sfh_irq_isr(int irq, void *dev)
{
struct amd_sfh_dev *sfh_dev = dev;
dev_info(&sfh_dev->pci_dev->dev, "got IRQ: %d\n", irq);
return IRQ_NONE;
}
/**
* amd_sfh_pci_init - Initializes the PCI device
* #sfh_dev: The device data
* #pci_dev: The PCI device
*/
static int amd_sfh_pci_init(struct amd_sfh_dev *sfh_dev,
struct pci_dev *pci_dev)
{
int rc;
pci_set_drvdata(pci_dev, sfh_dev);
rc = pcim_enable_device(pci_dev);
if (rc)
goto err_pci_enable;
rc = pcim_iomap_regions(pci_dev, BIT(2), pci_name(pci_dev));
if (rc)
goto err_pci_enable;
sfh_dev->pci_dev = pci_dev;
sfh_dev->mmio = pcim_iomap_table(pci_dev)[2];
pci_set_master(pci_dev);
rc = pci_set_dma_mask(pci_dev, DMA_BIT_MASK(64));
if (rc) {
rc = pci_set_dma_mask(pci_dev, DMA_BIT_MASK(32));
if (rc)
goto err_dma_mask;
}
/* Set up intx irq */
writel(0, sfh_dev->mmio + AMD_P2C_MSG_INTEN);
pci_intx(pci_dev, 1);
dev_info(&pci_dev->dev, "available interrupt: %d\n", pci_dev->irq);
rc = devm_request_irq(&pci_dev->dev, pci_dev->irq, amd_sfh_irq_isr,
0, dev_name(&pci_dev->dev), sfh_dev);
if (rc)
dev_err(&pci_dev->dev, "Failure requesting irq %i: %d\n",
pci_dev->irq, rc);
return rc;
err_dma_mask:
pci_clear_master(pci_dev);
err_pci_enable:
return rc;
}
static int amd_sfh_pci_probe(struct pci_dev *pci_dev,
const struct pci_device_id *id)
{
int rc;
struct amd_sfh_dev *sfh_dev;
sfh_dev = devm_kzalloc(&pci_dev->dev, sizeof(*sfh_dev), GFP_KERNEL);
if (!sfh_dev)
return -ENOMEM;
rc = amd_sfh_pci_init(sfh_dev, pci_dev);
if (rc)
return rc;
pm_runtime_set_autosuspend_delay(&pci_dev->dev, 1000);
pm_runtime_use_autosuspend(&pci_dev->dev);
pm_runtime_put_autosuspend(&pci_dev->dev);
pm_runtime_allow(&pci_dev->dev);
dev_info(&pci_dev->dev, "SFH device registered.\n");
return 0;
}
static void amd_sfh_pci_remove(struct pci_dev *pci_dev)
{
struct amd_sfh_dev *sfh_dev = pci_get_drvdata(pci_dev);
amd_sfh_stop_all_sensors(sfh_dev->pci_dev);
pm_runtime_forbid(&pci_dev->dev);
pm_runtime_get_noresume(&pci_dev->dev);
pci_intx(pci_dev, 0);
pci_clear_master(pci_dev);
}
static const struct pci_device_id amd_sfh_pci_tbl[] = {
{PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_SFH)},
{0}
};
MODULE_DEVICE_TABLE(pci, amd_sfh_pci_tbl);
static struct pci_driver amd_sfh_pci_driver = {
.name = "amd-sfh-pci",
.id_table = amd_sfh_pci_tbl,
.probe = amd_sfh_pci_probe,
.remove = amd_sfh_pci_remove,
};
module_pci_driver(amd_sfh_pci_driver);
/**
* amd_sfh_find_device - Returns the first best AMD SFH device.
*/
struct amd_sfh_dev *amd_sfh_find_device(void)
{
struct device *dev;
struct pci_dev *pci_dev;
dev = driver_find_next_device(&amd_sfh_pci_driver.driver, NULL);
if (!dev)
return NULL;
pci_dev = to_pci_dev(dev);
return pci_get_drvdata(pci_dev);
}
EXPORT_SYMBOL_GPL(amd_sfh_find_device);
MODULE_DESCRIPTION("AMD(R) Sensor Fusion Hub PCI driver");
MODULE_AUTHOR("Shyam Sundar S K <Shyam-sundar.S-k#amd.com>");
MODULE_AUTHOR("Nehal Bakulchandra Shah <Nehal-bakulchandra.Shah#amd.com>");
MODULE_AUTHOR("Richard Neumann <mail#richard-neumann.de>");
MODULE_LICENSE("Dual BSD/GPL");
However the dev_info from amd_sfh_irq_isr is never logged, so I suspect that the IRQ is never triggered when writing to the device registers. What can be the reason for that and what can I do to correctly implement the IRQ handling?
PS
dmesg output:
[ 2272.642762] amd-sfh-pci 0000:04:00.7: available interrupt: 56
[ 2272.642840] amd-sfh-pci 0000:04:00.7: SFH device registered.
Update
The device seems to support MSI:
04:00.7 Non-VGA unclassified device [0000]: Advanced Micro Devices, Inc. [AMD] Raven/Raven2/Renoir Sensor Fusion Hub [1022:15e4]
Subsystem: Hewlett-Packard Company Raven/Raven2/Renoir Sensor Fusion Hub [103c:8496]
Flags: bus master, fast devsel, latency 0, IRQ 56
Memory at fc800000 (32-bit, non-prefetchable) [size=1M]
Memory at fcc8c000 (32-bit, non-prefetchable) [size=8K]
Capabilities: [48] Vendor Specific Information: Len=08 <?>
Capabilities: [50] Power Management version 3
Capabilities: [64] Express Endpoint, MSI 00
Capabilities: [a0] MSI: Enable- Count=1/2 Maskable- 64bit+
Capabilities: [c0] MSI-X: Enable- Count=2 Masked-
Capabilities: [100] Vendor Specific Information: ID=0001 Rev=1 Len=010 <?>
Kernel modules: amd_sfh_pci
But the interrupts are still not received / handled:
static void amd_sfh_debug(struct pci_dev *pci_dev)
{
bool msi;
msi = pci_dev_msi_enabled(pci_dev);
pci_info(pci_dev, "MSI: %s\n", msi ? "true" : "false");
pci_info(pci_dev, "No MSI: %s\n", pci_dev->no_msi ? "true" : "false");
}
/**
* amd_sfh_handle_irq - Handles IRQs.
* #irq: The interrupt request to be handled
* #dev: The driver data
*
* Returns an appropriate IRQ return type.
*/
static irqreturn_t amd_sfh_handle_irq(int irq, void *dev)
{
struct amd_sfh_dev *privdata = dev;
pci_info(privdata->pci_dev, "got IRQ: %d\n", irq);
return IRQ_NONE;
}
static int amd_sfh_setup_irq(struct amd_sfh_dev *privdata)
{
int rc, vecs, irq;
struct pci_dev *pci_dev = privdata->pci_dev;
vecs = pci_alloc_irq_vectors(pci_dev, 1, 3, PCI_IRQ_ALL_TYPES);
if (vecs < 0)
return vecs;
pci_info(pci_dev, "allocated %d IRQ vectors\n", vecs);
for (irq = 0; irq < vecs; irq++) {
pci_info(pci_dev, "requesting IRQ: %d\n", irq);
rc = devm_request_irq(&pci_dev->dev,
pci_irq_vector(pci_dev, irq),
amd_sfh_handle_irq, 0, "sfh-irq",
privdata);
if (rc) {
pci_err(pci_dev, "failed to get IRQ\n");
goto free_irq_vectors;
}
}
return 0;
free_irq_vectors:
pci_free_irq_vectors(pci_dev);
return rc;
}
static int amd_sfh_pci_init(struct amd_sfh_dev *privdata,
struct pci_dev *pci_dev)
{
int rc;
pci_set_drvdata(pci_dev, privdata);
rc = pcim_enable_device(pci_dev);
if (rc)
return rc;
rc = pcim_iomap_regions(pci_dev, BIT(2), pci_name(pci_dev));
if (rc)
return rc;
privdata->pci_dev = pci_dev;
privdata->mmio = pcim_iomap_table(pci_dev)[2];
pci_set_master(pci_dev);
rc = pci_set_dma_mask(pci_dev, DMA_BIT_MASK(64));
if (rc) {
rc = pci_set_dma_mask(pci_dev, DMA_BIT_MASK(32));
if (rc)
goto clear_master;
}
/* Setup IRQ */
amd_sfh_debug(pci_dev);
rc = amd_sfh_setup_irq(privdata);
if (rc)
goto clear_master;
amd_sfh_debug(pci_dev);
/* End of IRQ setup */
pci_info(pci_dev, "AMD Sensor Fusion Hub device initialized\n");
return 0;
clear_master:
pci_clear_master(pci_dev);
return rc;
}
dmesg:
[ 6.954524] amd-sfh-pci 0000:04:00.7: enabling device (0000 -> 0002)
[ 6.954641] amd-sfh-pci 0000:04:00.7: MSI: false
[ 6.954642] amd-sfh-pci 0000:04:00.7: No MSI: false
[ 6.954791] amd-sfh-pci 0000:04:00.7: allocated 2 IRQ vectors
[ 6.954792] amd-sfh-pci 0000:04:00.7: requesting IRQ: 0
[ 6.954825] amd-sfh-pci 0000:04:00.7: requesting IRQ: 1
[ 6.954860] amd-sfh-pci 0000:04:00.7: MSI: true
[ 6.954861] amd-sfh-pci 0000:04:00.7: No MSI: false
[ 6.954861] amd-sfh-pci 0000:04:00.7: AMD Sensor Fusion Hub device initialized
[ 6.969691] amd-sfh-pci 0000:04:00.7: [Firmware Bug]: No sensors marked active!
[ 6.971265] amd-sfh-pci 0000:04:00.7: sensor mask: 0x000001
[ 7.548189] hid-generic 0018:03FE:0001.0001: hidraw0: I2C HID v0.01 Device [amd-sfh-accel] on
The original documentation from AMD mentiones interrupts but is not too specific as to how they are being generated.
By try-and-error I found that I needed to set the P2C register 0x10690 to 1 in order to enable interrupts on the device. Whith this set, the device is flooding the driver with interrupts. I'm still figuring out how to make the device generate interrupts only on actual write events to the C2P registers.
Okay, I found out, how:
https://github.com/conqp/linux/blob/5ba797452a794100d65d103e8eb53f64ae14d1d0/drivers/hid/amd-sfh-hid/amd-sfh-pci.c#L301
Following the book ldd3 (- Linux Device Drivers 3 ed.) and using, also, source code files available here (as suggested by another stackoverflow's user here), I am able to compile the scull device module and load it on my Linux-based OS. For more details:
$ insmod scull.ko
$ lsmod
Module Size Used by
scull 20480 0
$ dmesg | tail -...
[...] scullsingle registered at f300008
[...] sculluid registered at f300009
[...] scullwuid registered at f30000a
[...] sullpriv registered at f30000b
However, in the /dev/ directory, there is NOT the scull0 device entry as I was expecting. So I cannot use it.
In addition, when executing:
$ rmmod scull
The system displays this error:
rmmod: ERROR: ../libkmod/libkmod.c:514 lookup_builtin_file() could not open builtin file '/lib/modules/4.9.0/modules.builtin.bin'
The funny phenomena is that now, executing lsmod, the device seems disappeared and before, as you can see from the snipped, it was present.
MORE INFO (may be they can be useful in some way):
using a 4.9.0 kernel version
currently, I am cross-compiling the module
gcc used: aarch64-Linux-gnu-gcc
ARCH=arm64
snipped code of scull:
CODE:
/*
* main.c -- the bare scull char module
*
* Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet
* Copyright (C) 2001 O'Reilly & Associates
*
* The source code in this file can be freely used, adapted,
* and redistributed in source or binary form, so long as an
* acknowledgment appears in derived source files. The citation
* should list that the code comes from the book "Linux Device
* Drivers" by Alessandro Rubini and Jonathan Corbet, published
* by O'Reilly & Associates. No warranty is attached;
* we cannot take responsibility for errors or fitness for use.
*
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/kernel.h> /* printk() */
#include <linux/slab.h> /* kmalloc() */
#include <linux/fs.h> /* everything... */
#include <linux/errno.h> /* error codes */
#include <linux/types.h> /* size_t */
#include <linux/proc_fs.h>
#include <linux/fcntl.h> /* O_ACCMODE */
#include <linux/seq_file.h>
#include <linux/cdev.h>
#include <asm/uaccess.h> /* copy_*_user */
#include "scull.h" /* local definitions */
/*
* Our parameters which can be set at load time.
*/
int scull_major = SCULL_MAJOR;
int scull_minor = 0;
int scull_nr_devs = SCULL_NR_DEVS; /* number of bare scull devices */
int scull_quantum = SCULL_QUANTUM;
int scull_qset = SCULL_QSET;
module_param(scull_major, int, S_IRUGO);
module_param(scull_minor, int, S_IRUGO);
module_param(scull_nr_devs, int, S_IRUGO);
module_param(scull_quantum, int, S_IRUGO);
module_param(scull_qset, int, S_IRUGO);
MODULE_AUTHOR("Alessandro Rubini, Jonathan Corbet");
MODULE_LICENSE("Dual BSD/GPL");
struct scull_dev *scull_devices; /* allocated in scull_init_module */
/*
* Empty out the scull device; must be called with the device
* semaphore held.
*/
int scull_trim(struct scull_dev *dev)
{
struct scull_qset *next, *dptr;
int qset = dev->qset; /* "dev" is not-null */
int i;
for (dptr = dev->data; dptr; dptr = next) { /* all the list items */
if (dptr->data) {
for (i = 0; i < qset; i++)
kfree(dptr->data[i]);
kfree(dptr->data);
dptr->data = NULL;
}
next = dptr->next;
kfree(dptr);
}
dev->size = 0;
dev->quantum = scull_quantum;
dev->qset = scull_qset;
dev->data = NULL;
return 0;
}
#ifdef SCULL_DEBUG /* use proc only if debugging */
/*
* The proc filesystem: function to read and entry
*/
int scull_read_procmem(struct seq_file *s, void *v)
{
int i, j;
int limit = s->size - 80; /* Don't print more than this */
for (i = 0; i < scull_nr_devs && s->count <= limit; i++) {
struct scull_dev *d = &scull_devices[i];
struct scull_qset *qs = d->data;
if (down_interruptible(&d->sem))
return -ERESTARTSYS;
seq_printf(s,"\nDevice %i: qset %i, q %i, sz %li\n",
i, d->qset, d->quantum, d->size);
for (; qs && s->count <= limit; qs = qs->next) { /* scan the list */
seq_printf(s, " item at %p, qset at %p\n",
qs, qs->data);
if (qs->data && !qs->next) /* dump only the last item */
for (j = 0; j < d->qset; j++) {
if (qs->data[j])
seq_printf(s, " % 4i: %8p\n",
j, qs->data[j]);
}
}
up(&scull_devices[i].sem);
}
return 0;
}
/*
* Here are our sequence iteration methods. Our "position" is
* simply the device number.
*/
static void *scull_seq_start(struct seq_file *s, loff_t *pos)
{
if (*pos >= scull_nr_devs)
return NULL; /* No more to read */
return scull_devices + *pos;
}
static void *scull_seq_next(struct seq_file *s, void *v, loff_t *pos)
{
(*pos)++;
if (*pos >= scull_nr_devs)
return NULL;
return scull_devices + *pos;
}
static void scull_seq_stop(struct seq_file *s, void *v)
{
/* Actually, there's nothing to do here */
}
static int scull_seq_show(struct seq_file *s, void *v)
{
struct scull_dev *dev = (struct scull_dev *) v;
struct scull_qset *d;
int i;
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
seq_printf(s, "\nDevice %i: qset %i, q %i, sz %li\n",
(int) (dev - scull_devices), dev->qset,
dev->quantum, dev->size);
for (d = dev->data; d; d = d->next) { /* scan the list */
seq_printf(s, " item at %p, qset at %p\n", d, d->data);
if (d->data && !d->next) /* dump only the last item */
for (i = 0; i < dev->qset; i++) {
if (d->data[i])
seq_printf(s, " % 4i: %8p\n",
i, d->data[i]);
}
}
up(&dev->sem);
return 0;
}
/*
* Tie the sequence operators up.
*/
static struct seq_operations scull_seq_ops = {
.start = scull_seq_start,
.next = scull_seq_next,
.stop = scull_seq_stop,
.show = scull_seq_show
};
/*
* Now to implement the /proc files we need only make an open
* method which sets up the sequence operators.
*/
static int scullmem_proc_open(struct inode *inode, struct file *file)
{
return single_open(file, scull_read_procmem, NULL);
}
static int scullseq_proc_open(struct inode *inode, struct file *file)
{
return seq_open(file, &scull_seq_ops);
}
/*
* Create a set of file operations for our proc files.
*/
static struct file_operations scullmem_proc_ops = {
.owner = THIS_MODULE,
.open = scullmem_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release
};
static struct file_operations scullseq_proc_ops = {
.owner = THIS_MODULE,
.open = scullseq_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release
};
/*
* Actually create (and remove) the /proc file(s).
*/
static void scull_create_proc(void)
{
proc_create_data("scullmem", 0 /* default mode */,
NULL /* parent dir */, &scullmem_proc_ops,
NULL /* client data */);
proc_create("scullseq", 0, NULL, &scullseq_proc_ops);
}
static void scull_remove_proc(void)
{
/* no problem if it was not registered */
remove_proc_entry("scullmem", NULL /* parent dir */);
remove_proc_entry("scullseq", NULL);
}
#endif /* SCULL_DEBUG */
/*
* Open and close
*/
int scull_open(struct inode *inode, struct file *filp)
{
struct scull_dev *dev; /* device information */
dev = container_of(inode->i_cdev, struct scull_dev, cdev);
filp->private_data = dev; /* for other methods */
/* now trim to 0 the length of the device if open was write-only */
if ( (filp->f_flags & O_ACCMODE) == O_WRONLY) {
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
scull_trim(dev); /* ignore errors */
up(&dev->sem);
}
return 0; /* success */
}
int scull_release(struct inode *inode, struct file *filp)
{
return 0;
}
/*
* Follow the list
*/
struct scull_qset *scull_follow(struct scull_dev *dev, int n)
{
struct scull_qset *qs = dev->data;
/* Allocate first qset explicitly if need be */
if (! qs) {
qs = dev->data = kmalloc(sizeof(struct scull_qset), GFP_KERNEL);
if (qs == NULL)
return NULL; /* Never mind */
memset(qs, 0, sizeof(struct scull_qset));
}
/* Then follow the list */
while (n--) {
if (!qs->next) {
qs->next = kmalloc(sizeof(struct scull_qset), GFP_KERNEL);
if (qs->next == NULL)
return NULL; /* Never mind */
memset(qs->next, 0, sizeof(struct scull_qset));
}
qs = qs->next;
continue;
}
return qs;
}
/*
* Data management: read and write
*/
ssize_t scull_read(struct file *filp, char __user *buf, size_t count,
loff_t *f_pos)
{
struct scull_dev *dev = filp->private_data;
struct scull_qset *dptr; /* the first listitem */
int quantum = dev->quantum, qset = dev->qset;
int itemsize = quantum * qset; /* how many bytes in the listitem */
int item, s_pos, q_pos, rest;
ssize_t retval = 0;
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
if (*f_pos >= dev->size)
goto out;
if (*f_pos + count > dev->size)
count = dev->size - *f_pos;
/* find listitem, qset index, and offset in the quantum */
item = (long)*f_pos / itemsize;
rest = (long)*f_pos % itemsize;
s_pos = rest / quantum; q_pos = rest % quantum;
/* follow the list up to the right position (defined elsewhere) */
dptr = scull_follow(dev, item);
if (dptr == NULL || !dptr->data || ! dptr->data[s_pos])
goto out; /* don't fill holes */
/* read only up to the end of this quantum */
if (count > quantum - q_pos)
count = quantum - q_pos;
if (copy_to_user(buf, dptr->data[s_pos] + q_pos, count)) {
retval = -EFAULT;
goto out;
}
*f_pos += count;
retval = count;
out:
up(&dev->sem);
return retval;
}
ssize_t scull_write(struct file *filp, const char __user *buf, size_t count,
loff_t *f_pos)
{
struct scull_dev *dev = filp->private_data;
struct scull_qset *dptr;
int quantum = dev->quantum, qset = dev->qset;
int itemsize = quantum * qset;
int item, s_pos, q_pos, rest;
ssize_t retval = -ENOMEM; /* value used in "goto out" statements */
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
/* find listitem, qset index and offset in the quantum */
item = (long)*f_pos / itemsize;
rest = (long)*f_pos % itemsize;
s_pos = rest / quantum; q_pos = rest % quantum;
/* follow the list up to the right position */
dptr = scull_follow(dev, item);
if (dptr == NULL)
goto out;
if (!dptr->data) {
dptr->data = kmalloc(qset * sizeof(char *), GFP_KERNEL);
if (!dptr->data)
goto out;
memset(dptr->data, 0, qset * sizeof(char *));
}
if (!dptr->data[s_pos]) {
dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL);
if (!dptr->data[s_pos])
goto out;
}
/* write only up to the end of this quantum */
if (count > quantum - q_pos)
count = quantum - q_pos;
if (copy_from_user(dptr->data[s_pos]+q_pos, buf, count)) {
retval = -EFAULT;
goto out;
}
*f_pos += count;
retval = count;
/* update the size */
if (dev->size < *f_pos)
dev->size = *f_pos;
out:
up(&dev->sem);
return retval;
}
/*
* The ioctl() implementation
*/
long scull_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int err = 0, tmp;
int retval = 0;
/*
* extract the type and number bitfields, and don't decode
* wrong cmds: return ENOTTY (inappropriate ioctl) before access_ok()
*/
if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC) return -ENOTTY;
if (_IOC_NR(cmd) > SCULL_IOC_MAXNR) return -ENOTTY;
/*
* the direction is a bitmask, and VERIFY_WRITE catches R/W
* transfers. `Type' is user-oriented, while
* access_ok is kernel-oriented, so the concept of "read" and
* "write" is reversed
*/
if (_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
else if (_IOC_DIR(cmd) & _IOC_WRITE)
err = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
if (err) return -EFAULT;
switch(cmd) {
case SCULL_IOCRESET:
scull_quantum = SCULL_QUANTUM;
scull_qset = SCULL_QSET;
break;
case SCULL_IOCSQUANTUM: /* Set: arg points to the value */
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
retval = __get_user(scull_quantum, (int __user *)arg);
break;
case SCULL_IOCTQUANTUM: /* Tell: arg is the value */
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
scull_quantum = arg;
break;
case SCULL_IOCGQUANTUM: /* Get: arg is pointer to result */
retval = __put_user(scull_quantum, (int __user *)arg);
break;
case SCULL_IOCQQUANTUM: /* Query: return it (it's positive) */
return scull_quantum;
case SCULL_IOCXQUANTUM: /* eXchange: use arg as pointer */
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
tmp = scull_quantum;
retval = __get_user(scull_quantum, (int __user *)arg);
if (retval == 0)
retval = __put_user(tmp, (int __user *)arg);
break;
case SCULL_IOCHQUANTUM: /* sHift: like Tell + Query */
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
tmp = scull_quantum;
scull_quantum = arg;
return tmp;
case SCULL_IOCSQSET:
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
retval = __get_user(scull_qset, (int __user *)arg);
break;
case SCULL_IOCTQSET:
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
scull_qset = arg;
break;
case SCULL_IOCGQSET:
retval = __put_user(scull_qset, (int __user *)arg);
break;
case SCULL_IOCQQSET:
return scull_qset;
case SCULL_IOCXQSET:
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
tmp = scull_qset;
retval = __get_user(scull_qset, (int __user *)arg);
if (retval == 0)
retval = put_user(tmp, (int __user *)arg);
break;
case SCULL_IOCHQSET:
if (! capable (CAP_SYS_ADMIN))
return -EPERM;
tmp = scull_qset;
scull_qset = arg;
return tmp;
/*
* The following two change the buffer size for scullpipe.
* The scullpipe device uses this same ioctl method, just to
* write less code. Actually, it's the same driver, isn't it?
*/
case SCULL_P_IOCTSIZE:
scull_p_buffer = arg;
break;
case SCULL_P_IOCQSIZE:
return scull_p_buffer;
default: /* redundant, as cmd was checked against MAXNR */
return -ENOTTY;
}
return retval;
}
/*
* The "extended" operations -- only seek
*/
loff_t scull_llseek(struct file *filp, loff_t off, int whence)
{
struct scull_dev *dev = filp->private_data;
loff_t newpos;
switch(whence) {
case 0: /* SEEK_SET */
newpos = off;
break;
case 1: /* SEEK_CUR */
newpos = filp->f_pos + off;
break;
case 2: /* SEEK_END */
newpos = dev->size + off;
break;
default: /* can't happen */
return -EINVAL;
}
if (newpos < 0) return -EINVAL;
filp->f_pos = newpos;
return newpos;
}
struct file_operations scull_fops = {
.owner = THIS_MODULE,
.llseek = scull_llseek,
.read = scull_read,
.write = scull_write,
.unlocked_ioctl = scull_ioctl,
.open = scull_open,
.release = scull_release,
};
/*
* Finally, the module stuff
*/
/*
* The cleanup function is used to handle initialization failures as well.
* Thefore, it must be careful to work correctly even if some of the items
* have not been initialized
*/
void scull_cleanup_module(void)
{
int i;
dev_t devno = MKDEV(scull_major, scull_minor);
/* Get rid of our char dev entries */
if (scull_devices) {
for (i = 0; i < scull_nr_devs; i++) {
scull_trim(scull_devices + i);
cdev_del(&scull_devices[i].cdev);
}
kfree(scull_devices);
}
#ifdef SCULL_DEBUG /* use proc only if debugging */
scull_remove_proc();
#endif
/* cleanup_module is never called if registering failed */
unregister_chrdev_region(devno, scull_nr_devs);
/* and call the cleanup functions for friend devices */
scull_p_cleanup();
scull_access_cleanup();
}
/*
* Set up the char_dev structure for this device.
*/
static void scull_setup_cdev(struct scull_dev *dev, int index)
{
int err, devno = MKDEV(scull_major, scull_minor + index);
cdev_init(&dev->cdev, &scull_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &scull_fops;
err = cdev_add (&dev->cdev, devno, 1);
/* Fail gracefully if need be */
if (err)
printk(KERN_NOTICE "Error %d adding scull%d", err, index);
}
int scull_init_module(void)
{
int result, i;
dev_t dev = 0;
/*
* Get a range of minor numbers to work with, asking for a dynamic
* major unless directed otherwise at load time.
*/
if (scull_major) {
dev = MKDEV(scull_major, scull_minor);
result = register_chrdev_region(dev, scull_nr_devs, "scull");
} else {
result = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs,
"scull");
scull_major = MAJOR(dev);
}
if (result < 0) {
printk(KERN_WARNING "scull: can't get major %d\n", scull_major);
return result;
}
/*
* allocate the devices -- we can't have them static, as the number
* can be specified at load time
*/
scull_devices = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL);
if (!scull_devices) {
result = -ENOMEM;
goto fail; /* Make this more graceful */
}
memset(scull_devices, 0, scull_nr_devs * sizeof(struct scull_dev));
/* Initialize each device. */
for (i = 0; i < scull_nr_devs; i++) {
scull_devices[i].quantum = scull_quantum;
scull_devices[i].qset = scull_qset;
sema_init(&scull_devices[i].sem, 1);
scull_setup_cdev(&scull_devices[i], i);
}
/* At this point call the init function for any friend device */
dev = MKDEV(scull_major, scull_minor + scull_nr_devs);
dev += scull_p_init(dev);
dev += scull_access_init(dev);
#ifdef SCULL_DEBUG /* only when debugging */
scull_create_proc();
#endif
return 0; /* succeed */
fail:
scull_cleanup_module();
return result;
}
module_init(scull_init_module);
module_exit(scull_cleanup_module);
SOLUTION:
Of course, I was doing the things in the wrong way: in order to load CORRECTLY the scull device module, in the book's source code and, also, in the other link of the question, there is load_scull script that does everything for you.
# source scull_load
# lsmod
Module Size Used by
scull 20480 0
# ls -l /dev/ | grep scull
lrwxrwxrwx 1 root root 6 Dec 19 15:00 scull -> scull0
crw-rw-r-- 1 root staff 243, 0 Dec 19 15:00 scull0
crw-rw-r-- 1 root staff 243, 1 Dec 19 15:00 scull1
crw-rw-r-- 1 root staff 243, 2 Dec 19 15:00 scull2
crw-rw-r-- 1 root staff 243, 3 Dec 19 15:00 scull3
lrwxrwxrwx 1 root root 10 Dec 19 15:00 scullpipe -> scullpipe0
crw-rw-r-- 1 root staff 243, 4 Dec 19 15:00 scullpipe0
crw-rw-r-- 1 root staff 243, 5 Dec 19 15:00 scullpipe1
crw-rw-r-- 1 root staff 243, 6 Dec 19 15:00 scullpipe2
crw-rw-r-- 1 root staff 243, 7 Dec 19 15:00 scullpipe3
crw-rw-r-- 1 root staff 243, 11 Dec 19 15:00 scullpriv
crw-rw-r-- 1 root staff 243, 8 Dec 19 15:00 scullsingle
crw-rw-r-- 1 root staff 243, 9 Dec 19 15:00 sculluid
crw-rw-r-- 1 root staff 243, 10 Dec 19 15:00 scullwuid
I am trying to write a proc driver that will print the driver history up till 10 last updates. In this driver, I haven't added the kernel data structure that I want to print. But, I am relying on the 'i' value to print the value up till 10. Here is my code.
#include <linux/init.h>
#include <linux/module.h> /** needed by all modules **/
#include <linux/kernel.h> /** This is for KERN_ALERT **/
#include <linux/proc_fs.h> /** This is for procfs **/
#include <linux/seq_file.h>
#include <linux/cdev.h> /** character device **/
#include <linux/device.h> /** for sys device registration in /dev/ and /sys/class **/
/** For class registration to work, you need GPL license **/
MODULE_LICENSE("GPL");
#define PROCFS_NAME "basicProcfs1"
static struct cdev basicCdev;
static struct class *basicDriverClass;
struct proc_dir_entry *procFileEntry = NULL;
static int basicMajorNumber = 0;
#define NUMBER_OF_MINOR_DEVICE (0)
#define NUM_MSG_HIST_ENTRIES (10)
static int gui32CmdMsgHistoryStartIndex=0;
/** This File operation table for proc file system **/
static int av_cmd_hist_show( struct seq_file *filp, void *v )
{
int i = *((int *)v);
printk("the av_cmd_hist_show called\r\n");
if ( i == 0)
{
seq_printf(filp, "Sequential print for debugging-- called for i-times i.e. 10 times \r\n");
}
seq_printf(filp, "Hello SJ proc! %d\r\n", i);
return 0;
}
static void av_cmd_hist_stop( struct seq_file *filp, void *v )
{
printk("av_cmd_hist_stop called..\r\n");
} /* av_cmd_hist_stop */
static void *av_cmd_hist_next( struct seq_file *filp, void *v, loff_t *pos )
{
(*pos)++;
printk("av_cmd_hist_next called..\r\n");
return( ( *pos < NUM_MSG_HIST_ENTRIES ) ? pos : NULL );
} /* av_cmd_hist_next */
static void *av_cmd_hist_start( struct seq_file *filp, loff_t *pos )
{
if( *pos == 0 )
{
printk("av_cmd_hist_start.. Initial..\r\n");
gui32CmdMsgHistoryStartIndex = 5;
}
printk("av_cmd_hist_start.. the *pos=0..\r\n");
return( ( *pos < NUM_MSG_HIST_ENTRIES ) ? pos : NULL );
} /* av_cmd_hist_start */
static struct seq_operations av_seq_cmd_hist_fops =
{
.start = av_cmd_hist_start,
.next = av_cmd_hist_next,
.stop = av_cmd_hist_stop,
.show = av_cmd_hist_show
};
static int basicProcShow(struct seq_file *m, void *v) {
seq_printf(m, "Hello SJ proc!\n");
return 0;
}
static int basicProcOpen(struct inode *inode, struct file *file)
{
int i;
i = seq_open( file, &av_seq_cmd_hist_fops );
return i;
//return single_open(file, basicProcShow, NULL);
}
/** Put data into the proc fs file **/
static const struct file_operations basic_proc_fops =
{
.owner = THIS_MODULE,
.open = basicProcOpen,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static struct file_operations fops = {
.read = NULL,
.write = NULL,
.open = NULL,
.release = NULL
};
static void setup_cdev(struct cdev *dev, int minor, struct file_operations *fops)
{
int err = -1;
/** MKDEV call creates a device number i.e. combination of major and minor number **/
int devno = MKDEV(basicMajorNumber, minor);
/** Initiliaze character dev with fops **/
cdev_init(dev, fops);
/**owner and operations initialized **/
dev->owner = THIS_MODULE;
dev->ops = fops;
/** add the character device to the system**/
/** Here 1 means only 1 minor number, you can give 2 for 2 minor device, the last param is the count of minor number enrolled **/
err = cdev_add (dev, devno, 1);
if (err)
{
printk (KERN_NOTICE "Couldn't add cdev");
}
}
static int chrDriverInit(void)
{
int result;
dev_t dev;
printk("Welcome!! Device Init now..");
/** int alloc_chrdev_region(dev_t *dev, unsigned int firstminor,unsigned int count, char *name); **/
/** dev -> The dev_t variable type,which will get the major number that the kernel allocates. **/
/**The same name will appear in /proc/devices. **/
/** it is registering the character device **/
/** a major number will be dynamically allocated here **/
/** alloc_chrdev_region(&dev_num, FIRST_MINOR, COUNT, DEVICE_NAME); **/
result = alloc_chrdev_region(&dev, 0, NUMBER_OF_MINOR_DEVICE, "pSeudoDrv1");
if( result < 0 )
{
printk("Error in allocating device");
return -1;
}
/** From these two if's we are avoiding the manual mknod command to create the /dev/<driver> **/
/** creating class, and then device created removes the dependency of calling mknod **/
/** A good method - the mknod way is depreciated **/
/** mknod way is - mknod /dev/<driver_name> c <majorNumber> <minorNumber>
/** add the driver to /sys/class/chardrv **/
if ((basicDriverClass = class_create(THIS_MODULE, "chardrv")) == NULL) //$ls /sys/class
{
unregister_chrdev_region(dev, 1);
return -1;
}
/** add the driver to /dev/pSeudoDrv -- here **/
if (device_create(basicDriverClass, NULL, dev, NULL, "pSeudoDrv") == NULL) //$ls /dev/
{
class_destroy(basicDriverClass);
unregister_chrdev_region(dev, 1);
return -1;
}
/** let's see what major number was assigned by the Kernel **/
basicMajorNumber = MAJOR(dev);
printk("Kernel assigned major number is %d ..\r\n",basicMajorNumber );
/** Now setup the cdev **/
setup_cdev(&basicCdev,NUMBER_OF_MINOR_DEVICE, &fops);
/** Setup Proc Entry here **/
/** 0644 means -
* 0 - owning (user) : read and write - 110
* Group - only read - 100
* Other - only read - 100 **/
procFileEntry = proc_create(PROCFS_NAME, 0, NULL, &basic_proc_fops);
if ( procFileEntry == NULL)
{
remove_proc_entry(PROCFS_NAME, NULL);
}
return 0;
}
static void chrDriverExit(void)
{
/** A reverse - destroy mechansim -- the way it was created **/
printk("Releasing Simple Devs -- %s\r\n", __FUNCTION__);
/** delete the character driver added **/
cdev_del(&basicCdev);
/** destroy the device created **/
device_destroy(basicDriverClass, MKDEV(basicMajorNumber, 0));
/** destroy the class created **/
class_destroy(basicDriverClass);
/** unregister the chr dev **/
unregister_chrdev(basicMajorNumber, NUMBER_OF_MINOR_DEVICE);
remove_proc_entry(PROCFS_NAME, NULL);
}
module_init(chrDriverInit);
module_exit(chrDriverExit);
The dmesg logs are the following.
# dmesg
[14102.921743] Releasing Simple Devs -- chrDriverExit
[14163.285107] Welcome!! Device Init now..Kernel assigned major number is 244 ..
[14174.979098] av_cmd_hist_start.. Initial..
[14174.979103] av_cmd_hist_start.. the *pos=0..
[14174.979104] the av_cmd_hist_show called
[14174.979107] av_cmd_hist_next called..
[14174.979108] the av_cmd_hist_show called
[14174.979109] av_cmd_hist_next called..
[14174.979110] the av_cmd_hist_show called
[14174.979112] av_cmd_hist_next called..
[14174.979113] the av_cmd_hist_show called
[14174.979114] av_cmd_hist_next called..
[14174.979115] the av_cmd_hist_show called
[14174.979117] av_cmd_hist_next called..
[14174.979118] the av_cmd_hist_show called
[14174.979119] av_cmd_hist_next called..
[14174.979120] the av_cmd_hist_show called
[14174.979121] av_cmd_hist_next called..
[14174.979122] the av_cmd_hist_show called
[14174.979124] av_cmd_hist_next called..
[14174.979125] the av_cmd_hist_show called
[14174.979126] av_cmd_hist_next called..
[14174.979127] the av_cmd_hist_show called
[14174.979128] av_cmd_hist_next called..
[14174.979130] av_cmd_hist_stop called..
[14174.979231] av_cmd_hist_start.. the *pos=0..
[14174.979233] av_cmd_hist_stop called..
[14174.979250] ------------[ cut here ]------------
[14174.979252] kernel BUG at mm/slub.c:3483!
[14174.979254] invalid opcode: 0000 [#2] SMP
[14174.979258] Modules linked in: procfs_driver1(O) procfs_driver(O-) tcp_lp nfsv3 nfsv4 nfs fscache dns_resolver fuse vboxpci(O) vboxnetadp(O) vboxnetflt(O) 8021q garp stp llc binfmt_misc vboxdrv(O) tpm_bios snd_hda_codec_hdmi snd_hda_codec_realtek fglrx(PO) snd_hda_intel snd_hda_codec snd_hwdep snd_seq snd_seq_device snd_pcm iTCO_wdt iTCO_vendor_support r8169 snd_timer mii e1000e snd i2c_i801 lpc_ich i2c_core soundcore snd_page_alloc coretemp kvm_intel kvm serio_raw video dcdbas microcode uinput nfsd lockd nfs_acl auth_rpcgss sunrpc crc32c_intel [last unloaded: procfs_driver1]
[14174.979297] Pid: 19055, comm: cat Tainted: P B D C O 3.6.11-4.fc16.i686 #1 Dell Inc. OptiPlex 9010/00F82W
[14174.979300] EIP: 0060:[<c0530891>] EFLAGS: 00210246 CPU: 5
The proc output is the following -
# cat /proc/basicProcfs1
Sequential print for debugging-- called for i-times i.e. 10 times
Hello SJ proc! 0
Hello SJ proc! 1
Hello SJ proc! 2
Hello SJ proc! 3
Hello SJ proc! 4
Hello SJ proc! 5
Hello SJ proc! 6
Hello SJ proc! 7
Hello SJ proc! 8
Hello SJ proc! 9
Segmentation fault
Sorry, I got the fix.
Erroneous code
static const struct file_operations basic_proc_fops =
{
.owner = THIS_MODULE,
.open = basicProcOpen,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
Right code-
static const struct file_operations basic_proc_fops =
{
.owner = THIS_MODULE,
.open = basicProcOpen,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
};
I would appreciate, if someone can explain why it makes the difference.
It is crashing becasue of this return( ( *pos < NUM_MSG_HIST_ENTRIES ) ? pos : NULL );
(I do not have enough reputation to comment, so adding it as an answer) Regarding your question on why seq_release and not single_release, 'release' functions should compliment 'open' functions. Your open function here 'basicProcOpen' calls 'seq_open' so you should be calling seq_release.
If you look at single_release, in addition to seq_release, there is a kfree(op) which causes your crash.
623 int single_release(struct inode *inode, struct file *file)
624 {
625 const struct seq_operations *op = ((struct seq_file *)file->private_data)->op;
626 int res = seq_release(inode, file);
627 kfree(op);
628 return res;
629 }
630 EXPORT_SYMBOL(single_release);