use std::str::FromStr;
use std::fmt::Debug;
use std::cmp;
use std::collections::VecDeque;

#[allow(dead_code)]
fn read_as_vec<T>() -> Vec<T>
where T: FromStr, <T as FromStr>::Err : Debug{
    let mut s = String::new();
    std::io::stdin().read_line(&mut s).unwrap();
    s.trim().split_whitespace().map(|c| T::from_str(c).unwrap()).collect()

}

#[allow(dead_code)]
fn read_as_string() -> String{
    let mut s = String::new();
    std::io::stdin().read_line(&mut s).unwrap();
    s.trim().to_string()
}

fn calc_shortest_path(maze: &Vec<Vec<char>>, costs: &mut Vec<Vec<usize>>, si: usize, sj: usize){
    let mut queue: VecDeque<(usize, usize, usize)> = VecDeque::new();
    queue.push_back((si, sj, 0));
    let dummy = std::usize::MAX;
    while let Some((i, j, ncost)) = queue.pop_front(){
        //println!("current {}, {}", i, j);
        if maze[i-1][j] == '.' && costs[i-1][j] == dummy{
            costs[i-1][j] = ncost+1;
            queue.push_back((i-1, j, ncost+1));
        }

        if maze[i+1][j] == '.' && costs[i+1][j] == dummy{
            costs[i+1][j] = ncost+1;
            queue.push_back((i+1, j, ncost+1));
        }

        if maze[i][j-1] == '.' && costs[i][j-1] == dummy{
            costs[i][j-1] = ncost+1;
            queue.push_back((i, j-1, ncost+1));
        }

        if maze[i][j+1] == '.' && costs[i][j+1] == dummy{
            costs[i][j+1] = ncost+1;
            queue.push_back((i, j+1, ncost+1));
        }
    }
}


fn calc_longest_path(maze: &Vec<Vec<char>>, si: usize, sj: usize) -> usize{
    let mut costs: Vec<Vec<usize>> = vec![vec![std::usize::MAX;maze[0].len()];maze.len()];
    costs[si][sj] = 0;
    calc_shortest_path(maze, &mut costs, si, sj);
    //println!("------start at {} {}-------", si, sj);
    //print_costs(&costs);
    let mut ans: usize = 0;
    for i in 1..maze.len()-1{
        for j in 1..maze[0].len()-1{
            if maze[i][j] == '.'{
                ans = cmp::max(costs[i][j], ans);
            }
        }
    }
    //println!("When start at({}, {}), longest path = {}", si, sj, ans);
    return ans;
}

fn print_maze(maze: &Vec<Vec<char>>){
    for row in maze{
        println!("{:?}", row);
    }
}

fn print_costs(costs: &Vec<Vec<usize>>){
    for row in costs{
        println!("{:>20?}", row);
    }
}

fn main(){
    let line = read_as_vec::<usize>();
    let (h, w) = (line[0], line[1]);
    let mut maze = vec![];
    maze.push(vec!['#';w+2]);
    for _ in 0..h{
        let mut line = read_as_string().chars().collect::<Vec<char>>();
        line.insert(0, '#');
        line.push('#');
        maze.push(line);
    }
    maze.push(vec!['#';w+2]);

    let (h, w) = (h+2, w+2);
    //print_maze(&maze);
    let mut ans = 0;

    for i in 1..h-1{
        for j in 1..w-1{
            ans = cmp::max(calc_longest_path(&maze, i, j), ans);
        }
    }


    println!("{}", ans);
}
